├── .idea ├── .name ├── .gitignore ├── compiler.xml ├── vcs.xml ├── misc.xml └── gradle.xml ├── app ├── .gitignore ├── src │ ├── debug │ │ ├── ic_launcher-playstore.png │ │ └── res │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_round.png │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_round.png │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_round.png │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_round.png │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_round.png │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ └── drawable │ │ │ └── ic_launcher_background.xml │ ├── main │ │ ├── res │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── drawable │ │ │ │ ├── background_image.xml │ │ │ │ ├── background_icon.xml │ │ │ │ ├── background_chat_input.xml │ │ │ │ ├── background_input.xml │ │ │ │ ├── background_content_bottom.xml │ │ │ │ ├── background_content_top.xml │ │ │ │ ├── background_sent_message.xml │ │ │ │ ├── background_received_message.xml │ │ │ │ ├── ic_add.xml │ │ │ │ ├── ic_info.xml │ │ │ │ ├── ic_back.xml │ │ │ │ ├── ic_round_send_24.xml │ │ │ │ ├── ic_notification.xml │ │ │ │ ├── ic_logout.xml │ │ │ │ └── ic_launcher_background.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── values │ │ │ │ ├── themes.xml │ │ │ │ ├── strings.xml │ │ │ │ └── colors.xml │ │ │ ├── values-night │ │ │ │ └── themes.xml │ │ │ ├── xml │ │ │ │ ├── backup_rules.xml │ │ │ │ └── data_extraction_rules.xml │ │ │ ├── layout │ │ │ │ ├── item_container_sent_message.xml │ │ │ │ ├── item_container_received_message.xml │ │ │ │ ├── item_container_user.xml │ │ │ │ ├── item_container_recent_conversion.xml │ │ │ │ ├── activity_users.xml │ │ │ │ ├── activity_main.xml │ │ │ │ ├── activity_sign_in.xml │ │ │ │ ├── activity_sign_up.xml │ │ │ │ └── activity_chat.xml │ │ │ └── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── chatapp │ │ │ │ ├── listeners │ │ │ │ ├── UserListener.java │ │ │ │ └── ConversionListener.java │ │ │ │ ├── models │ │ │ │ ├── User.java │ │ │ │ └── ChatMessage.java │ │ │ │ ├── network │ │ │ │ ├── ApiService.java │ │ │ │ └── ApiClient.java │ │ │ │ ├── utilities │ │ │ │ ├── PreferenceManager.java │ │ │ │ └── Constants.java │ │ │ │ ├── activities │ │ │ │ ├── BaseActivity.java │ │ │ │ ├── UsersActivity.java │ │ │ │ ├── SignInActivity.java │ │ │ │ ├── SignUpActivity.java │ │ │ │ ├── MainActivity.java │ │ │ │ └── ChatActivity.java │ │ │ │ ├── adapters │ │ │ │ ├── UsersAdapter.java │ │ │ │ ├── RecentConversationsAdapter.java │ │ │ │ └── ChatAdapter.java │ │ │ │ └── firebase │ │ │ │ └── MessagingService.java │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── example │ │ │ └── chatapp │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── example │ │ └── chatapp │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro ├── google-services.json └── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── settings.gradle ├── gradle.properties ├── gradlew.bat └── gradlew /.idea/.name: -------------------------------------------------------------------------------- 1 | Chat App -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renggadiansa/halo_dek/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/debug/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renggadiansa/halo_dek/HEAD/app/src/debug/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/debug/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renggadiansa/halo_dek/HEAD/app/src/debug/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/debug/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renggadiansa/halo_dek/HEAD/app/src/debug/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renggadiansa/halo_dek/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renggadiansa/halo_dek/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/debug/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renggadiansa/halo_dek/HEAD/app/src/debug/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/debug/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renggadiansa/halo_dek/HEAD/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renggadiansa/halo_dek/HEAD/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renggadiansa/halo_dek/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renggadiansa/halo_dek/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renggadiansa/halo_dek/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/debug/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renggadiansa/halo_dek/HEAD/app/src/debug/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/debug/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renggadiansa/halo_dek/HEAD/app/src/debug/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renggadiansa/halo_dek/HEAD/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renggadiansa/halo_dek/HEAD/app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renggadiansa/halo_dek/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renggadiansa/halo_dek/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renggadiansa/halo_dek/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renggadiansa/halo_dek/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renggadiansa/halo_dek/HEAD/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renggadiansa/halo_dek/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/debug/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renggadiansa/halo_dek/HEAD/app/src/debug/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/debug/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renggadiansa/halo_dek/HEAD/app/src/debug/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/debug/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renggadiansa/halo_dek/HEAD/app/src/debug/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/debug/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renggadiansa/halo_dek/HEAD/app/src/debug/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/debug/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renggadiansa/halo_dek/HEAD/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/chatapp/listeners/UserListener.java: -------------------------------------------------------------------------------- 1 | package com.example.chatapp.listeners; 2 | 3 | import com.example.chatapp.models.User; 4 | 5 | public interface UserListener { 6 | void onUserClicked(User user); 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/chatapp/models/User.java: -------------------------------------------------------------------------------- 1 | package com.example.chatapp.models; 2 | 3 | import java.io.Serializable; 4 | 5 | public class User implements Serializable { 6 | public String name, image, email, token, id; 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/chatapp/listeners/ConversionListener.java: -------------------------------------------------------------------------------- 1 | package com.example.chatapp.listeners; 2 | 3 | import com.example.chatapp.models.User; 4 | 5 | public interface ConversionListener { 6 | void onConversionClicked(User user); 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_image.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Dec 24 09:54:40 WIB 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_icon.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_chat_input.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_input.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/chatapp/models/ChatMessage.java: -------------------------------------------------------------------------------- 1 | package com.example.chatapp.models; 2 | 3 | import java.util.Date; 4 | 5 | public class ChatMessage { 6 | public String senderId, receiverId, message, dateTime; 7 | public Date dateObject; 8 | public String conversationId, conversionName, conversionImage; 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_content_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_content_top.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | } 14 | } 15 | rootProject.name = "Chat App" 16 | include ':app' 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_sent_message.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_received_message.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_info.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/chatapp/network/ApiService.java: -------------------------------------------------------------------------------- 1 | package com.example.chatapp.network; 2 | 3 | import java.util.HashMap; 4 | 5 | import retrofit2.Call; 6 | import retrofit2.http.Body; 7 | import retrofit2.http.HeaderMap; 8 | import retrofit2.http.POST; 9 | 10 | public interface ApiService { 11 | 12 | @POST("send") 13 | Call sendMessage( 14 | @HeaderMap HashMap headers, 15 | @Body String messageBody 16 | ); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /app/src/test/java/com/example/chatapp/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.example.chatapp; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_back.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_send_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notification.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/chatapp/network/ApiClient.java: -------------------------------------------------------------------------------- 1 | package com.example.chatapp.network; 2 | 3 | import retrofit2.Retrofit; 4 | import retrofit2.converter.scalars.ScalarsConverterFactory; 5 | 6 | 7 | public class ApiClient { 8 | 9 | private static Retrofit retrofit = null; 10 | 11 | public static Retrofit getClient() { 12 | if(retrofit == null) { 13 | retrofit = new Retrofit.Builder() 14 | .baseUrl("https://fcm.googleapis.com/fcm/") 15 | .addConverterFactory(ScalarsConverterFactory.create()) 16 | .build(); 17 | } 18 | return retrofit; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_logout.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Halo Dek👋 3 | Welcome Back! 4 | Login to continue 5 | Email 6 | Password 7 | Sign In 8 | Create New Account 9 | Add Image 10 | Name 11 | Confirm Password 12 | Sign Up 13 | Select User 14 | Type Massage 15 | Online 16 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | 11 | 12 | #1C2E46 13 | #142232 14 | #212121 15 | #757575 16 | #ECECEC 17 | #20FFFFFF 18 | #B00020 19 | #090D16 20 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/chatapp/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.example.chatapp; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | assertEquals("com.example.chatapp", appContext.getPackageName()); 25 | } 26 | } -------------------------------------------------------------------------------- /app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "536735816140", 4 | "project_id": "chat-app-29c16", 5 | "storage_bucket": "chat-app-29c16.appspot.com" 6 | }, 7 | "client": [ 8 | { 9 | "client_info": { 10 | "mobilesdk_app_id": "1:536735816140:android:cbdf5175389108505da569", 11 | "android_client_info": { 12 | "package_name": "com.example.chatapp" 13 | } 14 | }, 15 | "oauth_client": [ 16 | { 17 | "client_id": "536735816140-s95gkkg3b86nf0g9bton9kad933k7nmv.apps.googleusercontent.com", 18 | "client_type": 3 19 | } 20 | ], 21 | "api_key": [ 22 | { 23 | "current_key": "AIzaSyALWtsUIgObqvI_1JVuLz29vRbXiIxcZCc" 24 | } 25 | ], 26 | "services": { 27 | "appinvite_service": { 28 | "other_platform_oauth_client": [ 29 | { 30 | "client_id": "536735816140-s95gkkg3b86nf0g9bton9kad933k7nmv.apps.googleusercontent.com", 31 | "client_type": 3 32 | } 33 | ] 34 | } 35 | } 36 | } 37 | ], 38 | "configuration_version": "1" 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/chatapp/utilities/PreferenceManager.java: -------------------------------------------------------------------------------- 1 | package com.example.chatapp.utilities; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | 6 | public class PreferenceManager { 7 | private final SharedPreferences sharedPreferences; 8 | public PreferenceManager(Context context) { 9 | sharedPreferences = context.getSharedPreferences(Constants.KEY_PREFERENCE_NAME, Context.MODE_PRIVATE); 10 | } 11 | public void putBoolean(String key, boolean value) { 12 | SharedPreferences.Editor editor = sharedPreferences.edit(); 13 | editor.putBoolean(key, value); 14 | editor.apply(); 15 | } 16 | public boolean getBoolean(String key) { 17 | return sharedPreferences.getBoolean(key, false); 18 | } 19 | public void putString(String key, String value) { 20 | SharedPreferences.Editor editor = sharedPreferences.edit(); 21 | editor.putString(key, value); 22 | editor.apply(); 23 | } 24 | public String getString(String key) { 25 | return sharedPreferences.getString(key, null); 26 | } 27 | public void clear() { 28 | SharedPreferences.Editor editor = sharedPreferences.edit(); 29 | editor.clear(); 30 | editor.apply(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /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 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Enables namespacing of each library's R class so that its R class includes only the 19 | # resources declared in the library itself and none from the library's dependencies, 20 | # thereby reducing the size of the R class for that library 21 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /app/src/main/java/com/example/chatapp/activities/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.chatapp.activities; 2 | 3 | import android.os.Bundle; 4 | 5 | import androidx.annotation.Nullable; 6 | import androidx.appcompat.app.AppCompatActivity; 7 | 8 | import com.example.chatapp.utilities.Constants; 9 | import com.example.chatapp.utilities.PreferenceManager; 10 | import com.google.firebase.firestore.DocumentReference; 11 | import com.google.firebase.firestore.FirebaseFirestore; 12 | 13 | public class BaseActivity extends AppCompatActivity { 14 | 15 | private DocumentReference documentReference; 16 | 17 | @Override 18 | protected void onCreate(@Nullable Bundle savedInstanceState) { 19 | super.onCreate(savedInstanceState); 20 | PreferenceManager preferenceManager = new PreferenceManager(getApplicationContext()); 21 | FirebaseFirestore database = FirebaseFirestore.getInstance(); 22 | documentReference = database.collection(Constants.KEY_COLLECTION_USERS) 23 | .document(preferenceManager.getString(Constants.KEY_USER_ID)); 24 | } 25 | 26 | @Override 27 | protected void onPause() { 28 | super.onPause(); 29 | documentReference.update(Constants.KEY_AVAILABILITY, 0); 30 | } 31 | 32 | @Override 33 | protected void onResume() { 34 | super.onResume(); 35 | documentReference.update(Constants.KEY_AVAILABILITY, 1); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_container_sent_message.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 24 | 25 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'com.google.gms.google-services' 4 | } 5 | 6 | android { 7 | namespace 'com.example.chatapp' 8 | compileSdk 32 9 | 10 | defaultConfig { 11 | applicationId "com.example.chatapp" 12 | minSdk 21 13 | targetSdk 32 14 | versionCode 1 15 | versionName "1.0" 16 | multiDexEnabled true 17 | 18 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 19 | } 20 | 21 | buildTypes { 22 | release { 23 | minifyEnabled false 24 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 25 | } 26 | } 27 | compileOptions { 28 | sourceCompatibility JavaVersion.VERSION_1_8 29 | targetCompatibility JavaVersion.VERSION_1_8 30 | } 31 | buildFeatures { 32 | viewBinding true 33 | } 34 | } 35 | 36 | dependencies { 37 | 38 | implementation 'androidx.appcompat:appcompat:1.5.1' 39 | implementation 'com.google.android.material:material:1.7.0' 40 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 41 | implementation 'com.google.firebase:firebase-messaging:23.1.1' 42 | implementation 'com.google.firebase:firebase-firestore:24.4.1' 43 | testImplementation 'junit:junit:4.13.2' 44 | androidTestImplementation 'androidx.test.ext:junit:1.1.4' 45 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0' 46 | 47 | //scalable size unit (support for different screen size) 48 | implementation 'com.intuit.sdp:sdp-android:1.0.6' 49 | implementation 'com.intuit.ssp:ssp-android:1.0.6' 50 | 51 | //rounded image view 52 | implementation 'com.makeramen:roundedimageview:2.3.0' 53 | 54 | //multi dex 55 | implementation 'androidx.multidex:multidex:2.0.1' 56 | 57 | //retrofit 58 | implementation 'com.squareup.retrofit2:retrofit:2.9.0' 59 | implementation 'com.squareup.retrofit2:converter-scalars:2.9.0' 60 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/item_container_received_message.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 17 | 18 | 35 | 36 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_container_user.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 17 | 18 | 25 | 26 | 39 | 40 | 53 | 54 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_container_recent_conversion.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 17 | 18 | 25 | 26 | 39 | 40 | 53 | 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/chatapp/adapters/UsersAdapter.java: -------------------------------------------------------------------------------- 1 | package com.example.chatapp.adapters; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.BitmapFactory; 5 | import android.util.Base64; 6 | import android.view.LayoutInflater; 7 | import android.view.ViewGroup; 8 | 9 | import androidx.annotation.NonNull; 10 | import androidx.recyclerview.widget.RecyclerView; 11 | 12 | import com.example.chatapp.databinding.ItemContainerUserBinding; 13 | import com.example.chatapp.listeners.UserListener; 14 | import com.example.chatapp.models.User; 15 | 16 | import java.util.List; 17 | 18 | public class UsersAdapter extends RecyclerView.Adapter { 19 | 20 | private final List users; 21 | 22 | private final UserListener userListener; 23 | 24 | public UsersAdapter(List users, UserListener userListener) { 25 | this.users = users; 26 | this.userListener = userListener; 27 | } 28 | 29 | @NonNull 30 | @Override 31 | public UserViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 32 | ItemContainerUserBinding itemContainerUserBinding = ItemContainerUserBinding.inflate( 33 | LayoutInflater.from(parent.getContext()), 34 | parent, 35 | false 36 | ); 37 | return new UserViewHolder(itemContainerUserBinding); 38 | } 39 | 40 | @Override 41 | public void onBindViewHolder(@NonNull UsersAdapter.UserViewHolder holder, int position) { 42 | holder.setUserData(users.get(position)); 43 | 44 | } 45 | 46 | @Override 47 | public int getItemCount() { 48 | return users.size(); 49 | } 50 | 51 | class UserViewHolder extends RecyclerView.ViewHolder { 52 | ItemContainerUserBinding binding; 53 | UserViewHolder(ItemContainerUserBinding itemContainerUserBinding) { 54 | super(itemContainerUserBinding.getRoot()); 55 | binding = itemContainerUserBinding; 56 | } 57 | void setUserData(User user) { 58 | binding.textName.setText(user.name); 59 | binding.textEmail.setText(user.email); 60 | binding.imageProfile.setImageBitmap(getUserImage(user.image)); 61 | binding.getRoot().setOnClickListener(v -> userListener.onUserClicked(user)); 62 | } 63 | } 64 | 65 | private Bitmap getUserImage(String encodedImage) { 66 | byte[] bytes = Base64.decode(encodedImage, Base64.DEFAULT); 67 | return BitmapFactory.decodeByteArray(bytes, 0, bytes.length); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/chatapp/utilities/Constants.java: -------------------------------------------------------------------------------- 1 | package com.example.chatapp.utilities; 2 | 3 | import java.util.HashMap; 4 | 5 | public class Constants { 6 | public static final String KEY_COLLECTION_USERS = "users"; 7 | public static final String KEY_NAME = "name"; 8 | public static final String KEY_EMAIL = "email"; 9 | public static final String KEY_PASSWORD = "password"; 10 | public static final String KEY_PREFERENCE_NAME = "chatAppPreference"; 11 | public static final String KEY_IS_SINGED_IN = "isSignedIn"; 12 | public static final String KEY_USER_ID = "userId"; 13 | public static final String KEY_IMAGE = "image"; 14 | public static final String KEY_FCM_TOKEN = "fcmToken"; 15 | public static final String KEY_USER = "user"; 16 | public static final String KEY_COLLECTION_CHAT = "chats"; 17 | public static final String KEY_SENDER_ID = "senderId"; 18 | public static final String KEY_RECEIVER_ID = "receiverId"; 19 | public static final String KEY_MESSAGE = "message"; 20 | public static final String KEY_TIMESTAMP = "timestamp"; 21 | public static final String KEY_COLLECTION_CONVERSATIONS = "conversations"; 22 | public static final String KEY_SENDER_NAME = "senderName"; 23 | public static final String KEY_RECEIVER_NAME = "receiverName"; 24 | public static final String KEY_SENDER_IMAGE = "senderImage"; 25 | public static final String KEY_RECEIVER_IMAGE = "receiverImage"; 26 | public static final String KEY_LAST_MESSAGE = "lastMessage"; 27 | public static final String KEY_AVAILABILITY = "availability"; 28 | public static final String REMOTE_MSG_AUTHORIZATION = "Authorization"; 29 | public static final String REMOTE_MSG_CONTENT_TYPE = "Content-Type"; 30 | public static final String REMOTE_MSG_DATA = "data"; 31 | public static final String REMOTE_MSG_REGISTRATION_IDS = "registration_ids"; 32 | 33 | public static HashMap remoteMsgHeaders = null; 34 | 35 | public static HashMap getRemoteMsgHeaders() { 36 | if(remoteMsgHeaders == null) { 37 | remoteMsgHeaders = new HashMap<>(); 38 | remoteMsgHeaders.put( 39 | Constants.REMOTE_MSG_AUTHORIZATION, 40 | "key=AAAAfPfymcw:APA91bGoNGSGqP-SbeK035RVuKIdL3m4ugtiNDz_NpX_EgPMq4-eIO2I_bVl5idw1kaIBrbWXV_xPO3yXUkipWVH9JRNd5U1gtblTIW-87eqW3UmLD9081talbKAAivtvNnS0h9hJX-i" 41 | ); 42 | remoteMsgHeaders.put( 43 | REMOTE_MSG_CONTENT_TYPE, 44 | "application/json" 45 | ); 46 | } 47 | return remoteMsgHeaders; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 17 | 21 | 24 | 25 | 28 | 31 | 32 | 36 | 39 | 40 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 53 | 56 | 57 | 58 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/chatapp/adapters/RecentConversationsAdapter.java: -------------------------------------------------------------------------------- 1 | package com.example.chatapp.adapters; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.BitmapFactory; 5 | import android.util.Base64; 6 | import android.view.LayoutInflater; 7 | import android.view.ViewGroup; 8 | 9 | import androidx.annotation.NonNull; 10 | import androidx.recyclerview.widget.RecyclerView; 11 | 12 | import com.example.chatapp.databinding.ItemContainerRecentConversionBinding; 13 | import com.example.chatapp.listeners.ConversionListener; 14 | import com.example.chatapp.models.ChatMessage; 15 | import com.example.chatapp.models.User; 16 | 17 | import java.util.List; 18 | 19 | public class RecentConversationsAdapter extends RecyclerView.Adapter{ 20 | 21 | private final List chatMessage; 22 | private final ConversionListener conversionListener; 23 | 24 | public RecentConversationsAdapter(List chatMessage, ConversionListener conversionListener) { 25 | this.chatMessage = chatMessage; 26 | this.conversionListener = conversionListener; 27 | } 28 | 29 | @NonNull 30 | @Override 31 | public ConversationViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 32 | return new ConversationViewHolder( 33 | ItemContainerRecentConversionBinding.inflate( 34 | LayoutInflater.from(parent.getContext()), 35 | parent, 36 | false 37 | ) 38 | ); 39 | } 40 | 41 | @Override 42 | public void onBindViewHolder(@NonNull ConversationViewHolder holder, int position) { 43 | holder.setData(chatMessage.get(position)); 44 | 45 | } 46 | 47 | @Override 48 | public int getItemCount() { 49 | return chatMessage.size(); 50 | } 51 | 52 | class ConversationViewHolder extends RecyclerView.ViewHolder{ 53 | ItemContainerRecentConversionBinding binding; 54 | ConversationViewHolder(ItemContainerRecentConversionBinding itemContainerRecentConversionBinding) { 55 | super(itemContainerRecentConversionBinding.getRoot()); 56 | binding = itemContainerRecentConversionBinding; 57 | } 58 | void setData(ChatMessage chatMessage) { 59 | binding.imageProfile.setImageBitmap(getConversationImage(chatMessage.conversionImage)); 60 | binding.textName.setText(chatMessage.conversionName); 61 | binding.textRecentMessage.setText(chatMessage.message); 62 | binding.getRoot().setOnClickListener(v -> { 63 | User user = new User(); 64 | user.id = chatMessage.conversationId; 65 | user.name = chatMessage.conversionName; 66 | user.image = chatMessage.conversionImage; 67 | conversionListener.onConversionClicked(user); 68 | }); 69 | } 70 | } 71 | 72 | private Bitmap getConversationImage(String encodedImage) { 73 | byte[] bytes = Base64.decode(encodedImage, Base64.DEFAULT); 74 | return BitmapFactory.decodeByteArray(bytes, 0, bytes.length); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/chatapp/firebase/MessagingService.java: -------------------------------------------------------------------------------- 1 | package com.example.chatapp.firebase; 2 | 3 | import android.app.NotificationChannel; 4 | import android.app.NotificationManager; 5 | import android.app.PendingIntent; 6 | import android.content.Intent; 7 | import android.os.Build; 8 | import android.util.Log; 9 | 10 | import androidx.annotation.NonNull; 11 | import androidx.core.app.NotificationCompat; 12 | import androidx.core.app.NotificationManagerCompat; 13 | 14 | import com.example.chatapp.R; 15 | import com.example.chatapp.activities.ChatActivity; 16 | import com.example.chatapp.models.User; 17 | import com.example.chatapp.utilities.Constants; 18 | import com.google.firebase.messaging.FirebaseMessagingService; 19 | import com.google.firebase.messaging.RemoteMessage; 20 | 21 | import java.util.Random; 22 | 23 | public class MessagingService extends FirebaseMessagingService { 24 | 25 | @Override 26 | public void onNewToken(@NonNull String token) { 27 | super.onNewToken(token); 28 | Log.d("FCM","Token:" + token); 29 | } 30 | 31 | @Override 32 | public void onMessageReceived(@NonNull RemoteMessage message) { 33 | super.onMessageReceived(message); 34 | User user = new User(); 35 | user.id = message.getData().get(Constants.KEY_USER_ID); 36 | user.name = message.getData().get(Constants.KEY_NAME); 37 | user.token = message.getData().get(Constants.KEY_FCM_TOKEN); 38 | 39 | int notificationId = new Random().nextInt(); 40 | String channelId = "chat_Message"; 41 | 42 | Intent intent = new Intent(this, ChatActivity.class); 43 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); 44 | intent.putExtra(Constants.KEY_USER,user); 45 | PendingIntent pendingIntent = PendingIntent.getActivity(this,0,intent,0); 46 | 47 | NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelId); 48 | builder.setSmallIcon(R.drawable.ic_notification); 49 | builder.setContentTitle(user.name); 50 | builder.setContentText(message.getData().get(Constants.KEY_MESSAGE)); 51 | builder.setStyle(new NotificationCompat.BigTextStyle().bigText( 52 | message.getData().get(Constants.KEY_MESSAGE) 53 | )); 54 | builder.setPriority(NotificationCompat.PRIORITY_DEFAULT); 55 | builder.setContentIntent(pendingIntent); 56 | builder.setAutoCancel(true); 57 | 58 | if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 59 | CharSequence channelName = "Chat Message"; 60 | String channelDescription = "This notification channel is used for chat messages notifications"; 61 | int importance = NotificationManager.IMPORTANCE_DEFAULT; 62 | NotificationChannel channel = new NotificationChannel(channelId, channelName, importance); 63 | channel.setDescription(channelDescription); 64 | NotificationManager notificationManager = getSystemService(NotificationManager.class); 65 | notificationManager.createNotificationChannel(channel); 66 | } 67 | NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); 68 | notificationManager.notify(notificationId, builder.build()); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_users.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 24 | 25 | 36 | 37 | 44 | 45 | 55 | 56 | 61 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/chatapp/activities/UsersActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.chatapp.activities; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | 7 | import com.example.chatapp.adapters.UsersAdapter; 8 | import com.example.chatapp.databinding.ActivityUsersBinding; 9 | import com.example.chatapp.listeners.UserListener; 10 | import com.example.chatapp.models.User; 11 | import com.example.chatapp.utilities.Constants; 12 | import com.example.chatapp.utilities.PreferenceManager; 13 | import com.google.firebase.firestore.FirebaseFirestore; 14 | import com.google.firebase.firestore.QueryDocumentSnapshot; 15 | 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | public class UsersActivity extends BaseActivity implements UserListener { 20 | private ActivityUsersBinding binding; 21 | private PreferenceManager preferenceManager; 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | binding = ActivityUsersBinding.inflate(getLayoutInflater()); 27 | setContentView(binding.getRoot()); 28 | preferenceManager = new PreferenceManager(getApplicationContext()); 29 | setListeners(); 30 | getUsers(); 31 | } 32 | private void setListeners() { 33 | binding.imageBack.setOnClickListener(v -> onBackPressed()); 34 | } 35 | 36 | private void getUsers() { 37 | loading(true); 38 | FirebaseFirestore database = FirebaseFirestore.getInstance(); 39 | database.collection(Constants.KEY_COLLECTION_USERS) 40 | .get() 41 | .addOnCompleteListener(task -> { 42 | loading(false); 43 | String currentUserId = preferenceManager.getString(Constants.KEY_USER_ID); 44 | if (task.isSuccessful() && task.getResult() != null) { 45 | List users = new ArrayList<>(); 46 | for (QueryDocumentSnapshot queryDocumentSnapshot : task.getResult()) { 47 | if (currentUserId.equals(queryDocumentSnapshot.getId())) { 48 | continue; 49 | } 50 | User user = new User(); 51 | user.name = queryDocumentSnapshot.getString(Constants.KEY_NAME); 52 | user.email = queryDocumentSnapshot.getString(Constants.KEY_EMAIL); 53 | user.image = queryDocumentSnapshot.getString(Constants.KEY_IMAGE); 54 | user.token = queryDocumentSnapshot.getString(Constants.KEY_FCM_TOKEN); 55 | user.id = queryDocumentSnapshot.getId(); 56 | users.add(user); 57 | } 58 | if (users.size() > 0) { 59 | UsersAdapter usersAdapter = new UsersAdapter(users, this); 60 | binding.usersRecyclerView.setAdapter(usersAdapter); 61 | binding.usersRecyclerView.setVisibility(View.VISIBLE); 62 | } else { 63 | showErrorMessage(); 64 | } 65 | } else { 66 | showErrorMessage(); 67 | } 68 | }); 69 | } 70 | 71 | private void showErrorMessage() { 72 | binding.textErrorMessage.setText(String.format("%s", "No users available")); 73 | binding.textErrorMessage.setVisibility(View.VISIBLE); 74 | } 75 | 76 | private void loading(Boolean isLoading) { 77 | if (isLoading) { 78 | binding.progressBar.setVisibility(View.VISIBLE); 79 | } else { 80 | binding.progressBar.setVisibility(View.INVISIBLE); 81 | } 82 | } 83 | 84 | @Override 85 | public void onUserClicked(User user) { 86 | Intent intent = new Intent(getApplicationContext(), ChatActivity.class); 87 | intent.putExtra(Constants.KEY_USER, user); 88 | startActivity(intent); 89 | finish(); 90 | } 91 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 22 | 23 | 36 | 37 | 53 | 54 | 61 | 62 | 72 | 73 | 78 | 79 | 80 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/chatapp/adapters/ChatAdapter.java: -------------------------------------------------------------------------------- 1 | package com.example.chatapp.adapters; 2 | 3 | import android.graphics.Bitmap; 4 | import android.view.LayoutInflater; 5 | import android.view.ViewGroup; 6 | 7 | import androidx.annotation.NonNull; 8 | import androidx.recyclerview.widget.RecyclerView; 9 | 10 | import com.example.chatapp.databinding.ItemContainerReceivedMessageBinding; 11 | import com.example.chatapp.databinding.ItemContainerSentMessageBinding; 12 | import com.example.chatapp.models.ChatMessage; 13 | 14 | import java.util.List; 15 | 16 | public class ChatAdapter extends RecyclerView.Adapter{ 17 | 18 | private final List chatMessages; 19 | private Bitmap receiverProfileImage; 20 | private final String senderId; 21 | 22 | public static final int VIEW_TYPE_SENT = 1; 23 | public static final int VIEW_TYPE_RECEIVED = 2; 24 | 25 | public void setReceiverProfileImage(Bitmap bitmap) { 26 | receiverProfileImage = bitmap; 27 | } 28 | 29 | public ChatAdapter(List chatMessages, Bitmap receiverProfileImage, String senderId) { 30 | this.chatMessages = chatMessages; 31 | this.receiverProfileImage = receiverProfileImage; 32 | this.senderId = senderId; 33 | } 34 | 35 | @NonNull 36 | @Override 37 | public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 38 | if (viewType == VIEW_TYPE_SENT) { 39 | return new SentMessageViewHolder( 40 | ItemContainerSentMessageBinding.inflate( 41 | LayoutInflater.from(parent.getContext()), 42 | parent, 43 | false 44 | ) 45 | ); 46 | } else { 47 | return new ReceivedMessageViewHolder( 48 | ItemContainerReceivedMessageBinding.inflate( 49 | LayoutInflater.from(parent.getContext()), 50 | parent, 51 | false 52 | ) 53 | ); 54 | 55 | } 56 | } 57 | @Override 58 | public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { 59 | if(getItemViewType(position) == VIEW_TYPE_SENT) { 60 | ((SentMessageViewHolder) holder).setData(chatMessages.get(position)); 61 | } else { 62 | ((ReceivedMessageViewHolder) holder).setData(chatMessages.get(position), receiverProfileImage); 63 | } 64 | 65 | } 66 | 67 | @Override 68 | public int getItemCount() { 69 | return chatMessages.size(); 70 | } 71 | 72 | @Override 73 | public int getItemViewType(int position) { 74 | if(chatMessages.get(position).senderId.equals(senderId)) { 75 | return VIEW_TYPE_SENT; 76 | } else { 77 | return VIEW_TYPE_RECEIVED; 78 | } 79 | } 80 | 81 | static class SentMessageViewHolder extends RecyclerView.ViewHolder { 82 | private final ItemContainerSentMessageBinding binding; 83 | 84 | SentMessageViewHolder(ItemContainerSentMessageBinding itemContainerSentMessageBinding) { 85 | super(itemContainerSentMessageBinding.getRoot()); 86 | binding = itemContainerSentMessageBinding; 87 | } 88 | void setData(ChatMessage chatMessage) { 89 | binding.textMessage.setText(chatMessage.message); 90 | binding.textDateTime.setText(chatMessage.dateTime); 91 | } 92 | } 93 | static class ReceivedMessageViewHolder extends RecyclerView.ViewHolder { 94 | private final ItemContainerReceivedMessageBinding binding; 95 | 96 | ReceivedMessageViewHolder(ItemContainerReceivedMessageBinding itemContainerReceivedMessageBinding) { 97 | super(itemContainerReceivedMessageBinding.getRoot()); 98 | binding = itemContainerReceivedMessageBinding; 99 | } 100 | void setData(ChatMessage chatMessage, Bitmap receiverProfileImage) { 101 | binding.textMessage.setText(chatMessage.message); 102 | binding.textDateTime.setText(chatMessage.dateTime); 103 | if(receiverProfileImage != null) { 104 | binding.imageProfile.setImageBitmap(receiverProfileImage); 105 | } 106 | } 107 | 108 | 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_sign_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 17 | 18 | 26 | 27 | 35 | 36 | 51 | 52 | 67 | 68 | 73 | 74 | 83 | 84 | 90 | 91 | 92 | 93 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/chatapp/activities/SignInActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.chatapp.activities; 2 | 3 | import androidx.appcompat.app.AppCompatActivity; 4 | 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | import android.util.Patterns; 8 | import android.view.View; 9 | import android.widget.Toast; 10 | 11 | import com.example.chatapp.R; 12 | import com.example.chatapp.databinding.ActivitySignInBinding; 13 | import com.example.chatapp.utilities.Constants; 14 | import com.example.chatapp.utilities.PreferenceManager; 15 | import com.google.firebase.firestore.DocumentSnapshot; 16 | import com.google.firebase.firestore.FirebaseFirestore; 17 | 18 | import org.w3c.dom.Document; 19 | 20 | import java.util.HashMap; 21 | 22 | public class SignInActivity extends AppCompatActivity { 23 | 24 | private ActivitySignInBinding binding; 25 | private PreferenceManager preferenceManager; 26 | 27 | @Override 28 | protected void onCreate(Bundle savedInstanceState) { 29 | super.onCreate(savedInstanceState); 30 | preferenceManager = new PreferenceManager(getApplicationContext()); 31 | if (preferenceManager.getBoolean(Constants.KEY_IS_SINGED_IN)) { 32 | Intent intent = new Intent(getApplicationContext(), MainActivity.class); 33 | startActivity(intent); 34 | finish(); 35 | } 36 | binding = ActivitySignInBinding.inflate(getLayoutInflater()); 37 | setContentView(binding.getRoot()); 38 | setListeners(); 39 | } 40 | private void setListeners() { 41 | binding.textCreateNewAccount.setOnClickListener(v -> 42 | startActivity(new Intent(getApplicationContext(), SignUpActivity.class))); 43 | binding.buttonSignIn.setOnClickListener(v -> { 44 | if (isValidSignInDetails()) { 45 | signIn(); 46 | } 47 | }); 48 | } 49 | private void signIn() { 50 | loading(true); 51 | FirebaseFirestore database = FirebaseFirestore.getInstance(); 52 | database.collection(Constants.KEY_COLLECTION_USERS) 53 | .whereEqualTo(Constants.KEY_EMAIL, binding.inputEmail.getText().toString()) 54 | .whereEqualTo(Constants.KEY_PASSWORD, binding.inputPassword.getText().toString()) 55 | .get() 56 | .addOnCompleteListener(task ->{ 57 | if (task.isSuccessful() && task.getResult() != null 58 | && task.getResult().getDocuments().size() > 0) { 59 | DocumentSnapshot documentSnapshot = task.getResult().getDocuments().get(0); 60 | preferenceManager.putBoolean(Constants.KEY_IS_SINGED_IN, true); 61 | preferenceManager.putString(Constants.KEY_USER_ID, documentSnapshot.getId()); 62 | preferenceManager.putString(Constants.KEY_NAME, documentSnapshot.getString(Constants.KEY_NAME)); 63 | preferenceManager.putString(Constants.KEY_IMAGE, documentSnapshot.getString(Constants.KEY_IMAGE)); 64 | Intent intent = new Intent(getApplicationContext(), MainActivity.class); 65 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); 66 | startActivity(intent); 67 | }else{ 68 | loading(false); 69 | Toast.makeText(SignInActivity.this, "Unable to sign in", Toast.LENGTH_SHORT).show(); 70 | } 71 | }); 72 | 73 | } 74 | 75 | private void loading(Boolean isLoading) { 76 | if (isLoading) { 77 | binding.buttonSignIn.setVisibility(View.INVISIBLE); 78 | binding.progressBar.setVisibility(View.VISIBLE); 79 | } else { 80 | binding.progressBar.setVisibility(View.INVISIBLE); 81 | binding.buttonSignIn.setVisibility(View.VISIBLE); 82 | } 83 | } 84 | private void showToast (String message) { 85 | Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show(); 86 | } 87 | private boolean isValidSignInDetails() { 88 | if (binding.inputEmail.getText().toString().isEmpty()) { 89 | showToast("Please enter email"); 90 | return false; 91 | }else if(!Patterns.EMAIL_ADDRESS.matcher(binding.inputEmail.getText().toString()).matches()) { 92 | showToast("Please enter valid email"); 93 | return false; 94 | }else if (binding.inputPassword.getText().toString().isEmpty()) { 95 | showToast("Please enter password"); 96 | return false; 97 | }else { 98 | return true; 99 | } 100 | } 101 | 102 | } -------------------------------------------------------------------------------- /app/src/debug/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_sign_up.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | 20 | 21 | 29 | 30 | 34 | 35 | 43 | 44 | 53 | 54 | 55 | 70 | 71 | 86 | 87 | 102 | 117 | 124 | 133 | 139 | 140 | 141 | 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_chat.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 19 | 20 | 27 | 28 | 39 | 40 | 56 | 57 | 65 | 66 | 79 | 80 | 88 | 89 | 90 | 102 | 103 | 104 | 114 | 115 | 123 | 124 | 125 | 145 | 146 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/chatapp/activities/SignUpActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.chatapp.activities; 2 | 3 | import android.content.Intent; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.net.Uri; 7 | import android.os.Bundle; 8 | import android.provider.MediaStore; 9 | import android.util.Base64; 10 | import android.util.Patterns; 11 | import android.view.View; 12 | import android.widget.Toast; 13 | 14 | import androidx.activity.result.ActivityResultLauncher; 15 | import androidx.activity.result.contract.ActivityResultContracts; 16 | import androidx.appcompat.app.AppCompatActivity; 17 | 18 | import com.example.chatapp.databinding.ActivitySignUpBinding; 19 | import com.example.chatapp.utilities.Constants; 20 | import com.example.chatapp.utilities.PreferenceManager; 21 | import com.google.firebase.firestore.FirebaseFirestore; 22 | 23 | import java.io.ByteArrayOutputStream; 24 | import java.io.FileNotFoundException; 25 | import java.io.InputStream; 26 | import java.util.HashMap; 27 | 28 | public class SignUpActivity extends AppCompatActivity { 29 | 30 | private ActivitySignUpBinding binding; 31 | private PreferenceManager preferenceManager; 32 | private String encodedImage; 33 | 34 | @Override 35 | protected void onCreate(Bundle savedInstanceState) { 36 | super.onCreate(savedInstanceState); 37 | binding = ActivitySignUpBinding.inflate(getLayoutInflater()); 38 | setContentView(binding.getRoot()); 39 | preferenceManager = new PreferenceManager(getApplicationContext()); 40 | setListeners(); 41 | } 42 | private void setListeners() { 43 | binding.textSignIn.setOnClickListener(v -> onBackPressed()); 44 | binding.buttonSignUp.setOnClickListener(v -> { 45 | if (isValidSignUpDetails()) { 46 | signUp(); 47 | } 48 | }); 49 | binding.imageProfile.setOnClickListener(v ->{ 50 | Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); 51 | intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 52 | pickImage.launch(intent); 53 | }); 54 | } 55 | 56 | private void showToast(String message) { 57 | Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show(); 58 | } 59 | private void signUp() { 60 | loading(true); 61 | FirebaseFirestore database = FirebaseFirestore.getInstance(); 62 | HashMap user = new HashMap<>(); 63 | user.put(Constants.KEY_NAME, binding.inputName.getText().toString()); 64 | user.put(Constants.KEY_EMAIL, binding.inputEmail.getText().toString()); 65 | user.put(Constants.KEY_PASSWORD, binding.inputPassword.getText().toString()); 66 | user.put(Constants.KEY_IMAGE, encodedImage); 67 | database.collection(Constants.KEY_COLLECTION_USERS) 68 | .add(user) 69 | .addOnSuccessListener(documentReference -> { 70 | loading(false); 71 | showToast("Sign up success"); 72 | preferenceManager.putBoolean(Constants.KEY_IS_SINGED_IN, true); 73 | preferenceManager.putString(Constants.KEY_USER_ID, documentReference.getId()); 74 | preferenceManager.putString(Constants.KEY_NAME, binding.inputName.getText().toString()); 75 | preferenceManager.putString(Constants.KEY_IMAGE, encodedImage); 76 | Intent intent = new Intent(getApplicationContext(), MainActivity.class); 77 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); 78 | startActivity(intent); 79 | }) 80 | .addOnFailureListener(exception -> { 81 | loading(false); 82 | showToast(exception.getMessage()); 83 | }); 84 | 85 | } 86 | private String encodedImage(Bitmap bitmap) { 87 | int previewWidth = 150; 88 | int previewHeight = bitmap.getHeight() * previewWidth / bitmap.getWidth(); 89 | Bitmap previewBitmap = Bitmap.createScaledBitmap(bitmap, previewWidth, previewHeight, false); 90 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 91 | previewBitmap.compress(Bitmap.CompressFormat.JPEG, 50, byteArrayOutputStream); 92 | byte[] bytes = byteArrayOutputStream.toByteArray(); 93 | return Base64.encodeToString(bytes, Base64.DEFAULT); 94 | 95 | } 96 | private final ActivityResultLauncher pickImage = registerForActivityResult( 97 | new ActivityResultContracts.StartActivityForResult(), 98 | result -> { 99 | if (result.getResultCode() == RESULT_OK) { 100 | if (result.getData() != null) { 101 | Uri imageUri = result.getData().getData(); 102 | try { 103 | InputStream inputStream = getContentResolver().openInputStream(imageUri); 104 | Bitmap bitmap = BitmapFactory.decodeStream(inputStream); 105 | binding.imageProfile.setImageBitmap(bitmap); 106 | binding.textAddImage.setVisibility(View.GONE); 107 | encodedImage = encodedImage(bitmap); 108 | 109 | } catch (FileNotFoundException e) { 110 | e.printStackTrace(); 111 | } 112 | } 113 | } 114 | } 115 | ); 116 | 117 | private boolean isValidSignUpDetails() { 118 | if(encodedImage == null) { 119 | showToast("Please select an image"); 120 | return false; 121 | }else if(binding.inputName.getText().toString().trim().isEmpty()) { 122 | showToast("Please enter first name"); 123 | return false; 124 | } else if(binding.inputEmail.getText().toString().trim().isEmpty()) { 125 | showToast("Please enter email"); 126 | return false; 127 | }else if (!Patterns.EMAIL_ADDRESS.matcher(binding.inputEmail.getText().toString()).matches()) { 128 | showToast("Please enter valid email"); 129 | return false; 130 | }else if(binding.inputPassword.getText().toString().trim().isEmpty()) { 131 | showToast("Please enter password"); 132 | return false; 133 | }else if (binding.inputConfirmPassword.getText().toString().trim().isEmpty()) { 134 | showToast("Please enter confirm password"); 135 | return false; 136 | }else if (!binding.inputPassword.getText().toString().equals(binding.inputConfirmPassword.getText().toString())) { 137 | showToast("Password and confirm password should be same"); 138 | return false; 139 | }else { 140 | return true; 141 | } 142 | } 143 | private void loading (Boolean isLoading) { 144 | if (isLoading) { 145 | binding.buttonSignUp.setVisibility(View.INVISIBLE); 146 | binding.buttonSignUp.setVisibility(View.VISIBLE); 147 | } else { 148 | binding.buttonSignUp.setVisibility(View.INVISIBLE); 149 | binding.buttonSignUp.setVisibility(View.VISIBLE); 150 | } 151 | } 152 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/chatapp/activities/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.chatapp.activities; 2 | 3 | import android.content.Intent; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.os.Bundle; 7 | import android.util.Base64; 8 | import android.view.View; 9 | import android.widget.Toast; 10 | 11 | import com.example.chatapp.adapters.RecentConversationsAdapter; 12 | import com.example.chatapp.databinding.ActivityMainBinding; 13 | import com.example.chatapp.listeners.ConversionListener; 14 | import com.example.chatapp.models.ChatMessage; 15 | import com.example.chatapp.models.User; 16 | import com.example.chatapp.utilities.Constants; 17 | import com.example.chatapp.utilities.PreferenceManager; 18 | import com.google.firebase.firestore.DocumentChange; 19 | import com.google.firebase.firestore.DocumentReference; 20 | import com.google.firebase.firestore.EventListener; 21 | import com.google.firebase.firestore.FieldValue; 22 | import com.google.firebase.firestore.FirebaseFirestore; 23 | import com.google.firebase.firestore.QuerySnapshot; 24 | import com.google.firebase.messaging.FirebaseMessaging; 25 | 26 | import java.util.ArrayList; 27 | import java.util.Collections; 28 | import java.util.HashMap; 29 | import java.util.List; 30 | 31 | public class MainActivity extends BaseActivity implements ConversionListener { 32 | 33 | private ActivityMainBinding binding; 34 | private PreferenceManager preferenceManager; 35 | private List conversations; 36 | private RecentConversationsAdapter conversationsAdapter; 37 | private FirebaseFirestore database; 38 | 39 | @Override 40 | protected void onCreate(Bundle savedInstanceState) { 41 | super.onCreate(savedInstanceState); 42 | binding = ActivityMainBinding.inflate(getLayoutInflater()); 43 | setContentView(binding.getRoot()); 44 | preferenceManager = new PreferenceManager(getApplicationContext()); 45 | init(); 46 | loadUserDetails(); 47 | getToken(); 48 | setListeners(); 49 | listenConversations(); 50 | } 51 | 52 | private void init() { 53 | conversations = new ArrayList<>(); 54 | conversationsAdapter = new RecentConversationsAdapter(conversations, this); 55 | binding.conversationsRecyclerView.setAdapter(conversationsAdapter); 56 | database = FirebaseFirestore.getInstance(); 57 | 58 | } 59 | 60 | private void setListeners() { 61 | binding.signOut.setOnClickListener(v -> signOut()); 62 | binding.fabNewChat.setOnClickListener(v -> 63 | startActivity(new Intent(getApplicationContext(), UsersActivity.class))); 64 | 65 | } 66 | private void loadUserDetails() { 67 | binding.textName.setText((preferenceManager.getString(Constants.KEY_NAME))); 68 | byte[] bytes = Base64.decode(preferenceManager.getString(Constants.KEY_IMAGE), Base64.DEFAULT); 69 | Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length); 70 | binding.imageProfile.setImageBitmap(bitmap); 71 | 72 | } 73 | private void showToast(String message) { 74 | Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show(); 75 | } 76 | 77 | private void listenConversations() { 78 | database.collection(Constants.KEY_COLLECTION_CONVERSATIONS) 79 | .whereEqualTo(Constants.KEY_SENDER_ID, preferenceManager.getString(Constants.KEY_USER_ID)) 80 | .addSnapshotListener(eventListener); 81 | database.collection(Constants.KEY_COLLECTION_CONVERSATIONS) 82 | .whereEqualTo(Constants.KEY_RECEIVER_ID, preferenceManager.getString(Constants.KEY_USER_ID)) 83 | .addSnapshotListener(eventListener); 84 | } 85 | 86 | 87 | private final EventListener eventListener = (value, error) -> { 88 | if (error != null) { 89 | return; 90 | } 91 | if(value != null) { 92 | for (DocumentChange documentChange : value.getDocumentChanges()) { 93 | if(documentChange.getType() == DocumentChange.Type.ADDED) { 94 | String senderId = documentChange.getDocument().getString(Constants.KEY_SENDER_ID); 95 | String receiverId = documentChange.getDocument().getString(Constants.KEY_RECEIVER_ID); 96 | ChatMessage chatMessage = new ChatMessage(); 97 | chatMessage.senderId = senderId; 98 | chatMessage.receiverId = receiverId; 99 | if(preferenceManager.getString(Constants.KEY_USER_ID).equals(senderId)) { 100 | chatMessage.conversionImage = documentChange.getDocument().getString(Constants.KEY_RECEIVER_IMAGE); 101 | chatMessage.conversionName = documentChange.getDocument().getString(Constants.KEY_RECEIVER_NAME); 102 | chatMessage.conversationId = documentChange.getDocument().getString(Constants.KEY_RECEIVER_ID); 103 | } else { 104 | chatMessage.conversionImage = documentChange.getDocument().getString(Constants.KEY_SENDER_IMAGE); 105 | chatMessage.conversionName = documentChange.getDocument().getString(Constants.KEY_SENDER_NAME); 106 | chatMessage.conversationId = documentChange.getDocument().getString(Constants.KEY_SENDER_ID); 107 | } 108 | chatMessage.message = documentChange.getDocument().getString(Constants.KEY_LAST_MESSAGE); 109 | chatMessage.dateObject = documentChange.getDocument().getDate(Constants.KEY_TIMESTAMP); 110 | conversations.add(chatMessage); 111 | }else if(documentChange.getType() == DocumentChange.Type.MODIFIED) { 112 | for (int i = 0; i < conversations.size(); i++) { 113 | String senderId = documentChange.getDocument().getString(Constants.KEY_SENDER_ID); 114 | String receiverId = documentChange.getDocument().getString(Constants.KEY_RECEIVER_ID); 115 | if(conversations.get(i).senderId.equals(receiverId) && conversations.get(i).receiverId.equals(senderId)) { 116 | conversations.get(i).message = documentChange.getDocument().getString(Constants.KEY_LAST_MESSAGE); 117 | conversations.get(i).dateObject = documentChange.getDocument().getDate(Constants.KEY_TIMESTAMP); 118 | break; 119 | } 120 | } 121 | } 122 | } 123 | Collections.sort(conversations, (obj1, obj2) -> obj2.dateObject.compareTo(obj1.dateObject)); 124 | conversationsAdapter.notifyDataSetChanged(); 125 | binding.conversationsRecyclerView.smoothScrollToPosition(0); 126 | binding.conversationsRecyclerView.setVisibility(View.VISIBLE); 127 | binding.progressBar.setVisibility(View.GONE); 128 | } 129 | }; 130 | 131 | private void getToken() { 132 | FirebaseMessaging.getInstance().getToken().addOnSuccessListener(this::updateToken); 133 | } 134 | 135 | private void updateToken(String token) { 136 | preferenceManager.putString(Constants.KEY_FCM_TOKEN, token); 137 | FirebaseFirestore database = FirebaseFirestore.getInstance(); 138 | DocumentReference documentReference = database.collection(Constants.KEY_COLLECTION_USERS) 139 | .document(preferenceManager.getString(Constants.KEY_USER_ID)); 140 | documentReference.update(Constants.KEY_FCM_TOKEN, token) 141 | .addOnFailureListener(e -> showToast("Unable to update token")); 142 | } 143 | private void signOut() { 144 | showToast("Signing out..."); 145 | FirebaseFirestore database = FirebaseFirestore.getInstance(); 146 | DocumentReference documentReference = database.collection(Constants.KEY_COLLECTION_USERS) 147 | .document(preferenceManager.getString(Constants.KEY_USER_ID)); 148 | HashMap updates = new HashMap<>(); 149 | updates.put(Constants.KEY_FCM_TOKEN, FieldValue.delete()); 150 | documentReference.update(updates) 151 | .addOnSuccessListener(unused -> { 152 | preferenceManager.clear(); 153 | startActivity(new Intent(getApplicationContext(), SignInActivity.class)); 154 | finish(); 155 | }) 156 | .addOnFailureListener(e -> showToast("Unable to sign out")); 157 | 158 | } 159 | 160 | @Override 161 | public void onConversionClicked(User user) { 162 | Intent intent = new Intent(getApplicationContext(), ChatActivity.class); 163 | intent.putExtra(Constants.KEY_USER, user); 164 | startActivity(intent); 165 | } 166 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/chatapp/activities/ChatActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.chatapp.activities; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.BitmapFactory; 5 | import android.os.Bundle; 6 | import android.util.Base64; 7 | import android.view.View; 8 | import android.widget.Toast; 9 | 10 | import androidx.annotation.NonNull; 11 | 12 | import com.example.chatapp.adapters.ChatAdapter; 13 | import com.example.chatapp.databinding.ActivityChatBinding; 14 | import com.example.chatapp.models.ChatMessage; 15 | import com.example.chatapp.models.User; 16 | import com.example.chatapp.network.ApiClient; 17 | import com.example.chatapp.network.ApiService; 18 | import com.example.chatapp.utilities.Constants; 19 | import com.example.chatapp.utilities.PreferenceManager; 20 | import com.google.android.gms.tasks.OnCompleteListener; 21 | import com.google.firebase.firestore.DocumentChange; 22 | import com.google.firebase.firestore.DocumentReference; 23 | import com.google.firebase.firestore.DocumentSnapshot; 24 | import com.google.firebase.firestore.EventListener; 25 | import com.google.firebase.firestore.FirebaseFirestore; 26 | import com.google.firebase.firestore.QuerySnapshot; 27 | 28 | import org.json.JSONArray; 29 | import org.json.JSONException; 30 | import org.json.JSONObject; 31 | 32 | import java.text.SimpleDateFormat; 33 | import java.util.ArrayList; 34 | import java.util.Collections; 35 | import java.util.Date; 36 | import java.util.HashMap; 37 | import java.util.List; 38 | import java.util.Locale; 39 | import java.util.Objects; 40 | 41 | import retrofit2.Call; 42 | import retrofit2.Callback; 43 | import retrofit2.Response; 44 | 45 | public class ChatActivity extends BaseActivity { 46 | 47 | private ActivityChatBinding binding; 48 | private User receiverUser; 49 | private List chatMessages; 50 | private ChatAdapter chatAdapter; 51 | private PreferenceManager preferenceManager; 52 | private FirebaseFirestore database; 53 | private String conversionId = null; 54 | private boolean isReceiverAvailable = false; 55 | 56 | @Override 57 | protected void onCreate(Bundle savedInstanceState) { 58 | super.onCreate(savedInstanceState); 59 | binding = ActivityChatBinding.inflate(getLayoutInflater()); 60 | setContentView(binding.getRoot()); 61 | setListeners(); 62 | loadReceiverDetails(); 63 | init(); 64 | listenMessages(); 65 | } 66 | private void init() { 67 | preferenceManager = new PreferenceManager(getApplicationContext()); 68 | chatMessages = new ArrayList<>(); 69 | chatAdapter = new ChatAdapter( 70 | chatMessages, 71 | getBitmapFromEncodedString(receiverUser.image), 72 | preferenceManager.getString(Constants.KEY_USER_ID) 73 | ); 74 | binding.chatRecyclerView.setAdapter(chatAdapter); 75 | database = FirebaseFirestore.getInstance(); 76 | } 77 | 78 | private void sendMessage() { 79 | HashMap message = new HashMap<>(); 80 | message.put(Constants.KEY_SENDER_ID, preferenceManager.getString(Constants.KEY_USER_ID)); 81 | message.put(Constants.KEY_RECEIVER_ID, receiverUser.id); 82 | message.put(Constants.KEY_MESSAGE, binding.inputMessage.getText().toString()); 83 | message.put(Constants.KEY_TIMESTAMP, new Date()); 84 | database.collection(Constants.KEY_COLLECTION_CHAT).add(message); 85 | if(conversionId != null) { 86 | updateConversion(binding.inputMessage.getText().toString()); 87 | }else{ 88 | HashMap conversion = new HashMap<>(); 89 | conversion.put(Constants.KEY_SENDER_ID, preferenceManager.getString(Constants.KEY_USER_ID)); 90 | conversion.put(Constants.KEY_SENDER_NAME, preferenceManager.getString(Constants.KEY_NAME)); 91 | conversion.put(Constants.KEY_SENDER_IMAGE, preferenceManager.getString(Constants.KEY_IMAGE)); 92 | conversion.put(Constants.KEY_RECEIVER_ID, receiverUser.id); 93 | conversion.put(Constants.KEY_RECEIVER_NAME, receiverUser.name); 94 | conversion.put(Constants.KEY_RECEIVER_IMAGE, receiverUser.image); 95 | conversion.put(Constants.KEY_LAST_MESSAGE, binding.inputMessage.getText().toString()); 96 | conversion.put(Constants.KEY_TIMESTAMP, new Date()); 97 | addConversion(conversion); 98 | } 99 | if(!isReceiverAvailable) { 100 | try { 101 | JSONArray tokens = new JSONArray(); 102 | tokens.put(receiverUser.token); 103 | 104 | JSONObject data = new JSONObject(); 105 | data.put(Constants.KEY_USER_ID, preferenceManager.getString(Constants.KEY_USER_ID)); 106 | data.put(Constants.KEY_NAME, preferenceManager.getString(Constants.KEY_NAME)); 107 | data.put(Constants.KEY_FCM_TOKEN, preferenceManager.getString(Constants.KEY_FCM_TOKEN)); 108 | data.put(Constants.KEY_MESSAGE, binding.inputMessage.getText().toString()); 109 | 110 | JSONObject body = new JSONObject(); 111 | body.put(Constants.REMOTE_MSG_DATA, data); 112 | body.put(Constants.REMOTE_MSG_REGISTRATION_IDS, tokens); 113 | 114 | sendNotificationS(body.toString()); 115 | }catch (Exception exception) { 116 | showToast(exception.getMessage()); 117 | } 118 | } 119 | binding.inputMessage.setText(null); 120 | } 121 | 122 | private void showToast(String message) { 123 | Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show(); 124 | } 125 | 126 | private void sendNotificationS(String messageBody) { 127 | ApiClient.getClient().create(ApiService.class).sendMessage( 128 | Constants.getRemoteMsgHeaders(), 129 | messageBody 130 | ).enqueue(new Callback() { 131 | @Override 132 | public void onResponse(@NonNull Call call, @NonNull Response response) { 133 | if(response.isSuccessful()) { 134 | try { 135 | if(response.body() != null) { 136 | JSONObject responseJson = new JSONObject(response.body()); 137 | JSONArray resultsArray = responseJson.getJSONArray("results"); 138 | if(responseJson.getInt("failure") == 0) { 139 | JSONObject error = (JSONObject) resultsArray.get(0); 140 | showToast(error.getString("error")); 141 | return; 142 | } 143 | } 144 | 145 | }catch (JSONException e) { 146 | e.printStackTrace(); 147 | } 148 | showToast("Message sent successfully"); 149 | 150 | }else{ 151 | showToast("Error: " + response.code()); 152 | } 153 | 154 | } 155 | 156 | @Override 157 | public void onFailure(@NonNull Call call,@NonNull Throwable t) { 158 | showToast(t.getMessage()); 159 | 160 | } 161 | }); 162 | } 163 | 164 | private void listenAvailabilityOfReceiver() { 165 | database.collection(Constants.KEY_COLLECTION_USERS).document( 166 | receiverUser.id 167 | ).addSnapshotListener(ChatActivity.this,(value, error) -> { 168 | if(error != null) { 169 | return; 170 | } 171 | if(value != null) { 172 | if(value.getLong(Constants.KEY_AVAILABILITY) != null) { 173 | int availability = Objects.requireNonNull( 174 | value.getLong(Constants.KEY_AVAILABILITY) 175 | ).intValue(); 176 | isReceiverAvailable = availability == 1; 177 | } 178 | receiverUser.token = value.getString(Constants.KEY_FCM_TOKEN); 179 | if(receiverUser.image == null) { 180 | receiverUser.image = value.getString(Constants.KEY_IMAGE); 181 | chatAdapter.setReceiverProfileImage(getBitmapFromEncodedString(receiverUser.image)); 182 | chatAdapter.notifyItemRangeInserted(0, chatMessages.size()); 183 | } 184 | } 185 | if(isReceiverAvailable) { 186 | binding.textAvailability.setVisibility(View.VISIBLE); 187 | }else{ 188 | binding.textAvailability.setVisibility(View.GONE); 189 | } 190 | }); 191 | } 192 | 193 | 194 | private void listenMessages() { 195 | database.collection(Constants.KEY_COLLECTION_CHAT) 196 | .whereEqualTo(Constants.KEY_SENDER_ID, preferenceManager.getString(Constants.KEY_USER_ID)) 197 | .whereEqualTo(Constants.KEY_RECEIVER_ID, receiverUser.id) 198 | .addSnapshotListener(eventListener); 199 | database.collection(Constants.KEY_COLLECTION_CHAT) 200 | .whereEqualTo(Constants.KEY_SENDER_ID, receiverUser.id) 201 | .whereEqualTo(Constants.KEY_RECEIVER_ID, preferenceManager.getString(Constants.KEY_USER_ID)) 202 | .addSnapshotListener(eventListener); 203 | } 204 | 205 | 206 | private final EventListener eventListener = (value, error) -> { 207 | if(error != null) { 208 | return; 209 | } 210 | if(value != null) { 211 | int count = chatMessages.size(); 212 | for (DocumentChange documentChange : value.getDocumentChanges()) { 213 | if(documentChange.getType() == DocumentChange.Type.ADDED) { 214 | ChatMessage chatMessage = new ChatMessage(); 215 | chatMessage.senderId = documentChange.getDocument().getString(Constants.KEY_SENDER_ID); 216 | chatMessage.receiverId = documentChange.getDocument().getString(Constants.KEY_RECEIVER_ID); 217 | chatMessage.message = documentChange.getDocument().getString(Constants.KEY_MESSAGE); 218 | chatMessage.dateTime = getReadableDate(documentChange.getDocument().getDate(Constants.KEY_TIMESTAMP)); 219 | chatMessage.dateObject = documentChange.getDocument().getDate(Constants.KEY_TIMESTAMP); 220 | chatMessages.add(chatMessage); 221 | } 222 | } 223 | Collections.sort(chatMessages, (obj1, obj2) -> obj1.dateTime.compareTo(obj2.dateTime)); 224 | if(count == 0) { 225 | chatAdapter.notifyDataSetChanged(); 226 | } else { 227 | chatAdapter.notifyItemRangeChanged(chatMessages.size(), chatMessages.size()); 228 | binding.chatRecyclerView.smoothScrollToPosition(chatMessages.size() - 1); 229 | } 230 | binding.chatRecyclerView.setVisibility(View.VISIBLE); 231 | } 232 | binding.progressBar.setVisibility(View.GONE); 233 | if(conversionId == null) { 234 | checkForConversion(); 235 | } 236 | }; 237 | 238 | 239 | private Bitmap getBitmapFromEncodedString(String encodedImage) { 240 | if(encodedImage != null) { 241 | byte[] bytes = Base64.decode(encodedImage, Base64.DEFAULT); 242 | return BitmapFactory.decodeByteArray(bytes, 0, bytes.length); 243 | }else{ 244 | return null; 245 | } 246 | } 247 | 248 | private void loadReceiverDetails() { 249 | receiverUser = (User) getIntent().getSerializableExtra(Constants.KEY_USER); 250 | binding.textName.setText(receiverUser.name); 251 | } 252 | private void setListeners() { 253 | binding.imageBack.setOnClickListener(v -> onBackPressed()); 254 | binding.layoutSend.setOnClickListener(v -> sendMessage()); 255 | } 256 | 257 | private String getReadableDate(Date date) { 258 | return new SimpleDateFormat("MMM dd, yyyy - hh:mm a", Locale.getDefault()).format(date); 259 | } 260 | 261 | private void addConversion(HashMap conversion) { 262 | database.collection(Constants.KEY_COLLECTION_CONVERSATIONS) 263 | .add(conversion) 264 | .addOnSuccessListener(documentReference -> conversionId = documentReference.getId()); 265 | } 266 | 267 | private void updateConversion(String message) { 268 | DocumentReference documentReference = 269 | database.collection(Constants.KEY_COLLECTION_CONVERSATIONS).document(conversionId); 270 | documentReference.update( 271 | Constants.KEY_LAST_MESSAGE, message, 272 | Constants.KEY_TIMESTAMP, new Date() 273 | ); 274 | 275 | } 276 | 277 | private void checkForConversion() { 278 | if(chatMessages.size() != 0) { 279 | checkForConversionRemotely( 280 | preferenceManager.getString(Constants.KEY_USER_ID), 281 | receiverUser.id 282 | ); 283 | checkForConversionRemotely( 284 | receiverUser.id, 285 | preferenceManager.getString(Constants.KEY_USER_ID) 286 | ); 287 | } 288 | } 289 | 290 | private void checkForConversionRemotely(String senderId, String receiverId) { 291 | database.collection(Constants.KEY_COLLECTION_CONVERSATIONS) 292 | .whereEqualTo(Constants.KEY_SENDER_ID, senderId) 293 | .whereEqualTo(Constants.KEY_RECEIVER_ID, receiverId) 294 | .get() 295 | .addOnCompleteListener(conversionOnCompleteListener); 296 | } 297 | 298 | private final OnCompleteListener conversionOnCompleteListener = task -> { 299 | if(task.isSuccessful() && task.getResult() != null && task.getResult().getDocuments().size() > 0) { 300 | DocumentSnapshot documentSnapshot = task.getResult().getDocuments().get(0); 301 | conversionId = documentSnapshot.getId(); 302 | } 303 | }; 304 | 305 | @Override 306 | protected void onResume() { 307 | super.onResume(); 308 | listenAvailabilityOfReceiver(); 309 | } 310 | } --------------------------------------------------------------------------------