11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2018 Anshdeep Singh
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Kotlin Messenger
2 | A one-to-one chat app (similar to WhatsApp) built using Kotlin and Firebase.
3 |
4 | ## Screenshots
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | ## Libraries used:
16 | 1. [Firebase Authentication](https://firebase.google.com/docs/auth/)
17 | 2. [Firebase Realtime Database](https://firebase.google.com/docs/database/)
18 | 3. [Firebase Cloud Storage](https://firebase.google.com/docs/storage/)
19 | 4. [Glide](https://github.com/bumptech/glide/)
20 | 5. [Groupie](https://github.com/lisawray/groupie)
21 |
22 | ## License
23 |
24 | Copyright 2018 Anshdeep Singh
25 |
26 | Licensed under the Apache License, Version 2.0 (the "License");
27 | you may not use this file except in compliance with the License.
28 | You may obtain a copy of the License at
29 |
30 | http://www.apache.org/licenses/LICENSE-2.0
31 |
32 | Unless required by applicable law or agreed to in writing, software
33 | distributed under the License is distributed on an "AS IS" BASIS,
34 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
35 | See the License for the specific language governing permissions and
36 | limitations under the License.
37 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | apply plugin: 'kotlin-android'
4 |
5 | apply plugin: 'kotlin-android-extensions'
6 |
7 | androidExtensions {
8 | experimental = true
9 | }
10 |
11 | android {
12 | compileSdkVersion 28
13 | defaultConfig {
14 | applicationId "com.anshdeep.kotlinmessenger"
15 | minSdkVersion 19
16 | targetSdkVersion 28
17 | versionCode 1
18 | versionName "1.0"
19 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
20 | }
21 | buildTypes {
22 | release {
23 | minifyEnabled false
24 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
25 | }
26 | }
27 | }
28 |
29 | dependencies {
30 | implementation fileTree(dir: 'libs', include: ['*.jar'])
31 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${kotlin_version}"
32 | implementation 'androidx.appcompat:appcompat:1.0.2'
33 |
34 | // AndroidX Support Libraries
35 | implementation 'androidx.cardview:cardview:1.0.0'
36 | implementation 'androidx.recyclerview:recyclerview:1.0.0'
37 | implementation 'com.google.android.material:material:1.0.0'
38 | implementation 'androidx.legacy:legacy-support-v4:1.0.0'
39 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
40 |
41 | // Testing
42 | testImplementation 'junit:junit:4.12'
43 | androidTestImplementation 'androidx.test:runner:1.2.0'
44 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
45 |
46 |
47 | // Firebase
48 | implementation "com.google.firebase:firebase-core:17.0.1"
49 | implementation "com.google.firebase:firebase-auth:18.1.0"
50 | implementation "com.google.firebase:firebase-storage:18.1.0"
51 | implementation "com.google.firebase:firebase-database:18.0.0"
52 |
53 | // Circle Image View
54 | implementation "de.hdodenhof:circleimageview:2.2.0"
55 |
56 | // Groupie - for complex recycler views
57 | implementation "com.xwray:groupie:2.1.0"
58 |
59 | // Picasso
60 | implementation "com.squareup.picasso:picasso:2.71828"
61 | implementation "com.wang.avi:library:2.1.3"
62 |
63 | // Glide
64 | implementation "com.github.bumptech.glide:glide:4.8.0"
65 | annotationProcessor "com.github.bumptech.glide:compiler:4.8.0"
66 |
67 | }
68 | apply plugin: 'com.google.gms.google-services'
69 |
--------------------------------------------------------------------------------
/app/google-services.json:
--------------------------------------------------------------------------------
1 | {
2 | "project_info": {
3 | "project_number": "988221398804",
4 | "firebase_url": "https://kotlinmessenger-d385d.firebaseio.com",
5 | "project_id": "kotlinmessenger-d385d",
6 | "storage_bucket": "kotlinmessenger-d385d.appspot.com"
7 | },
8 | "client": [
9 | {
10 | "client_info": {
11 | "mobilesdk_app_id": "1:988221398804:android:ec1c31386495b212",
12 | "android_client_info": {
13 | "package_name": "com.anshdeep.kotlinmessenger"
14 | }
15 | },
16 | "oauth_client": [
17 | {
18 | "client_id": "988221398804-c0jljl4farp2k48gee40ftckg3r96uib.apps.googleusercontent.com",
19 | "client_type": 3
20 | }
21 | ],
22 | "api_key": [
23 | {
24 | "current_key": "AIzaSyAcOFt6qr5QPC1WIA_LAlPj3BrHZv_bxe0"
25 | }
26 | ],
27 | "services": {
28 | "analytics_service": {
29 | "status": 1
30 | },
31 | "appinvite_service": {
32 | "status": 1,
33 | "other_platform_oauth_client": []
34 | },
35 | "ads_service": {
36 | "status": 2
37 | }
38 | }
39 | }
40 | ],
41 | "configuration_version": "1"
42 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/anshdeep/kotlinmessenger/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.anshdeep.kotlinmessenger
2 |
3 | import androidx.test.InstrumentationRegistry
4 | import androidx.test.runner.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getTargetContext()
22 | assertEquals("com.anshdeep.kotlinmessenger", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
25 |
26 |
29 |
30 |
33 |
36 |
37 |
40 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/app/src/main/java/com/anshdeep/kotlinmessenger/messages/ChatLogActivity.kt:
--------------------------------------------------------------------------------
1 | package com.anshdeep.kotlinmessenger.messages
2 |
3 | import android.os.Bundle
4 | import android.util.Log
5 | import android.widget.Toast
6 | import androidx.appcompat.app.AppCompatActivity
7 | import androidx.core.content.ContextCompat
8 | import com.anshdeep.kotlinmessenger.R
9 | import com.anshdeep.kotlinmessenger.models.ChatMessage
10 | import com.anshdeep.kotlinmessenger.models.User
11 | import com.anshdeep.kotlinmessenger.utils.DateUtils.getFormattedTimeChatLog
12 | import com.bumptech.glide.Glide
13 | import com.bumptech.glide.request.RequestOptions
14 | import com.google.firebase.auth.FirebaseAuth
15 | import com.google.firebase.database.*
16 | import com.xwray.groupie.GroupAdapter
17 | import com.xwray.groupie.Item
18 | import com.xwray.groupie.ViewHolder
19 | import kotlinx.android.synthetic.main.activity_chat_log.*
20 | import kotlinx.android.synthetic.main.chat_from_row.view.*
21 | import kotlinx.android.synthetic.main.chat_to_row.view.*
22 |
23 | class ChatLogActivity : AppCompatActivity() {
24 |
25 | companion object {
26 | val TAG = ChatLogActivity::class.java.simpleName
27 | }
28 |
29 | val adapter = GroupAdapter()
30 |
31 | // Bundle Data
32 | private val toUser: User
33 | get() = intent.getParcelableExtra(NewMessageActivity.USER_KEY)
34 |
35 | override fun onCreate(savedInstanceState: Bundle?) {
36 | super.onCreate(savedInstanceState)
37 | setContentView(R.layout.activity_chat_log)
38 |
39 | swiperefresh.setColorSchemeColors(ContextCompat.getColor(this, R.color.colorAccent))
40 |
41 | recyclerview_chat_log.adapter = adapter
42 |
43 | supportActionBar?.title = toUser.name
44 |
45 | listenForMessages()
46 |
47 | send_button_chat_log.setOnClickListener {
48 | performSendMessage()
49 | }
50 | }
51 |
52 | private fun listenForMessages() {
53 | swiperefresh.isEnabled = true
54 | swiperefresh.isRefreshing = true
55 |
56 | val fromId = FirebaseAuth.getInstance().uid ?: return
57 | val toId = toUser.uid
58 | val ref = FirebaseDatabase.getInstance().getReference("/user-messages/$fromId/$toId")
59 |
60 | ref.addListenerForSingleValueEvent(object : ValueEventListener {
61 | override fun onCancelled(databaseError: DatabaseError) {
62 | Log.d(TAG, "database error: " + databaseError.message)
63 | }
64 |
65 | override fun onDataChange(dataSnapshot: DataSnapshot) {
66 | Log.d(TAG, "has children: " + dataSnapshot.hasChildren())
67 | if (!dataSnapshot.hasChildren()) {
68 | swiperefresh.isRefreshing = false
69 | swiperefresh.isEnabled = false
70 | }
71 | }
72 | })
73 |
74 | ref.addChildEventListener(object : ChildEventListener {
75 | override fun onCancelled(databaseError: DatabaseError) {
76 | }
77 |
78 | override fun onChildMoved(dataSnapshot: DataSnapshot, previousChildName: String?) {
79 | }
80 |
81 | override fun onChildChanged(dataSnapshot: DataSnapshot, previousChildName: String?) {
82 | }
83 |
84 | override fun onChildAdded(dataSnapshot: DataSnapshot, previousChildName: String?) {
85 | dataSnapshot.getValue(ChatMessage::class.java)?.let {
86 | if (it.fromId == FirebaseAuth.getInstance().uid) {
87 | val currentUser = LatestMessagesActivity.currentUser ?: return
88 | adapter.add(ChatFromItem(it.text, currentUser, it.timestamp))
89 | } else {
90 | adapter.add(ChatToItem(it.text, toUser, it.timestamp))
91 | }
92 | }
93 | recyclerview_chat_log.scrollToPosition(adapter.itemCount - 1)
94 | swiperefresh.isRefreshing = false
95 | swiperefresh.isEnabled = false
96 | }
97 |
98 | override fun onChildRemoved(dataSnapshot: DataSnapshot) {
99 | }
100 |
101 | })
102 |
103 | }
104 |
105 | private fun performSendMessage() {
106 | val text = edittext_chat_log.text.toString()
107 | if (text.isEmpty()) {
108 | Toast.makeText(this, "Message cannot be empty", Toast.LENGTH_SHORT).show()
109 | return
110 | }
111 |
112 | val fromId = FirebaseAuth.getInstance().uid ?: return
113 | val toId = toUser.uid
114 |
115 | val reference = FirebaseDatabase.getInstance().getReference("/user-messages/$fromId/$toId").push()
116 | val toReference = FirebaseDatabase.getInstance().getReference("/user-messages/$toId/$fromId").push()
117 |
118 | val chatMessage = ChatMessage(reference.key!!, text, fromId, toId, System.currentTimeMillis() / 1000)
119 | reference.setValue(chatMessage)
120 | .addOnSuccessListener {
121 | Log.d(TAG, "Saved our chat message: ${reference.key}")
122 | edittext_chat_log.text.clear()
123 | recyclerview_chat_log.smoothScrollToPosition(adapter.itemCount - 1)
124 | }
125 |
126 | toReference.setValue(chatMessage)
127 |
128 |
129 | val latestMessageRef = FirebaseDatabase.getInstance().getReference("/latest-messages/$fromId/$toId")
130 | latestMessageRef.setValue(chatMessage)
131 |
132 | val latestMessageToRef = FirebaseDatabase.getInstance().getReference("/latest-messages/$toId/$fromId")
133 | latestMessageToRef.setValue(chatMessage)
134 | }
135 |
136 | }
137 |
138 | class ChatFromItem(val text: String, val user: User, val timestamp: Long) : Item() {
139 |
140 | override fun bind(viewHolder: ViewHolder, position: Int) {
141 |
142 | viewHolder.itemView.textview_from_row.text = text
143 | viewHolder.itemView.from_msg_time.text = getFormattedTimeChatLog(timestamp)
144 |
145 | val targetImageView = viewHolder.itemView.imageview_chat_from_row
146 |
147 | if (!user.profileImageUrl!!.isEmpty()) {
148 |
149 | val requestOptions = RequestOptions().placeholder(R.drawable.no_image2)
150 |
151 |
152 | Glide.with(targetImageView.context)
153 | .load(user.profileImageUrl)
154 | .thumbnail(0.1f)
155 | .apply(requestOptions)
156 | .into(targetImageView)
157 |
158 | }
159 | }
160 |
161 | override fun getLayout(): Int {
162 | return R.layout.chat_from_row
163 | }
164 |
165 | }
166 |
167 | class ChatToItem(val text: String, val user: User, val timestamp: Long) : Item() {
168 |
169 | override fun bind(viewHolder: ViewHolder, position: Int) {
170 | viewHolder.itemView.textview_to_row.text = text
171 | viewHolder.itemView.to_msg_time.text = getFormattedTimeChatLog(timestamp)
172 |
173 | val targetImageView = viewHolder.itemView.imageview_chat_to_row
174 |
175 | if (!user.profileImageUrl!!.isEmpty()) {
176 |
177 | val requestOptions = RequestOptions().placeholder(R.drawable.no_image2)
178 |
179 | Glide.with(targetImageView.context)
180 | .load(user.profileImageUrl)
181 | .thumbnail(0.1f)
182 | .apply(requestOptions)
183 | .into(targetImageView)
184 |
185 | }
186 | }
187 |
188 | override fun getLayout(): Int {
189 | return R.layout.chat_to_row
190 | }
191 |
192 | }
193 |
194 |
195 |
--------------------------------------------------------------------------------
/app/src/main/java/com/anshdeep/kotlinmessenger/messages/LatestMessagesActivity.kt:
--------------------------------------------------------------------------------
1 | package com.anshdeep.kotlinmessenger.messages
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import androidx.core.content.ContextCompat
6 | import androidx.appcompat.app.AppCompatActivity
7 | import android.util.Log
8 | import android.view.Menu
9 | import android.view.MenuItem
10 | import com.anshdeep.kotlinmessenger.R
11 | import com.anshdeep.kotlinmessenger.messages.NewMessageActivity.Companion.USER_KEY
12 | import com.anshdeep.kotlinmessenger.models.ChatMessage
13 | import com.anshdeep.kotlinmessenger.models.User
14 | import com.anshdeep.kotlinmessenger.ui.register.RegisterActivity
15 | import com.anshdeep.kotlinmessenger.views.LatestMessageRow
16 | import com.google.firebase.auth.FirebaseAuth
17 | import com.google.firebase.database.*
18 | import com.xwray.groupie.GroupAdapter
19 | import com.xwray.groupie.ViewHolder
20 | import kotlinx.android.synthetic.main.activity_latest_messages.*
21 |
22 |
23 | class LatestMessagesActivity : AppCompatActivity() {
24 |
25 | private val adapter = GroupAdapter()
26 | private val latestMessagesMap = HashMap()
27 |
28 |
29 | companion object {
30 | var currentUser: User? = null
31 | val TAG = LatestMessagesActivity::class.java.simpleName
32 | }
33 |
34 | override fun onCreate(savedInstanceState: Bundle?) {
35 | super.onCreate(savedInstanceState)
36 | setContentView(R.layout.activity_latest_messages)
37 | verifyUserIsLoggedIn()
38 |
39 | recyclerview_latest_messages.adapter = adapter
40 |
41 | swiperefresh.setColorSchemeColors(ContextCompat.getColor(this, R.color.colorAccent))
42 |
43 | fetchCurrentUser()
44 | listenForLatestMessages()
45 |
46 | adapter.setOnItemClickListener { item, _ ->
47 | val intent = Intent(this, ChatLogActivity::class.java)
48 | val row = item as LatestMessageRow
49 | intent.putExtra(USER_KEY, row.chatPartnerUser)
50 | startActivity(intent)
51 | }
52 |
53 |
54 | new_message_fab.setOnClickListener {
55 | val intent = Intent(this, NewMessageActivity::class.java)
56 | startActivity(intent)
57 | }
58 |
59 | swiperefresh.setOnRefreshListener {
60 | verifyUserIsLoggedIn()
61 | fetchCurrentUser()
62 | listenForLatestMessages()
63 | }
64 | }
65 |
66 | private fun refreshRecyclerViewMessages() {
67 | adapter.clear()
68 | latestMessagesMap.values.forEach {
69 | adapter.add(LatestMessageRow(it, this))
70 | }
71 | swiperefresh.isRefreshing = false
72 | }
73 |
74 | private fun listenForLatestMessages() {
75 | swiperefresh.isRefreshing = true
76 | val fromId = FirebaseAuth.getInstance().uid ?: return
77 | val ref = FirebaseDatabase.getInstance().getReference("/latest-messages/$fromId")
78 |
79 | ref.addListenerForSingleValueEvent(object : ValueEventListener {
80 | override fun onCancelled(databaseError: DatabaseError) {
81 | Log.d(TAG, "database error: " + databaseError.message)
82 | }
83 |
84 | override fun onDataChange(dataSnapshot: DataSnapshot) {
85 | Log.d(TAG, "has children: " + dataSnapshot.hasChildren())
86 | if (!dataSnapshot.hasChildren()) {
87 | swiperefresh.isRefreshing = false
88 | }
89 | }
90 |
91 | })
92 |
93 |
94 | ref.addChildEventListener(object : ChildEventListener {
95 | override fun onCancelled(databaseError: DatabaseError) {
96 | }
97 |
98 | override fun onChildMoved(dataSnapshot: DataSnapshot, previousChildName: String?) {
99 | }
100 |
101 | override fun onChildChanged(dataSnapshot: DataSnapshot, previousChildName: String?) {
102 | dataSnapshot.getValue(ChatMessage::class.java)?.let {
103 | latestMessagesMap[dataSnapshot.key!!] = it
104 | refreshRecyclerViewMessages()
105 | }
106 | }
107 |
108 | override fun onChildAdded(dataSnapshot: DataSnapshot, previousChildName: String?) {
109 | dataSnapshot.getValue(ChatMessage::class.java)?.let {
110 | latestMessagesMap[dataSnapshot.key!!] = it
111 | refreshRecyclerViewMessages()
112 | }
113 | }
114 |
115 | override fun onChildRemoved(p0: DataSnapshot) {
116 | }
117 |
118 | })
119 | }
120 |
121 | private fun fetchCurrentUser() {
122 | val uid = FirebaseAuth.getInstance().uid ?: return
123 | val ref = FirebaseDatabase.getInstance().getReference("/users/$uid")
124 | ref.addListenerForSingleValueEvent(object : ValueEventListener {
125 | override fun onCancelled(databaseError: DatabaseError) {
126 | }
127 |
128 | override fun onDataChange(dataSnapshot: DataSnapshot) {
129 | currentUser = dataSnapshot.getValue(User::class.java)
130 | }
131 |
132 | })
133 | }
134 |
135 | private fun verifyUserIsLoggedIn() {
136 | val uid = FirebaseAuth.getInstance().uid
137 | if (uid == null) {
138 | val intent = Intent(this, RegisterActivity::class.java)
139 | intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK.or(Intent.FLAG_ACTIVITY_NEW_TASK)
140 | startActivity(intent)
141 | }
142 | }
143 |
144 | override fun onCreateOptionsMenu(menu: Menu?): Boolean {
145 | menuInflater.inflate(R.menu.nav_menu, menu)
146 | return super.onCreateOptionsMenu(menu)
147 | }
148 |
149 | override fun onOptionsItemSelected(item: MenuItem?): Boolean {
150 | when (item?.itemId) {
151 |
152 | R.id.menu_sign_out -> {
153 | FirebaseAuth.getInstance().signOut()
154 | val intent = Intent(this, RegisterActivity::class.java)
155 | intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK.or(Intent.FLAG_ACTIVITY_NEW_TASK)
156 | startActivity(intent)
157 | }
158 | }
159 |
160 | return super.onOptionsItemSelected(item)
161 | }
162 |
163 |
164 | }
165 |
--------------------------------------------------------------------------------
/app/src/main/java/com/anshdeep/kotlinmessenger/messages/NewMessageActivity.kt:
--------------------------------------------------------------------------------
1 | package com.anshdeep.kotlinmessenger.messages
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.os.Bundle
7 | import androidx.core.content.ContextCompat
8 | import androidx.appcompat.app.AppCompatActivity
9 | import android.util.Log
10 | import com.anshdeep.kotlinmessenger.R
11 | import com.anshdeep.kotlinmessenger.models.User
12 | import com.anshdeep.kotlinmessenger.views.BigImageDialog
13 | import com.bumptech.glide.Glide
14 | import com.bumptech.glide.request.RequestOptions
15 | import com.google.firebase.auth.FirebaseAuth
16 | import com.google.firebase.database.DataSnapshot
17 | import com.google.firebase.database.DatabaseError
18 | import com.google.firebase.database.FirebaseDatabase
19 | import com.google.firebase.database.ValueEventListener
20 | import com.xwray.groupie.GroupAdapter
21 | import com.xwray.groupie.Item
22 | import com.xwray.groupie.ViewHolder
23 | import kotlinx.android.synthetic.main.activity_new_message.*
24 | import kotlinx.android.synthetic.main.user_row_new_message.view.*
25 |
26 | class NewMessageActivity : AppCompatActivity() {
27 |
28 | companion object {
29 | const val USER_KEY = "USER_KEY"
30 | private val TAG = NewMessageActivity::class.java.simpleName
31 | }
32 |
33 | override fun onCreate(savedInstanceState: Bundle?) {
34 | super.onCreate(savedInstanceState)
35 | setContentView(R.layout.activity_new_message)
36 |
37 | swiperefresh.setColorSchemeColors(ContextCompat.getColor(this, R.color.colorAccent))
38 |
39 | supportActionBar?.title = "Select User"
40 |
41 | fetchUsers()
42 | //Todo - Add more users and messages for screenshots
43 |
44 | swiperefresh.setOnRefreshListener {
45 | fetchUsers()
46 | }
47 | }
48 |
49 | private fun fetchUsers() {
50 | swiperefresh.isRefreshing = true
51 |
52 | val ref = FirebaseDatabase.getInstance().getReference("/users")
53 | ref.addListenerForSingleValueEvent(object : ValueEventListener {
54 | override fun onCancelled(databaseError: DatabaseError) {
55 |
56 | }
57 |
58 | override fun onDataChange(dataSnapshot: DataSnapshot) {
59 | val adapter = GroupAdapter()
60 |
61 | dataSnapshot.children.forEach {
62 | Log.d(TAG, it.toString())
63 | @Suppress("NestedLambdaShadowedImplicitParameter")
64 | it.getValue(User::class.java)?.let {
65 | if (it.uid != FirebaseAuth.getInstance().uid) {
66 | adapter.add(UserItem(it, this@NewMessageActivity))
67 | }
68 | }
69 | }
70 |
71 | adapter.setOnItemClickListener { item, view ->
72 | val userItem = item as UserItem
73 | val intent = Intent(view.context, ChatLogActivity::class.java)
74 | intent.putExtra(USER_KEY, userItem.user)
75 | startActivity(intent)
76 | finish()
77 | }
78 |
79 | recyclerview_newmessage.adapter = adapter
80 | swiperefresh.isRefreshing = false
81 | }
82 |
83 | })
84 | }
85 | }
86 |
87 | class UserItem(val user: User, val context: Context) : Item() {
88 |
89 | override fun bind(viewHolder: ViewHolder, position: Int) {
90 | viewHolder.itemView.username_textview_new_message.text = user.name
91 |
92 | if (!user.profileImageUrl!!.isEmpty()) {
93 | val requestOptions = RequestOptions().placeholder(R.drawable.no_image2)
94 |
95 |
96 | Glide.with(viewHolder.itemView.imageview_new_message.context)
97 | .load(user.profileImageUrl)
98 | .apply(requestOptions)
99 | .into(viewHolder.itemView.imageview_new_message)
100 |
101 | viewHolder.itemView.imageview_new_message.setOnClickListener {
102 | BigImageDialog.newInstance(user?.profileImageUrl!!).show((context as Activity).fragmentManager
103 | , "")
104 | }
105 | }
106 | }
107 |
108 | override fun getLayout(): Int {
109 | return R.layout.user_row_new_message
110 | }
111 |
112 | }
113 |
--------------------------------------------------------------------------------
/app/src/main/java/com/anshdeep/kotlinmessenger/models/ChatMessage.kt:
--------------------------------------------------------------------------------
1 | package com.anshdeep.kotlinmessenger.models
2 |
3 | /**
4 | * Created by ansh on 28/08/18.
5 | */
6 | class ChatMessage(
7 | val id: String,
8 | val text: String,
9 | val fromId: String,
10 | val toId: String,
11 | val timestamp: Long
12 | ) {
13 | constructor() : this("", "", "", "", -1)
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/anshdeep/kotlinmessenger/models/User.kt:
--------------------------------------------------------------------------------
1 | package com.anshdeep.kotlinmessenger.models
2 |
3 | import android.os.Parcelable
4 | import kotlinx.android.parcel.Parcelize
5 |
6 | /**
7 | * Created by ansh on 25/08/18.
8 | */
9 | @Parcelize
10 | data class User(
11 | val uid: String,
12 | val name: String,
13 | val profileImageUrl: String?
14 | ) : Parcelable {
15 | constructor() : this("", "", "")
16 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/anshdeep/kotlinmessenger/ui/login/LoginActivity.kt:
--------------------------------------------------------------------------------
1 | package com.anshdeep.kotlinmessenger.ui.login
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import androidx.appcompat.app.ActionBar
6 | import androidx.appcompat.app.AppCompatActivity
7 | import android.util.Log
8 | import android.view.View
9 | import android.widget.Toast
10 | import com.anshdeep.kotlinmessenger.R
11 | import com.anshdeep.kotlinmessenger.messages.LatestMessagesActivity
12 | import com.bumptech.glide.Glide
13 | import com.bumptech.glide.request.RequestOptions
14 | import com.google.firebase.auth.FirebaseAuth
15 | import kotlinx.android.synthetic.main.activity_login.*
16 |
17 | /**
18 | * Created by ansh on 18/08/18.
19 | */
20 | class LoginActivity : AppCompatActivity() {
21 |
22 | private val TAG = LoginActivity::class.java.simpleName
23 |
24 | override fun onCreate(savedInstanceState: Bundle?) {
25 | super.onCreate(savedInstanceState)
26 | setContentView(R.layout.activity_login)
27 | supportActionBar?.displayOptions = ActionBar.DISPLAY_SHOW_CUSTOM
28 | supportActionBar?.setCustomView(R.layout.abs_layout)
29 | supportActionBar!!.elevation = 0.0f
30 |
31 | Glide.with(this).asGif()
32 | .load("https://media1.tenor.com/images/1d550cc7494b9ac5a85fbe4f6bc184c8/tenor.gif?itemid=11525834")
33 | .apply(RequestOptions.circleCropTransform())
34 | .into(kotlinImageView)
35 |
36 | login_button_login.setOnClickListener {
37 | performLogin()
38 | }
39 |
40 | back_to_register_textview.setOnClickListener {
41 | finish()
42 | overridePendingTransition(R.anim.left_to_right, R.anim.right_to_left)
43 | }
44 | }
45 |
46 | override fun onBackPressed() {
47 | super.onBackPressed()
48 | overridePendingTransition(R.anim.left_to_right, R.anim.right_to_left)
49 | }
50 |
51 | private fun performLogin() {
52 | val email = email_edittext_login.text.toString()
53 | val password = password_edittext_login.text.toString()
54 |
55 | if (email.isEmpty() || password.isEmpty()) {
56 | Toast.makeText(this, "Please fill all the fields", Toast.LENGTH_SHORT).show()
57 | return
58 | }
59 |
60 | back_to_register_textview.visibility = View.GONE
61 | loading_view.visibility = View.VISIBLE
62 |
63 | FirebaseAuth.getInstance().signInWithEmailAndPassword(email, password)
64 | .addOnCompleteListener {
65 | if (!it.isSuccessful) return@addOnCompleteListener
66 | Log.d(TAG, "Successfully logged in: ${it.result!!.user.uid}")
67 |
68 | val intent = Intent(this, LatestMessagesActivity::class.java)
69 | intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK.or(Intent.FLAG_ACTIVITY_NEW_TASK)
70 | startActivity(intent)
71 | overridePendingTransition(R.anim.enter, R.anim.exit)
72 | }
73 | .addOnFailureListener {
74 | Toast.makeText(this, "${it.message}", Toast.LENGTH_SHORT).show()
75 |
76 | back_to_register_textview.visibility = View.VISIBLE
77 | loading_view.visibility = View.GONE
78 | }
79 | }
80 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/anshdeep/kotlinmessenger/ui/register/RegisterActivity.kt:
--------------------------------------------------------------------------------
1 | package com.anshdeep.kotlinmessenger.ui.register
2 |
3 | import android.app.Activity
4 | import android.content.Intent
5 | import android.net.Uri
6 | import android.os.Bundle
7 | import android.provider.MediaStore
8 | import androidx.appcompat.app.ActionBar
9 | import androidx.appcompat.app.AppCompatActivity
10 | import android.util.Log
11 | import android.view.View
12 | import android.widget.Toast
13 | import com.anshdeep.kotlinmessenger.R
14 | import com.anshdeep.kotlinmessenger.messages.LatestMessagesActivity
15 | import com.anshdeep.kotlinmessenger.models.User
16 | import com.anshdeep.kotlinmessenger.ui.login.LoginActivity
17 | import com.google.firebase.auth.FirebaseAuth
18 | import com.google.firebase.database.FirebaseDatabase
19 | import com.google.firebase.storage.FirebaseStorage
20 | import com.squareup.picasso.Picasso
21 | import kotlinx.android.synthetic.main.activity_register.*
22 | import java.util.*
23 |
24 | class RegisterActivity : AppCompatActivity() {
25 |
26 | private var selectedPhotoUri: Uri? = null
27 |
28 | companion object {
29 | val TAG = RegisterActivity::class.java.simpleName!!
30 | }
31 |
32 | override fun onCreate(savedInstanceState: Bundle?) {
33 | super.onCreate(savedInstanceState)
34 | setContentView(R.layout.activity_register)
35 | supportActionBar?.displayOptions = ActionBar.DISPLAY_SHOW_CUSTOM
36 | supportActionBar?.setCustomView(R.layout.abs_layout)
37 | supportActionBar?.elevation = 0.0f
38 |
39 | register_button_register.setOnClickListener {
40 | performRegistration()
41 | }
42 |
43 | already_have_account_text_view.setOnClickListener {
44 | val intent = Intent(this, LoginActivity::class.java)
45 | startActivity(intent)
46 | overridePendingTransition(R.anim.enter, R.anim.exit)
47 | }
48 |
49 | selectphoto_button_register.setOnClickListener {
50 | val intent = Intent(Intent.ACTION_PICK)
51 | intent.type = "image/*"
52 | startActivityForResult(intent, 0)
53 | }
54 | }
55 |
56 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
57 | if (requestCode == 0 && resultCode == Activity.RESULT_OK && data != null) {
58 | selectedPhotoUri = data.data ?: return
59 | Log.d(TAG, "Photo was selected")
60 | // Get and resize profile image
61 | val filePathColumn = arrayOf(MediaStore.Images.Media.DATA)
62 | contentResolver.query(selectedPhotoUri!!, filePathColumn, null, null, null)?.use {
63 | it.moveToFirst()
64 | val columnIndex = it.getColumnIndex(filePathColumn[0])
65 | val picturePath = it.getString(columnIndex)
66 | // If picture chosen from camera rotate by 270 degrees else
67 | if (picturePath.contains("DCIM")) {
68 | Picasso.get().load(selectedPhotoUri).rotate(270f).into(selectphoto_imageview_register)
69 | } else {
70 | Picasso.get().load(selectedPhotoUri).into(selectphoto_imageview_register)
71 | }
72 | }
73 | selectphoto_button_register.alpha = 0f
74 | }
75 | }
76 |
77 | private fun performRegistration() {
78 | val email = email_edittext_register.text.toString()
79 | val password = password_edittext_register.text.toString()
80 | val name = name_edittext_register.text.toString()
81 |
82 | if (email.isEmpty() || password.isEmpty() || name.isEmpty()) {
83 | Toast.makeText(this, "Please fill all the fields", Toast.LENGTH_SHORT).show()
84 | return
85 | }
86 |
87 | if (selectedPhotoUri == null) {
88 | Toast.makeText(this, "Please select a photo", Toast.LENGTH_SHORT).show()
89 | return
90 | }
91 |
92 | already_have_account_text_view.visibility = View.GONE
93 | loading_view.visibility = View.VISIBLE
94 |
95 | // Firebase Authentication to create a user with email and password
96 | FirebaseAuth.getInstance().createUserWithEmailAndPassword(email, password)
97 | .addOnCompleteListener {
98 | if (!it.isSuccessful) return@addOnCompleteListener
99 |
100 | // else if successful
101 | Log.d(TAG, "Successfully created user with uid: ${it.result!!.user.uid}")
102 | uploadImageToFirebaseStorage()
103 | }
104 | .addOnFailureListener {
105 | Log.d(TAG, "Failed to create user: ${it.message}")
106 | loading_view.visibility = View.GONE
107 | already_have_account_text_view.visibility = View.VISIBLE
108 | Toast.makeText(this, "${it.message}", Toast.LENGTH_LONG).show()
109 | }
110 | }
111 |
112 | private fun uploadImageToFirebaseStorage() {
113 | if (selectedPhotoUri == null) {
114 | // save user without photo
115 | saveUserToFirebaseDatabase(null)
116 | } else {
117 | val filename = UUID.randomUUID().toString()
118 | val ref = FirebaseStorage.getInstance().getReference("/images/$filename")
119 | ref.putFile(selectedPhotoUri!!)
120 | .addOnSuccessListener {
121 | Log.d(TAG, "Successfully uploaded image: ${it.metadata?.path}")
122 |
123 | @Suppress("NestedLambdaShadowedImplicitParameter")
124 | ref.downloadUrl.addOnSuccessListener {
125 | Log.d(TAG, "File Location: $it")
126 | saveUserToFirebaseDatabase(it.toString())
127 | }
128 | }
129 | .addOnFailureListener {
130 | Log.d(TAG, "Failed to upload image to storage: ${it.message}")
131 | loading_view.visibility = View.GONE
132 | already_have_account_text_view.visibility = View.VISIBLE
133 | }
134 | }
135 |
136 | }
137 |
138 | private fun saveUserToFirebaseDatabase(profileImageUrl: String?) {
139 | val uid = FirebaseAuth.getInstance().uid ?: return
140 | val ref = FirebaseDatabase.getInstance().getReference("/users/$uid")
141 |
142 | val user = if (profileImageUrl == null) {
143 | User(uid, name_edittext_register.text.toString(), null)
144 | } else {
145 | User(uid, name_edittext_register.text.toString(), profileImageUrl)
146 | }
147 |
148 | ref.setValue(user)
149 | .addOnSuccessListener {
150 | Log.d(TAG, "Finally we saved the user to Firebase Database")
151 |
152 | val intent = Intent(this, LatestMessagesActivity::class.java)
153 | intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK.or(Intent.FLAG_ACTIVITY_NEW_TASK)
154 | startActivity(intent)
155 | overridePendingTransition(R.anim.enter, R.anim.exit)
156 | }
157 | .addOnFailureListener {
158 | Log.d(TAG, "Failed to set value to database: ${it.message}")
159 | loading_view.visibility = View.GONE
160 | already_have_account_text_view.visibility = View.VISIBLE
161 | }
162 | }
163 | }
164 |
165 |
--------------------------------------------------------------------------------
/app/src/main/java/com/anshdeep/kotlinmessenger/utils/DateUtils.kt:
--------------------------------------------------------------------------------
1 | package com.anshdeep.kotlinmessenger.utils
2 |
3 | import android.text.format.DateUtils
4 | import java.text.SimpleDateFormat
5 | import java.util.*
6 |
7 | /**
8 | * Created by ansh on 13/10/18.
9 | */
10 | object DateUtils {
11 |
12 | val fullFormattedTime = SimpleDateFormat("d MMM, h:mm a", Locale.US) // the format of your date
13 | val onlyTime = SimpleDateFormat("h:mm a", Locale.US) // the format of your date
14 | val onlyDate = SimpleDateFormat("d MMM", Locale.US) // the format of your date
15 |
16 | fun getFormattedTime(timeInMilis: Long): String {
17 | val date = Date(timeInMilis * 1000L) // *1000 is to convert seconds to milliseconds
18 |
19 | return when {
20 | isToday(date) -> onlyTime.format(date)
21 | isYesterday(date) -> "Yesterday"
22 | else -> onlyDate.format(date)
23 | }
24 |
25 | }
26 |
27 | fun getFormattedTimeChatLog(timeInMilis: Long): String {
28 | val date = Date(timeInMilis * 1000L) // *1000 is to convert seconds to milliseconds
29 | val fullFormattedTime = SimpleDateFormat("d MMM, h:mm a", Locale.US) // the format of your date
30 | val onlyTime = SimpleDateFormat("h:mm a", Locale.US) // the format of your date
31 |
32 | return when {
33 | isToday(date) -> onlyTime.format(date)
34 | else -> fullFormattedTime.format(date)
35 | }
36 |
37 | }
38 |
39 | fun isYesterday(d: Date): Boolean {
40 | return DateUtils.isToday(d.time + DateUtils.DAY_IN_MILLIS)
41 | }
42 |
43 | fun isToday(d: Date): Boolean {
44 | return DateUtils.isToday(d.time)
45 | }
46 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/anshdeep/kotlinmessenger/views/BigImageDialog.kt:
--------------------------------------------------------------------------------
1 | package com.anshdeep.kotlinmessenger.views
2 |
3 | import android.app.DialogFragment
4 | import android.graphics.Color
5 | import android.graphics.drawable.ColorDrawable
6 | import android.os.Bundle
7 | import androidx.swiperefreshlayout.widget.CircularProgressDrawable
8 | import android.view.LayoutInflater
9 | import android.view.View
10 | import android.view.ViewGroup
11 | import android.view.Window
12 | import com.anshdeep.kotlinmessenger.R
13 | import com.bumptech.glide.Glide
14 | import com.bumptech.glide.request.RequestOptions
15 | import kotlinx.android.synthetic.main.image_dialog_layout.view.*
16 |
17 | /**
18 | * Created by ansh on 11/03/19.
19 | */
20 | class BigImageDialog : DialogFragment() {
21 | private var imageUrl = ""
22 | override fun onCreate(savedInstanceState: Bundle?) {
23 | super.onCreate(savedInstanceState)
24 | arguments?.let {
25 | imageUrl = arguments!!.getString("url")
26 | }
27 | }
28 |
29 |
30 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
31 | val v = inflater.inflate(R.layout.image_dialog_layout, container, false)
32 | this.dialog.window.requestFeature(Window.FEATURE_NO_TITLE)
33 | this.dialog.window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
34 |
35 | val circularProgressDrawable = CircularProgressDrawable(this.dialog.context)
36 | circularProgressDrawable.strokeWidth = 5f
37 | circularProgressDrawable.centerRadius = 30f
38 | circularProgressDrawable.start()
39 |
40 | val requestOptions = RequestOptions().placeholder(circularProgressDrawable)
41 |
42 | Glide.with(this.dialog.context)
43 | .load(imageUrl)
44 | .apply(requestOptions)
45 | .into(v.bigImageView)
46 |
47 | return v
48 | }
49 |
50 |
51 | companion object {
52 | @JvmStatic
53 | fun newInstance(imageUrl: String) =
54 | BigImageDialog().apply {
55 | arguments = Bundle().apply {
56 | putString("url", imageUrl)
57 | }
58 | }
59 | }
60 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/anshdeep/kotlinmessenger/views/LatestMessageRow.kt:
--------------------------------------------------------------------------------
1 | package com.anshdeep.kotlinmessenger.views
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import com.anshdeep.kotlinmessenger.R
6 | import com.anshdeep.kotlinmessenger.models.ChatMessage
7 | import com.anshdeep.kotlinmessenger.models.User
8 | import com.anshdeep.kotlinmessenger.utils.DateUtils
9 | import com.bumptech.glide.Glide
10 | import com.bumptech.glide.request.RequestOptions
11 | import com.google.firebase.auth.FirebaseAuth
12 | import com.google.firebase.database.DataSnapshot
13 | import com.google.firebase.database.DatabaseError
14 | import com.google.firebase.database.FirebaseDatabase
15 | import com.google.firebase.database.ValueEventListener
16 | import com.xwray.groupie.Item
17 | import com.xwray.groupie.ViewHolder
18 | import kotlinx.android.synthetic.main.latest_message_row.view.*
19 |
20 |
21 | /**
22 | * Created by ansh on 04/09/18.
23 | */
24 | class LatestMessageRow(val chatMessage: ChatMessage, val context: Context) : Item() {
25 |
26 | var chatPartnerUser: User? = null
27 |
28 | override fun getLayout(): Int {
29 | return R.layout.latest_message_row
30 | }
31 |
32 | override fun bind(viewHolder: ViewHolder, position: Int) {
33 | viewHolder.itemView.latest_message_textview.text = chatMessage.text
34 |
35 | val chatPartnerId: String
36 | if (chatMessage.fromId == FirebaseAuth.getInstance().uid) {
37 | chatPartnerId = chatMessage.toId
38 | } else {
39 | chatPartnerId = chatMessage.fromId
40 | }
41 |
42 | val ref = FirebaseDatabase.getInstance().getReference("/users/$chatPartnerId")
43 | ref.addListenerForSingleValueEvent(object : ValueEventListener {
44 | override fun onCancelled(p0: DatabaseError) {
45 |
46 | }
47 |
48 | override fun onDataChange(p0: DataSnapshot) {
49 | chatPartnerUser = p0.getValue(User::class.java)
50 | viewHolder.itemView.username_textview_latest_message.text = chatPartnerUser?.name
51 | viewHolder.itemView.latest_msg_time.text = DateUtils.getFormattedTime(chatMessage.timestamp)
52 |
53 | if (!chatPartnerUser?.profileImageUrl?.isEmpty()!!) {
54 | val requestOptions = RequestOptions().placeholder(R.drawable.no_image2)
55 |
56 | Glide.with(viewHolder.itemView.imageview_latest_message.context)
57 | .load(chatPartnerUser?.profileImageUrl)
58 | .apply(requestOptions)
59 | .into(viewHolder.itemView.imageview_latest_message)
60 |
61 | viewHolder.itemView.imageview_latest_message.setOnClickListener {
62 | BigImageDialog.newInstance(chatPartnerUser?.profileImageUrl!!).show((context as Activity).fragmentManager
63 | , "")
64 | }
65 |
66 | }
67 | }
68 |
69 | })
70 |
71 |
72 | }
73 |
74 | }
--------------------------------------------------------------------------------
/app/src/main/res/anim/enter.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/exit.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/left_to_right.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/right_to_left.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
11 |
16 |
21 |
26 |
31 |
36 |
41 |
46 |
51 |
56 |
61 |
66 |
71 |
76 |
81 |
86 |
91 |
96 |
101 |
106 |
111 |
116 |
121 |
126 |
131 |
136 |
141 |
146 |
151 |
156 |
161 |
166 |
171 |
172 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_message_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_person_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_power_settings_new_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_send_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/kotlin_android.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ansh94/KotlinMessenger/59c60426fd3af138da869b8771404094a9ebb3a6/app/src/main/res/drawable/kotlin_android.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/no_image2.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ansh94/KotlinMessenger/59c60426fd3af138da869b8771404094a9ebb3a6/app/src/main/res/drawable/no_image2.jpeg
--------------------------------------------------------------------------------
/app/src/main/res/drawable/rounded_button.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/rounded_edittext_register_login.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/rounded_select_photo_button.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/shape_bg_incoming_bubble.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/shape_bg_outgoing_bubble.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/abs_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_chat_log.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
18 |
19 |
30 |
31 |
38 |
39 |
56 |
57 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_latest_messages.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
16 |
25 |
26 |
27 |
28 |
29 |
38 |
39 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_login.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
21 |
22 |
38 |
39 |
53 |
54 |
67 |
68 |
84 |
85 |
99 |
100 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_new_message.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
14 |
15 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_register.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
16 |
33 |
34 |
47 |
48 |
61 |
62 |
75 |
76 |
77 |
94 |
95 |
109 |
110 |
126 |
127 |
135 |
136 |
137 |
138 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/chat_from_row.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
26 |
27 |
39 |
40 |
50 |
51 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/chat_to_row.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
26 |
27 |
38 |
39 |
49 |
50 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/image_dialog_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/latest_message_row.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
20 |
21 |
34 |
35 |
49 |
50 |
51 |
62 |
63 |
64 |
73 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/user_row_new_message.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
22 |
23 |
39 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/nav_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ansh94/KotlinMessenger/59c60426fd3af138da869b8771404094a9ebb3a6/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ansh94/KotlinMessenger/59c60426fd3af138da869b8771404094a9ebb3a6/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ansh94/KotlinMessenger/59c60426fd3af138da869b8771404094a9ebb3a6/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ansh94/KotlinMessenger/59c60426fd3af138da869b8771404094a9ebb3a6/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ansh94/KotlinMessenger/59c60426fd3af138da869b8771404094a9ebb3a6/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ansh94/KotlinMessenger/59c60426fd3af138da869b8771404094a9ebb3a6/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ansh94/KotlinMessenger/59c60426fd3af138da869b8771404094a9ebb3a6/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ansh94/KotlinMessenger/59c60426fd3af138da869b8771404094a9ebb3a6/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ansh94/KotlinMessenger/59c60426fd3af138da869b8771404094a9ebb3a6/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ansh94/KotlinMessenger/59c60426fd3af138da869b8771404094a9ebb3a6/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | @android:color/black
4 | @android:color/black
5 | #B3E5FC
6 | @android:color/holo_red_dark
7 | #757575
8 | #FFFFFF
9 | #BDBDBD
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Kotlin Messenger
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/test/java/com/anshdeep/kotlinmessenger/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.anshdeep.kotlinmessenger
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext.kotlin_version = '1.3.31'
5 | repositories {
6 | google()
7 | jcenter()
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.4.2'
11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
12 |
13 | // NOTE: Do not place your application dependencies here; they belong
14 | // in the individual module build.gradle files
15 | classpath 'com.google.gms:google-services:4.2.0'
16 |
17 | }
18 | }
19 |
20 | allprojects {
21 | repositories {
22 | mavenCentral()
23 | google()
24 | jcenter()
25 | }
26 | }
27 |
28 | task clean(type: Delete) {
29 | delete rootProject.buildDir
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | android.enableJetifier=true
10 | android.useAndroidX=true
11 | org.gradle.jvmargs=-Xmx1536m
12 | # When configured, Gradle will run in incubating parallel mode.
13 | # This option should only be used with decoupled projects. More details, visit
14 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
15 | # org.gradle.parallel=true
16 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ansh94/KotlinMessenger/59c60426fd3af138da869b8771404094a9ebb3a6/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Oct 15 21:04:57 IST 2018
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/graphics/screen1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ansh94/KotlinMessenger/59c60426fd3af138da869b8771404094a9ebb3a6/graphics/screen1.png
--------------------------------------------------------------------------------
/graphics/screen2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ansh94/KotlinMessenger/59c60426fd3af138da869b8771404094a9ebb3a6/graphics/screen2.png
--------------------------------------------------------------------------------
/graphics/screen3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ansh94/KotlinMessenger/59c60426fd3af138da869b8771404094a9ebb3a6/graphics/screen3.png
--------------------------------------------------------------------------------
/graphics/screen4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ansh94/KotlinMessenger/59c60426fd3af138da869b8771404094a9ebb3a6/graphics/screen4.png
--------------------------------------------------------------------------------
/graphics/screen4extra.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ansh94/KotlinMessenger/59c60426fd3af138da869b8771404094a9ebb3a6/graphics/screen4extra.png
--------------------------------------------------------------------------------
/graphics/screen5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ansh94/KotlinMessenger/59c60426fd3af138da869b8771404094a9ebb3a6/graphics/screen5.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------