├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── google-services.json ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── app │ │ └── sample │ │ └── fchat │ │ ├── activity │ │ ├── ChatActivity.java │ │ ├── MainActivity.java │ │ ├── SelectFriendActivity.java │ │ └── SplashActivity.java │ │ ├── adapter │ │ ├── ChatDetailsListAdapter.kt │ │ ├── ChatsListAdapter.kt │ │ └── FriendsListAdapter.kt │ │ ├── data │ │ ├── ParseFirebaseData.java │ │ └── SettingsAPI.java │ │ ├── fragment │ │ ├── ConversationListFragment.kt │ │ └── FragmentAdapter.java │ │ ├── model │ │ ├── ChatMessage.kt │ │ └── Friend.kt │ │ ├── service │ │ ├── MyFirebaseMessagingService.java │ │ └── NotificationService.kt │ │ ├── ui │ │ ├── CustomToast.kt │ │ └── ViewHelper.kt │ │ ├── util │ │ ├── Constants.kt │ │ └── Tools.java │ │ └── widget │ │ ├── CircleTransform.java │ │ └── DividerItemDecoration.java │ └── res │ ├── anim │ └── slide_in_bottom.xml │ ├── drawable │ ├── baseline_done_24.xml │ ├── baseline_done_all_24.xml │ ├── baseline_exit_to_app_24.xml │ ├── button_send.xml │ ├── circle_blue.xml │ ├── ic_discuss.png │ ├── ic_logo_white.png │ ├── ic_not_found.png │ ├── ic_people.png │ ├── ic_send.png │ ├── layout_click.xml │ ├── round_error_24.xml │ ├── round_info_24.xml │ ├── round_success_24.xml │ ├── round_warning_24.xml │ ├── thread_bg_me.xml │ ├── thread_bg_you.xml │ ├── toolbar_gradient.xml │ └── unknown_avatar.xml │ ├── layout │ ├── activity_chat.xml │ ├── activity_main.xml │ ├── activity_new_chat.xml │ ├── activity_splash.xml │ ├── custom_meaasge.xml │ ├── fragment_chat.xml │ ├── row_chat_details.xml │ ├── row_chats.xml │ ├── row_friends.xml │ └── toolbar.xml │ ├── menu │ └── menu_main.xml │ ├── mipmap │ └── ic_launcher.png │ ├── values-v21 │ └── styles.xml │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenshots ├── screenshot_1.png ├── screenshot_2.png ├── screenshot_3.png └── screenshot_4.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | .idea 4 | /local.properties 5 | /.idea/workspace.xml 6 | /.idea/libraries 7 | .DS_Store 8 | /build 9 | /.gradle 10 | /.idea 11 | /captures 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Bibaswann Bandyopadhyay 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FChat 2 | This is a simple real time one-to-one chat app for android using firebase database. Users can login using their google accounts and have conversations with other registered members. 3 | 4 | ![ScreenShot](https://raw.github.com/g0g0l/FChat/master/screenshots/screenshot_1.png) 5 | ![ScreenShot](https://raw.github.com/g0g0l/FChat/master/screenshots/screenshot_2.png) 6 | ![ScreenShot](https://raw.github.com/g0g0l/FChat/master/screenshots/screenshot_3.png) 7 | ![ScreenShot](https://raw.github.com/g0g0l/FChat/master/screenshots/screenshot_4.png) 8 | 9 | Before you use: 10 | 1. Create a firebase project in your firebase console 11 | 2. Enable google login for firebase in your firebase project (more http://www.androidhive.info/2016/06/android-getting-started-firebase-simple-login-registration-auth/) 12 | 3. Create SHA1 fingerprint in your system 13 | 14 | Windows: keytool -list -v -keystore "%USERPROFILE%\.android\debug.keystore" -alias androiddebugkey -storepass android -keypass android (From Java->jdk-bin) 15 | 16 | Mac/Linux: keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android 17 | 18 | (For production, the json file is to be created from play store console) 19 | 20 | 4. Download and add google-services.json file in the app directory (more https://firebase.google.com/docs/android/setup) 21 | 22 | 23 | For reference, see https://codelabs.developers.google.com/codelabs/firebase-android/#0 24 | 25 | I have provided my google-services.json as a sample (DO NOT USE IT IN YOUR OWN PROJECT, IT WILL NOT WORK) 26 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion 29 7 | buildToolsVersion '29.0.0' 8 | 9 | defaultConfig { 10 | applicationId "com.app.sample.fchat" 11 | minSdkVersion 16 12 | targetSdkVersion 29 13 | versionCode 1 14 | versionName "1.0" 15 | vectorDrawables.useSupportLibrary = true 16 | multiDexEnabled true 17 | } 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | compileOptions { 25 | sourceCompatibility = 1.8 26 | targetCompatibility = 1.8 27 | } 28 | } 29 | 30 | /* IMPORTANT : 31 | * Be careful when update dependencies, different version library may caused error */ 32 | dependencies { 33 | implementation fileTree(dir: 'libs', include: ['*.jar']) 34 | testImplementation 'junit:junit:4.12' 35 | implementation 'androidx.appcompat:appcompat:1.1.0' 36 | implementation 'androidx.browser:browser:1.2.0' 37 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 38 | implementation 'androidx.cardview:cardview:1.0.0' 39 | implementation 'androidx.recyclerview:recyclerview:1.1.0' 40 | implementation 'com.google.android.material:material:1.1.0' 41 | implementation 'com.squareup.picasso:picasso:2.5.2' 42 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 43 | implementation 'com.balysv:material-ripple:1.0.2' 44 | implementation 'com.android.support:multidex:1.0.3' 45 | 46 | // Google 47 | implementation 'com.google.android.gms:play-services-auth:17.0.0' 48 | implementation 'com.google.android.gms:play-services-appinvite:18.0.0' 49 | implementation 'com.google.android.gms:play-services-measurement-base:17.2.2' 50 | 51 | // Firebase 52 | implementation 'com.google.firebase:firebase-database:19.2.1' 53 | implementation 'com.google.firebase:firebase-auth:19.2.0' 54 | implementation 'com.google.firebase:firebase-config:19.1.1' 55 | implementation 'com.google.firebase:firebase-messaging:20.1.0' 56 | implementation 'com.google.firebase:firebase-appindexing:19.1.0' 57 | implementation 'com.google.firebase:firebase-crash:16.2.1' 58 | implementation 'com.google.firebase:firebase-analytics:17.2.2' 59 | 60 | //UI 61 | implementation "androidx.constraintlayout:constraintlayout:2.0.0-beta4" 62 | 63 | //Kotlin 64 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 65 | } 66 | apply plugin: 'com.google.gms.google-services' 67 | repositories { 68 | mavenCentral() 69 | } 70 | -------------------------------------------------------------------------------- /app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "419301899506", 4 | "firebase_url": "https://fchat-68dac.firebaseio.com", 5 | "project_id": "fchat-68dac", 6 | "storage_bucket": "fchat-68dac.appspot.com" 7 | }, 8 | "client": [ 9 | { 10 | "client_info": { 11 | "mobilesdk_app_id": "1:419301899506:android:6d7d160511e27a92", 12 | "android_client_info": { 13 | "package_name": "com.app.sample.fchat" 14 | } 15 | }, 16 | "oauth_client": [ 17 | { 18 | "client_id": "419301899506-j40g754jlj25sou8gb24dd0hu4lri0fn.apps.googleusercontent.com", 19 | "client_type": 1, 20 | "android_info": { 21 | "package_name": "com.app.sample.fchat", 22 | "certificate_hash": "ee1d98581e339bc19421806c92f8afbf8cf23255" 23 | } 24 | }, 25 | { 26 | "client_id": "419301899506-6m8ispsckoi1pq9i6gu7pmk0i44mbjbe.apps.googleusercontent.com", 27 | "client_type": 3 28 | } 29 | ], 30 | "api_key": [ 31 | { 32 | "current_key": "AIzaSyDv1uvqv1efL3NmgUEd5KFfgpDlX4P8xk8" 33 | } 34 | ], 35 | "services": { 36 | "analytics_service": { 37 | "status": 1 38 | }, 39 | "appinvite_service": { 40 | "status": 2, 41 | "other_platform_oauth_client": [ 42 | { 43 | "client_id": "419301899506-6m8ispsckoi1pq9i6gu7pmk0i44mbjbe.apps.googleusercontent.com", 44 | "client_type": 3 45 | } 46 | ] 47 | }, 48 | "ads_service": { 49 | "status": 2 50 | } 51 | } 52 | } 53 | ], 54 | "configuration_version": "1" 55 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\COMPETITION\Android Studio\Android Studio SDK/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 28 | 29 | 30 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 50 | 51 | 52 | 56 | 57 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /app/src/main/java/com/app/sample/fchat/activity/ChatActivity.java: -------------------------------------------------------------------------------- 1 | package com.app.sample.fchat.activity; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Build; 6 | import android.os.Bundle; 7 | import android.text.Editable; 8 | import android.text.TextWatcher; 9 | import android.util.Log; 10 | import android.view.View; 11 | import android.view.View.OnClickListener; 12 | import android.view.inputmethod.InputMethodManager; 13 | import android.widget.Button; 14 | import android.widget.EditText; 15 | import android.widget.ListView; 16 | 17 | import com.app.sample.fchat.R; 18 | import com.app.sample.fchat.adapter.ChatDetailsListAdapter; 19 | import com.app.sample.fchat.data.ParseFirebaseData; 20 | import com.app.sample.fchat.data.SettingsAPI; 21 | import com.app.sample.fchat.model.ChatMessage; 22 | import com.app.sample.fchat.model.Friend; 23 | import com.app.sample.fchat.ui.CustomToast; 24 | import com.app.sample.fchat.util.Constants; 25 | import com.app.sample.fchat.util.Tools; 26 | import com.google.firebase.database.DataSnapshot; 27 | import com.google.firebase.database.DatabaseError; 28 | import com.google.firebase.database.DatabaseReference; 29 | import com.google.firebase.database.FirebaseDatabase; 30 | import com.google.firebase.database.MutableData; 31 | import com.google.firebase.database.Transaction; 32 | import com.google.firebase.database.ValueEventListener; 33 | 34 | import java.util.ArrayList; 35 | import java.util.HashMap; 36 | import java.util.List; 37 | 38 | import androidx.annotation.NonNull; 39 | import androidx.annotation.Nullable; 40 | import androidx.appcompat.app.ActionBar; 41 | import androidx.appcompat.app.AppCompatActivity; 42 | import androidx.appcompat.widget.Toolbar; 43 | import androidx.core.app.ActivityCompat; 44 | import androidx.core.app.ActivityOptionsCompat; 45 | import androidx.core.view.ViewCompat; 46 | 47 | public class ChatActivity extends AppCompatActivity { 48 | public static String KEY_FRIEND = "FRIEND"; 49 | 50 | // give preparation animation activity transition 51 | public static void navigate(AppCompatActivity activity, View transitionImage, Friend obj) { 52 | Intent intent = new Intent(activity, ChatActivity.class); 53 | intent.putExtra(KEY_FRIEND, obj); 54 | ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, transitionImage, KEY_FRIEND); 55 | ActivityCompat.startActivity(activity, intent, options.toBundle()); 56 | } 57 | 58 | private Button btn_send; 59 | private EditText et_content; 60 | public static ChatDetailsListAdapter mAdapter; 61 | 62 | private ListView listview; 63 | private ActionBar actionBar; 64 | private Friend friend; 65 | private List items = new ArrayList<>(); 66 | private View parent_view; 67 | ParseFirebaseData pfbd; 68 | SettingsAPI set; 69 | 70 | String chatNode, chatNode_1, chatNode_2; 71 | 72 | DatabaseReference ref; 73 | ValueEventListener valueEventListener; 74 | 75 | @Override 76 | protected void onCreate(Bundle savedInstanceState) { 77 | super.onCreate(savedInstanceState); 78 | setContentView(R.layout.activity_chat); 79 | parent_view = findViewById(android.R.id.content); 80 | pfbd = new ParseFirebaseData(this); 81 | set = new SettingsAPI(this); 82 | 83 | // animation transition 84 | ViewCompat.setTransitionName(parent_view, KEY_FRIEND); 85 | 86 | // initialize conversation data 87 | Intent intent = getIntent(); 88 | friend = (Friend) intent.getExtras().getSerializable(KEY_FRIEND); 89 | initToolbar(); 90 | 91 | iniComponen(); 92 | chatNode_1 = set.readSetting(Constants.PREF_MY_ID) + "-" + friend.getId(); 93 | chatNode_2 = friend.getId() + "-" + set.readSetting(Constants.PREF_MY_ID); 94 | 95 | valueEventListener=new ValueEventListener() { 96 | @Override 97 | public void onDataChange(DataSnapshot dataSnapshot) { 98 | Log.d(Constants.LOG_TAG,"Data changed from activity"); 99 | if (dataSnapshot.hasChild(chatNode_1)) { 100 | chatNode = chatNode_1; 101 | } else if (dataSnapshot.hasChild(chatNode_2)) { 102 | chatNode = chatNode_2; 103 | } else { 104 | chatNode = chatNode_1; 105 | } 106 | items.clear(); 107 | items.addAll(pfbd.getMessagesForSingleUser(dataSnapshot.child(chatNode))); 108 | 109 | //Here we are traversing all the messages and mark all received messages read 110 | 111 | for (DataSnapshot data : dataSnapshot.child(chatNode).getChildren()) { 112 | if (data.child(Constants.NODE_RECEIVER_ID).getValue().toString().equals(set.readSetting(Constants.PREF_MY_ID))) { 113 | data.child(Constants.NODE_IS_READ).getRef().runTransaction(new Transaction.Handler() { 114 | @NonNull 115 | @Override 116 | public Transaction.Result doTransaction(@NonNull MutableData mutableData) { 117 | mutableData.setValue(true); 118 | return Transaction.success(mutableData); 119 | } 120 | 121 | @Override 122 | public void onComplete(@Nullable DatabaseError databaseError, boolean b, @Nullable DataSnapshot dataSnapshot) { 123 | 124 | } 125 | }); 126 | } 127 | } 128 | 129 | // TODO: 12/09/18 Change it to recyclerview 130 | mAdapter = new ChatDetailsListAdapter(ChatActivity.this, items); 131 | listview.setAdapter(mAdapter); 132 | listview.requestFocus(); 133 | registerForContextMenu(listview); 134 | } 135 | 136 | @Override 137 | public void onCancelled(DatabaseError databaseError) { 138 | new CustomToast(ChatActivity.this).showError(getString(R.string.error_could_not_connect)); 139 | } 140 | }; 141 | 142 | ref = FirebaseDatabase.getInstance().getReference(Constants.MESSAGE_CHILD); 143 | ref.addValueEventListener(valueEventListener); 144 | 145 | // for system bar in lollipop 146 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 147 | Tools.systemBarLolipop(this); 148 | } 149 | } 150 | 151 | public void initToolbar() { 152 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 153 | setSupportActionBar(toolbar); 154 | actionBar = getSupportActionBar(); 155 | actionBar.setDisplayHomeAsUpEnabled(true); 156 | actionBar.setHomeButtonEnabled(true); 157 | actionBar.setTitle(friend.getName()); 158 | } 159 | 160 | public void iniComponen() { 161 | listview = (ListView) findViewById(R.id.listview); 162 | btn_send = (Button) findViewById(R.id.btn_send); 163 | et_content = (EditText) findViewById(R.id.text_content); 164 | btn_send.setOnClickListener(new OnClickListener() { 165 | @Override 166 | public void onClick(View view) { 167 | // ChatMessage im=new ChatMessage(et_content.getText().toString(), String.valueOf(System.currentTimeMillis()),friend.getId(),friend.getName(),friend.getPhoto()); 168 | 169 | HashMap hm = new HashMap(); 170 | hm.put(Constants.NODE_TEXT, et_content.getText().toString()); 171 | hm.put(Constants.NODE_TIMESTAMP, String.valueOf(System.currentTimeMillis())); 172 | hm.put(Constants.NODE_RECEIVER_ID, friend.getId()); 173 | hm.put(Constants.NODE_RECEIVER_NAME, friend.getName()); 174 | hm.put(Constants.NODE_RECEIVER_PHOTO, friend.getPhoto()); 175 | hm.put(Constants.NODE_SENDER_ID, set.readSetting(Constants.PREF_MY_ID)); 176 | hm.put(Constants.NODE_SENDER_NAME, set.readSetting(Constants.PREF_MY_NAME)); 177 | hm.put(Constants.NODE_SENDER_PHOTO, set.readSetting(Constants.PREF_MY_DP)); 178 | hm.put(Constants.NODE_IS_READ, false); 179 | 180 | ref.child(chatNode).push().setValue(hm); 181 | et_content.setText(""); 182 | hideKeyboard(); 183 | } 184 | }); 185 | et_content.addTextChangedListener(contentWatcher); 186 | if (et_content.length() == 0) { 187 | btn_send.setVisibility(View.GONE); 188 | } 189 | hideKeyboard(); 190 | } 191 | 192 | 193 | private void hideKeyboard() { 194 | View view = this.getCurrentFocus(); 195 | if (view != null) { 196 | InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); 197 | imm.hideSoftInputFromWindow(view.getWindowToken(), 0); 198 | } 199 | } 200 | 201 | private TextWatcher contentWatcher = new TextWatcher() { 202 | @Override 203 | public void afterTextChanged(Editable etd) { 204 | if (etd.toString().trim().length() == 0) { 205 | btn_send.setVisibility(View.GONE); 206 | } else { 207 | btn_send.setVisibility(View.VISIBLE); 208 | } 209 | //draft.setContent(etd.toString()); 210 | } 211 | 212 | @Override 213 | public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) { 214 | } 215 | 216 | @Override 217 | public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) { 218 | } 219 | }; 220 | 221 | @Override 222 | public void onBackPressed() { 223 | super.onBackPressed(); 224 | } 225 | 226 | @Override 227 | protected void onDestroy() { 228 | //Remove the listener, otherwise it will continue listening in the background 229 | //We have service to run in the background 230 | ref.removeEventListener(valueEventListener); 231 | super.onDestroy(); 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /app/src/main/java/com/app/sample/fchat/activity/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.app.sample.fchat.activity; 2 | 3 | import android.app.job.JobInfo; 4 | import android.app.job.JobScheduler; 5 | import android.content.ComponentName; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.os.Build; 9 | import android.os.Bundle; 10 | import android.view.Menu; 11 | import android.view.MenuInflater; 12 | import android.view.MenuItem; 13 | 14 | import com.app.sample.fchat.R; 15 | import com.app.sample.fchat.fragment.ConversationListFragment; 16 | import com.app.sample.fchat.service.NotificationService; 17 | import com.app.sample.fchat.ui.CustomToast; 18 | import com.app.sample.fchat.util.Constants; 19 | import com.app.sample.fchat.util.Tools; 20 | import com.google.android.material.floatingactionbutton.FloatingActionButton; 21 | 22 | import androidx.appcompat.app.ActionBar; 23 | import androidx.appcompat.app.AppCompatActivity; 24 | import androidx.appcompat.widget.Toolbar; 25 | import androidx.fragment.app.FragmentManager; 26 | import androidx.fragment.app.FragmentTransaction; 27 | 28 | public class MainActivity extends AppCompatActivity { 29 | private Toolbar toolbar; 30 | public FloatingActionButton fab; 31 | JobScheduler mJobScheduler; 32 | 33 | @Override 34 | protected void onCreate(Bundle savedInstanceState) { 35 | super.onCreate(savedInstanceState); 36 | setContentView(R.layout.activity_main); 37 | 38 | toolbar = (Toolbar) findViewById(R.id.toolbar); 39 | fab = (FloatingActionButton) findViewById(R.id.add); 40 | 41 | prepareActionBar(toolbar); 42 | initComponent(); 43 | 44 | fab.setOnClickListener(view -> { 45 | Intent i = new Intent(MainActivity.this, SelectFriendActivity.class); 46 | startActivity(i); 47 | }); 48 | 49 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 50 | // for system bar in lollipop 51 | Tools.systemBarLolipop(this); 52 | //Create the scheduler 53 | mJobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); 54 | JobInfo.Builder builder = new JobInfo.Builder(1, new ComponentName(getPackageName(), NotificationService.class.getName())); 55 | builder.setPeriodic(900000); 56 | //If it needs to continue even after boot, persisted needs to be true 57 | //builder.setPersisted(true); 58 | builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); 59 | mJobScheduler.schedule(builder.build()); 60 | } 61 | } 62 | 63 | private void initComponent() { 64 | FragmentManager fragmentManager = getSupportFragmentManager(); 65 | FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); 66 | ConversationListFragment ctf = new ConversationListFragment(); 67 | //icf.setRetainInstance(true); 68 | fragmentTransaction.add(R.id.main_container, ctf, Constants.TAG_CHAT_HISTORY); 69 | fragmentTransaction.commit(); 70 | 71 | } 72 | 73 | private void prepareActionBar(Toolbar toolbar) { 74 | setSupportActionBar(toolbar); 75 | ActionBar actionBar = getSupportActionBar(); 76 | actionBar.setDisplayHomeAsUpEnabled(false); 77 | actionBar.setHomeButtonEnabled(false); 78 | } 79 | 80 | @Override 81 | public boolean onCreateOptionsMenu(Menu menu) { 82 | MenuInflater inflater = getMenuInflater(); 83 | inflater.inflate(R.menu.menu_main, menu); 84 | return super.onCreateOptionsMenu(menu); 85 | } 86 | 87 | 88 | @Override 89 | public boolean onOptionsItemSelected(MenuItem item) { 90 | int id = item.getItemId(); 91 | switch (id) { 92 | case R.id.action_logout: { 93 | Intent logoutIntent = new Intent(this, SplashActivity.class); 94 | logoutIntent.putExtra("mode", "logout"); 95 | startActivity(logoutIntent); 96 | finish(); 97 | return true; 98 | } 99 | default: 100 | return super.onOptionsItemSelected(item); 101 | } 102 | } 103 | 104 | private long exitTime = 0; 105 | 106 | public void doExitApp() { 107 | if ((System.currentTimeMillis() - exitTime) > 2000) { 108 | new CustomToast(this).showInfo(getString(R.string.press_again_exit_app)); 109 | exitTime = System.currentTimeMillis(); 110 | } else { 111 | finish(); 112 | } 113 | } 114 | 115 | @Override 116 | public void onBackPressed() { 117 | doExitApp(); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /app/src/main/java/com/app/sample/fchat/activity/SelectFriendActivity.java: -------------------------------------------------------------------------------- 1 | package com.app.sample.fchat.activity; 2 | 3 | import android.os.Build; 4 | import android.os.Bundle; 5 | 6 | import com.app.sample.fchat.R; 7 | import com.app.sample.fchat.adapter.FriendsListAdapter; 8 | import com.app.sample.fchat.data.ParseFirebaseData; 9 | import com.app.sample.fchat.model.Friend; 10 | import com.app.sample.fchat.ui.CustomToast; 11 | import com.app.sample.fchat.ui.ViewHelper; 12 | import com.app.sample.fchat.util.Tools; 13 | import com.app.sample.fchat.widget.DividerItemDecoration; 14 | import com.google.firebase.database.DataSnapshot; 15 | import com.google.firebase.database.DatabaseError; 16 | import com.google.firebase.database.DatabaseReference; 17 | import com.google.firebase.database.FirebaseDatabase; 18 | import com.google.firebase.database.ValueEventListener; 19 | 20 | import org.jetbrains.annotations.NotNull; 21 | 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | 25 | import androidx.appcompat.app.ActionBar; 26 | import androidx.appcompat.app.AppCompatActivity; 27 | import androidx.appcompat.widget.Toolbar; 28 | import androidx.recyclerview.widget.LinearLayoutManager; 29 | import androidx.recyclerview.widget.RecyclerView; 30 | 31 | public class SelectFriendActivity extends AppCompatActivity { 32 | 33 | private ActionBar actionBar; 34 | private RecyclerView recyclerView; 35 | private FriendsListAdapter mAdapter; 36 | List friendList; 37 | 38 | public static final String USERS_CHILD = "users"; 39 | ParseFirebaseData pfbd; 40 | ViewHelper viewHelper; 41 | 42 | @Override 43 | protected void onCreate(Bundle savedInstanceState) { 44 | super.onCreate(savedInstanceState); 45 | setContentView(R.layout.activity_new_chat); 46 | initToolbar(); 47 | initComponent(); 48 | friendList = new ArrayList<>(); 49 | pfbd = new ParseFirebaseData(this); 50 | viewHelper=new ViewHelper(this); 51 | 52 | viewHelper.showProgressDialog(); 53 | 54 | DatabaseReference ref = FirebaseDatabase.getInstance().getReference(USERS_CHILD); 55 | ref.addValueEventListener(new ValueEventListener() { 56 | @Override 57 | public void onDataChange(@NotNull DataSnapshot dataSnapshot) { 58 | // TODO: 25-05-2017 if number of items is 0 then show something else 59 | mAdapter = new FriendsListAdapter(SelectFriendActivity.this, 60 | pfbd.getAllUser(dataSnapshot)); 61 | recyclerView.setAdapter(mAdapter); 62 | 63 | mAdapter.setOnItemClickListener((view, obj, position) -> ChatActivity 64 | .navigate((SelectFriendActivity) SelectFriendActivity.this, 65 | findViewById(R.id.lyt_parent), obj)); 66 | 67 | bindView(); 68 | } 69 | 70 | @Override 71 | public void onCancelled(DatabaseError databaseError) { 72 | new CustomToast(SelectFriendActivity.this) 73 | .showError(getString(R.string.error_could_not_connect)); 74 | } 75 | }); 76 | 77 | // for system bar in lollipop 78 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 79 | Tools.systemBarLolipop(this); 80 | } 81 | } 82 | 83 | private void initComponent() { 84 | recyclerView = (RecyclerView) findViewById(R.id.recyclerView); 85 | 86 | // use a linear layout manager 87 | LinearLayoutManager mLayoutManager = new LinearLayoutManager(this); 88 | recyclerView.setLayoutManager(mLayoutManager); 89 | recyclerView.setHasFixedSize(true); 90 | recyclerView.addItemDecoration( 91 | new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST)); 92 | } 93 | 94 | public void initToolbar() { 95 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 96 | setSupportActionBar(toolbar); 97 | actionBar = getSupportActionBar(); 98 | actionBar.setDisplayHomeAsUpEnabled(true); 99 | actionBar.setHomeButtonEnabled(true); 100 | // actionBar.setSubtitle(Constant.getFriendsData(this).size()+" friends"); 101 | } 102 | 103 | public void bindView() { 104 | try { 105 | mAdapter.notifyDataSetChanged(); 106 | viewHelper.dismissProgressDialog(); 107 | } catch (Exception e) { 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /app/src/main/java/com/app/sample/fchat/activity/SplashActivity.java: -------------------------------------------------------------------------------- 1 | package com.app.sample.fchat.activity; 2 | 3 | import android.content.Intent; 4 | import android.os.Build; 5 | import android.os.Bundle; 6 | import android.os.Handler; 7 | import android.view.View; 8 | import android.view.animation.AlphaAnimation; 9 | import android.view.animation.Animation; 10 | import android.widget.ImageView; 11 | import android.widget.ProgressBar; 12 | 13 | import com.app.sample.fchat.R; 14 | import com.app.sample.fchat.data.SettingsAPI; 15 | import com.app.sample.fchat.ui.CustomToast; 16 | import com.app.sample.fchat.ui.ViewHelper; 17 | import com.app.sample.fchat.util.Constants; 18 | import com.app.sample.fchat.util.Tools; 19 | import com.google.android.gms.auth.api.Auth; 20 | import com.google.android.gms.auth.api.signin.GoogleSignInAccount; 21 | import com.google.android.gms.auth.api.signin.GoogleSignInOptions; 22 | import com.google.android.gms.auth.api.signin.GoogleSignInResult; 23 | import com.google.android.gms.common.ConnectionResult; 24 | import com.google.android.gms.common.SignInButton; 25 | import com.google.android.gms.common.api.GoogleApiClient; 26 | import com.google.firebase.auth.AuthCredential; 27 | import com.google.firebase.auth.FirebaseAuth; 28 | import com.google.firebase.auth.GoogleAuthProvider; 29 | import com.google.firebase.database.DataSnapshot; 30 | import com.google.firebase.database.DatabaseError; 31 | import com.google.firebase.database.DatabaseReference; 32 | import com.google.firebase.database.FirebaseDatabase; 33 | import com.google.firebase.database.ValueEventListener; 34 | 35 | import org.jetbrains.annotations.NotNull; 36 | 37 | import androidx.annotation.NonNull; 38 | import androidx.annotation.Nullable; 39 | import androidx.appcompat.app.AppCompatActivity; 40 | 41 | import static com.app.sample.fchat.util.Constants.NODE_NAME; 42 | import static com.app.sample.fchat.util.Constants.NODE_PHOTO; 43 | import static com.app.sample.fchat.util.Constants.NODE_USER_ID; 44 | 45 | public class SplashActivity extends AppCompatActivity 46 | implements GoogleApiClient.OnConnectionFailedListener { 47 | 48 | private static final int RC_SIGN_IN = 100; 49 | private SignInButton signInButton; 50 | private ProgressBar loginProgress; 51 | 52 | private GoogleApiClient mGoogleApiClient; 53 | private FirebaseAuth mFirebaseAuth; 54 | DatabaseReference ref; 55 | SettingsAPI set; 56 | 57 | CustomToast customToast; 58 | ViewHelper viewHelper; 59 | 60 | public static final String USERS_CHILD = "users"; 61 | 62 | @Override 63 | protected void onCreate(Bundle savedInstanceState) { 64 | super.onCreate(savedInstanceState); 65 | setContentView(R.layout.activity_splash); 66 | bindLogo(); 67 | 68 | customToast = new CustomToast(this); 69 | viewHelper = new ViewHelper(getApplicationContext()); 70 | viewHelper.clearNotofication(); 71 | 72 | // Assign fields 73 | signInButton = (SignInButton) findViewById(R.id.sign_in_button); 74 | loginProgress = (ProgressBar) findViewById(R.id.login_progress); 75 | 76 | // Set click listeners 77 | signInButton.setOnClickListener(v -> signIn()); 78 | 79 | GoogleSignInOptions gso = 80 | new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) 81 | .requestIdToken(getString(R.string.default_web_client_id)).requestEmail().build(); 82 | mGoogleApiClient = new GoogleApiClient.Builder(this) 83 | .enableAutoManage(this /* FragmentActivity */, this /* OnConnectionFailedListener */) 84 | .addApi(Auth.GOOGLE_SIGN_IN_API, gso).build(); 85 | 86 | // Initialize FirebaseAuth 87 | mFirebaseAuth = FirebaseAuth.getInstance(); 88 | set = new SettingsAPI(this); 89 | 90 | if (getIntent().getStringExtra("mode") != null) { 91 | if (getIntent().getStringExtra("mode").equals("logout")) { 92 | mGoogleApiClient.connect(); 93 | mGoogleApiClient 94 | .registerConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() { 95 | @Override 96 | public void onConnected( 97 | @Nullable 98 | Bundle bundle) { 99 | mFirebaseAuth.signOut(); 100 | Auth.GoogleSignInApi.signOut(mGoogleApiClient); 101 | set.deleteAllSettings(); 102 | } 103 | 104 | @Override 105 | public void onConnectionSuspended(int i) { 106 | 107 | } 108 | }); 109 | } 110 | } 111 | if (!mGoogleApiClient.isConnecting()) { 112 | if (!set.readSetting(Constants.PREF_MY_ID).equals("na")) { 113 | signInButton.setVisibility(View.GONE); 114 | final Handler handler = new Handler(); 115 | handler.postDelayed(() -> { 116 | startActivity(new Intent(SplashActivity.this, MainActivity.class)); 117 | finish(); 118 | }, 3000); 119 | } 120 | } 121 | // for system bar in lollipop 122 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 123 | Tools.systemBarLolipop(this); 124 | } 125 | } 126 | 127 | private void bindLogo() { 128 | // Start animating the image 129 | final ImageView splash = (ImageView) findViewById(R.id.splash); 130 | final AlphaAnimation animation1 = new AlphaAnimation(0.2f, 1.0f); 131 | animation1.setDuration(700); 132 | final AlphaAnimation animation2 = new AlphaAnimation(1.0f, 0.2f); 133 | animation2.setDuration(700); 134 | //animation1 AnimationListener 135 | animation1.setAnimationListener(new Animation.AnimationListener() { 136 | @Override 137 | public void onAnimationEnd(Animation arg0) { 138 | // start animation2 when animation1 ends (continue) 139 | splash.startAnimation(animation2); 140 | } 141 | 142 | @Override 143 | public void onAnimationRepeat(Animation arg0) { 144 | } 145 | 146 | @Override 147 | public void onAnimationStart(Animation arg0) { 148 | } 149 | }); 150 | 151 | //animation2 AnimationListener 152 | animation2.setAnimationListener(new Animation.AnimationListener() { 153 | @Override 154 | public void onAnimationEnd(Animation arg0) { 155 | // start animation1 when animation2 ends (repeat) 156 | splash.startAnimation(animation1); 157 | } 158 | 159 | @Override 160 | public void onAnimationRepeat(Animation arg0) { 161 | } 162 | 163 | @Override 164 | public void onAnimationStart(Animation arg0) { 165 | } 166 | }); 167 | 168 | splash.startAnimation(animation1); 169 | } 170 | 171 | private void signIn() { 172 | Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(mGoogleApiClient); 173 | startActivityForResult(signInIntent, RC_SIGN_IN); 174 | } 175 | 176 | @Override 177 | public void onActivityResult(int requestCode, int resultCode, Intent data) { 178 | super.onActivityResult(requestCode, resultCode, data); 179 | // Result returned from launching the Intent from GoogleSignInApi.getSignInIntent(...); 180 | if (requestCode == RC_SIGN_IN) { 181 | GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data); 182 | if (result.isSuccess()) { 183 | signInButton.setVisibility(View.GONE); 184 | loginProgress.setVisibility(View.VISIBLE); 185 | // Google Sign In was successful, authenticate with Firebase 186 | GoogleSignInAccount account = result.getSignInAccount(); 187 | firebaseAuthWithGoogle(account); 188 | } else { 189 | customToast.showError(getString(R.string.error_login_failed)); 190 | } 191 | } 192 | } 193 | 194 | private void firebaseAuthWithGoogle(final GoogleSignInAccount acct) { 195 | AuthCredential credential = GoogleAuthProvider.getCredential(acct.getIdToken(), null); 196 | mFirebaseAuth.signInWithCredential(credential).addOnCompleteListener(this, task -> { 197 | // If sign in fails, display a message to the user. If sign in succeeds 198 | // the auth state listener will be notified and logic to handle the 199 | // signed in user can be handled in the listener. 200 | if (!task.isSuccessful()) { 201 | customToast.showError(getString(R.string.error_authetication_failed)); 202 | } else { 203 | ref = FirebaseDatabase.getInstance().getReference(USERS_CHILD); 204 | ref.addListenerForSingleValueEvent(new ValueEventListener() { 205 | @Override 206 | public void onDataChange( 207 | @NotNull 208 | DataSnapshot snapshot) { 209 | final String usrNm = acct.getDisplayName(); 210 | final String usrId = acct.getId(); 211 | final String usrDp = acct.getPhotoUrl().toString(); 212 | 213 | set.addUpdateSettings(Constants.PREF_MY_ID, usrId); 214 | set.addUpdateSettings(Constants.PREF_MY_NAME, usrNm); 215 | set.addUpdateSettings(Constants.PREF_MY_DP, usrDp); 216 | 217 | if (!snapshot.hasChild(usrId)) { 218 | ref.child(usrId + "/" + NODE_NAME).setValue(usrNm); 219 | ref.child(usrId + "/" + NODE_PHOTO).setValue(usrDp); 220 | ref.child(usrId + "/" + NODE_USER_ID).setValue(usrId); 221 | } 222 | } 223 | 224 | @Override 225 | public void onCancelled(DatabaseError databaseError) { 226 | } 227 | }); 228 | 229 | startActivity(new Intent(SplashActivity.this, MainActivity.class)); 230 | finish(); 231 | } 232 | }); 233 | } 234 | 235 | @Override 236 | public void onConnectionFailed( 237 | @NonNull 238 | ConnectionResult connectionResult) { 239 | // An unresolvable error has occurred and Google APIs (including Sign-In) will not 240 | // be available. 241 | customToast.showError("Google Play Services error."); 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /app/src/main/java/com/app/sample/fchat/adapter/ChatDetailsListAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.app.sample.fchat.adapter 2 | 3 | import android.content.Context 4 | import android.graphics.Color 5 | import android.view.Gravity 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import android.widget.BaseAdapter 10 | import android.widget.ImageView 11 | import android.widget.LinearLayout 12 | import android.widget.TextView 13 | import androidx.cardview.widget.CardView 14 | import androidx.core.content.ContextCompat 15 | import com.app.sample.fchat.R 16 | import com.app.sample.fchat.data.SettingsAPI 17 | import com.app.sample.fchat.model.ChatMessage 18 | 19 | class ChatDetailsListAdapter(private val mContext: Context, private val mMessages: MutableList) : BaseAdapter() { 20 | internal var set: SettingsAPI 21 | 22 | init { 23 | set = SettingsAPI(mContext) 24 | } 25 | 26 | override fun getCount(): Int { 27 | return mMessages.size 28 | } 29 | 30 | override fun getItem(position: Int): Any { 31 | return mMessages[position] 32 | } 33 | 34 | override fun getItemId(position: Int): Long { 35 | return 0 36 | } 37 | 38 | override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { 39 | var convertView = convertView 40 | val msg = getItem(position) as ChatMessage 41 | val holder: ViewHolder 42 | if (convertView == null) { 43 | holder = ViewHolder() 44 | convertView = LayoutInflater.from(mContext).inflate(R.layout.row_chat_details, parent, false) 45 | holder.time = convertView!!.findViewById(R.id.text_time) as TextView 46 | holder.message = convertView.findViewById(R.id.text_content) as TextView 47 | holder.lyt_thread = convertView.findViewById(R.id.lyt_thread) as CardView 48 | holder.lyt_parent = convertView.findViewById(R.id.lyt_parent) as LinearLayout 49 | holder.image_status = convertView.findViewById(R.id.image_status) as ImageView 50 | convertView.tag = holder 51 | } else { 52 | holder = convertView.tag as ViewHolder 53 | } 54 | 55 | holder.message!!.text = msg.text 56 | holder.time!!.text = msg.readableTime 57 | 58 | if (msg.receiver.id == set.readSetting("myid")) { 59 | holder.lyt_parent!!.setPadding(15, 10, 100, 10) 60 | holder.lyt_parent!!.gravity = Gravity.LEFT 61 | holder.lyt_thread!!.setCardBackgroundColor(Color.parseColor("#FFFFFF")) 62 | holder.image_status!!.setImageResource(android.R.color.transparent) 63 | } else { 64 | holder.lyt_parent!!.setPadding(100, 10, 15, 10) 65 | holder.lyt_parent!!.gravity = Gravity.RIGHT 66 | holder.lyt_thread!!.setCardBackgroundColor(mContext.resources.getColor(R.color.me_chat_bg)) 67 | holder.image_status!!.setImageResource(R.drawable.baseline_done_24) 68 | holder.image_status!!.setColorFilter(ContextCompat.getColor(mContext, android.R.color.darker_gray), android.graphics.PorterDuff.Mode.MULTIPLY) 69 | 70 | if (msg.isRead!!) { 71 | holder.image_status!!.setImageResource(R.drawable.baseline_done_all_24) 72 | holder.image_status!!.setColorFilter(ContextCompat.getColor(mContext, android.R.color.holo_blue_dark), android.graphics.PorterDuff.Mode.MULTIPLY) 73 | } 74 | } 75 | return convertView 76 | } 77 | 78 | /** 79 | * remove data item from messageAdapter 80 | */ 81 | fun remove(position: Int) { 82 | mMessages.removeAt(position) 83 | } 84 | 85 | /** 86 | * add data item to messageAdapter 87 | */ 88 | fun add(msg: ChatMessage) { 89 | mMessages.add(msg) 90 | } 91 | 92 | private class ViewHolder { 93 | internal var time: TextView? = null 94 | internal var message: TextView? = null 95 | internal var lyt_parent: LinearLayout? = null 96 | internal var lyt_thread: CardView? = null 97 | internal var image_status: ImageView? = null 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /app/src/main/java/com/app/sample/fchat/adapter/ChatsListAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.app.sample.fchat.adapter 2 | 3 | import android.content.Context 4 | import android.util.SparseBooleanArray 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import android.view.animation.AnimationUtils 9 | import android.widget.* 10 | import androidx.recyclerview.widget.RecyclerView 11 | import com.app.sample.fchat.R 12 | import com.app.sample.fchat.data.SettingsAPI 13 | import com.app.sample.fchat.model.ChatMessage 14 | import com.app.sample.fchat.widget.CircleTransform 15 | import com.squareup.picasso.Picasso 16 | import java.util.* 17 | 18 | class ChatsListAdapter// Provide a suitable constructor (depends on the kind of dataset) 19 | (private val mContext: Context, items: ArrayList) : RecyclerView.Adapter(), Filterable { 20 | 21 | private val selectedItems: SparseBooleanArray 22 | 23 | private var original_items = ArrayList() 24 | private var filtered_items: ArrayList = ArrayList() 25 | private val mFilter = ItemFilter() 26 | private lateinit var set: SettingsAPI 27 | 28 | // for item click listener 29 | private var mOnItemClickListener: OnItemClickListener? = null 30 | 31 | // for item long click listener 32 | private val mOnItemLongClickListener: OnItemLongClickListener? = null 33 | 34 | /** 35 | * Here is the key method to apply the animation 36 | */ 37 | private var lastPosition = -1 38 | 39 | val selectedItemCount: Int 40 | get() = selectedItems.size() 41 | 42 | interface OnItemClickListener { 43 | fun onItemClick(view: View, obj: ChatMessage, position: Int) 44 | } 45 | 46 | fun setOnItemClickListener(mItemClickListener: OnItemClickListener) { 47 | this.mOnItemClickListener = mItemClickListener 48 | } 49 | 50 | interface OnItemLongClickListener { 51 | fun onItemClick(view: View, obj: ChatMessage, position: Int) 52 | } 53 | 54 | inner class ViewHolder(v: View) : RecyclerView.ViewHolder(v) { 55 | // each data item is just a string in this case 56 | var title: TextView 57 | var content: TextView 58 | var image: ImageView 59 | var lyt_parent: LinearLayout 60 | var unreadDot: LinearLayout 61 | 62 | init { 63 | title = v.findViewById(R.id.title) as TextView 64 | content = v.findViewById(R.id.content) as TextView 65 | image = v.findViewById(R.id.image) as ImageView 66 | lyt_parent = v.findViewById(R.id.lyt_parent) as LinearLayout 67 | unreadDot = v.findViewById(R.id.unread) as LinearLayout 68 | } 69 | 70 | } 71 | 72 | override fun getFilter(): Filter { 73 | return mFilter 74 | } 75 | 76 | init { 77 | original_items = items 78 | filtered_items = items 79 | selectedItems = SparseBooleanArray() 80 | } 81 | 82 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChatsListAdapter.ViewHolder { 83 | // create a new view 84 | val v = LayoutInflater.from(parent.context).inflate(R.layout.row_chats, parent, false) 85 | // set the view's size, margins, paddings and layout parameters 86 | return ViewHolder(v) 87 | } 88 | 89 | // Replace the contents of a view (invoked by the layout manager) 90 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 91 | set = SettingsAPI(mContext) 92 | val c = filtered_items[position] 93 | if (filtered_items[position].receiver.id == set.readSetting("myid") && (filtered_items[position].isRead == false)) 94 | holder.unreadDot.visibility = View.VISIBLE 95 | else 96 | holder.unreadDot.visibility = View.INVISIBLE 97 | holder.content.text = c.text 98 | if (c.sender.id == set.readSetting("myid")) { 99 | holder.title.text = c.receiver.name 100 | Picasso.with(mContext).load(c.receiver.photo).resize(100, 100).transform(CircleTransform()).into(holder.image) 101 | } else if (c.receiver.id == set.readSetting("myid")) { 102 | holder.title.text = c.sender.name 103 | Picasso.with(mContext).load(c.sender.photo).resize(100, 100).transform(CircleTransform()).into(holder.image) 104 | } 105 | 106 | // Here you apply the animation when the view is bound 107 | setAnimation(holder.itemView, position) 108 | holder.lyt_parent.setOnClickListener { view -> 109 | if (mOnItemClickListener != null) { 110 | mOnItemClickListener!!.onItemClick(view, c, position) 111 | } 112 | } 113 | 114 | holder.lyt_parent.setOnLongClickListener { view -> 115 | mOnItemLongClickListener?.onItemClick(view, c, position) 116 | false 117 | } 118 | 119 | holder.lyt_parent.isActivated = selectedItems.get(position, false) 120 | 121 | } 122 | 123 | private fun setAnimation(viewToAnimate: View, position: Int) { 124 | // If the bound view wasn't previously displayed on screen, it's animated 125 | if (position > lastPosition) { 126 | val animation = AnimationUtils.loadAnimation(mContext, R.anim.slide_in_bottom) 127 | viewToAnimate.startAnimation(animation) 128 | lastPosition = position 129 | } 130 | } 131 | 132 | /** 133 | * For multiple selection 134 | */ 135 | fun toggleSelection(pos: Int) { 136 | if (selectedItems.get(pos, false)) { 137 | selectedItems.delete(pos) 138 | } else { 139 | selectedItems.put(pos, true) 140 | } 141 | notifyItemChanged(pos) 142 | } 143 | 144 | fun clearSelections() { 145 | selectedItems.clear() 146 | notifyDataSetChanged() 147 | } 148 | 149 | fun removeSelectedItem() { 150 | val items = getSelectedItems() 151 | filtered_items.removeAll(items) 152 | } 153 | 154 | fun getSelectedItems(): List { 155 | val items = ArrayList() 156 | for (i in 0 until selectedItems.size()) { 157 | items.add(filtered_items[selectedItems.keyAt(i)]) 158 | } 159 | return items 160 | } 161 | 162 | // Return the size of your dataset (invoked by the layout manager) 163 | override fun getItemCount(): Int { 164 | return filtered_items.size 165 | } 166 | 167 | fun remove(position: Int) { 168 | filtered_items.removeAt(position) 169 | } 170 | 171 | override fun getItemId(position: Int): Long { 172 | return 0 173 | } 174 | 175 | //Original list contains all the last messages from all chats 176 | //We need only those messages where this particular user is involved 177 | private inner class ItemFilter : Filter() { 178 | override fun performFiltering(constraint: CharSequence): Filter.FilterResults { 179 | val query = constraint.toString().toLowerCase() 180 | 181 | val results = Filter.FilterResults() 182 | val list = original_items 183 | val result_list = ArrayList(list.size) 184 | 185 | for (i in list.indices) { 186 | val str_title = list[i].receiver.name 187 | if (str_title.toLowerCase().contains(query)) { 188 | result_list.add(list[i]) 189 | } 190 | } 191 | 192 | results.values = result_list 193 | results.count = result_list.size 194 | 195 | return results 196 | } 197 | 198 | override fun publishResults(constraint: CharSequence, results: Filter.FilterResults) { 199 | filtered_items = results.values as ArrayList 200 | notifyDataSetChanged() 201 | } 202 | 203 | } 204 | } -------------------------------------------------------------------------------- /app/src/main/java/com/app/sample/fchat/adapter/FriendsListAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.app.sample.fchat.adapter 2 | 3 | import android.content.Context 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.view.animation.AnimationUtils 8 | import android.widget.* 9 | import androidx.recyclerview.widget.RecyclerView 10 | import com.app.sample.fchat.R 11 | import com.app.sample.fchat.model.Friend 12 | import com.app.sample.fchat.widget.CircleTransform 13 | import com.squareup.picasso.Picasso 14 | import java.util.* 15 | 16 | class FriendsListAdapter// Provide a suitable constructor (depends on the kind of dataset) 17 | (private val mContext: Context, items: ArrayList) : RecyclerView.Adapter(), Filterable { 18 | 19 | private var original_items = ArrayList() 20 | private var filtered_items: List = ArrayList() 21 | private val mFilter = ItemFilter() 22 | 23 | private var mOnItemClickListener: OnItemClickListener? = null 24 | 25 | /** 26 | * Here is the key method to apply the animation 27 | */ 28 | private var lastPosition = -1 29 | 30 | interface OnItemClickListener { 31 | fun onItemClick(view: View, obj: Friend, position: Int) 32 | } 33 | 34 | fun setOnItemClickListener(mItemClickListener: OnItemClickListener) { 35 | this.mOnItemClickListener = mItemClickListener 36 | } 37 | 38 | init { 39 | original_items = items 40 | filtered_items = items 41 | } 42 | 43 | inner class ViewHolder(v: View) : RecyclerView.ViewHolder(v) { 44 | // each data item is just a string in this case 45 | var name: TextView 46 | var image: ImageView 47 | var lyt_parent: LinearLayout 48 | 49 | init { 50 | name = v.findViewById(R.id.name) as TextView 51 | image = v.findViewById(R.id.image) as ImageView 52 | lyt_parent = v.findViewById(R.id.lyt_parent) as LinearLayout 53 | } 54 | } 55 | 56 | override fun getFilter(): Filter { 57 | return mFilter 58 | } 59 | 60 | 61 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FriendsListAdapter.ViewHolder { 62 | // create a new view 63 | val v = LayoutInflater.from(parent.context).inflate(R.layout.row_friends, parent, false) 64 | // set the view's size, margins, paddings and layout parameters 65 | return ViewHolder(v) 66 | } 67 | 68 | // Replace the contents of a view (invoked by the layout manager) 69 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 70 | val c = filtered_items[position] 71 | holder.name.text = c.name 72 | Picasso 73 | .with(mContext) 74 | .load(c.photo) 75 | .error(R.drawable.unknown_avatar) 76 | .placeholder(R.drawable.unknown_avatar) 77 | .resize(100, 100) 78 | .transform(CircleTransform()) 79 | .into(holder.image) 80 | 81 | // Here you apply the animation when the view is bound 82 | setAnimation(holder.itemView, position) 83 | 84 | holder.lyt_parent.setOnClickListener { view -> 85 | if (mOnItemClickListener != null) { 86 | mOnItemClickListener!!.onItemClick(view, c, position) 87 | } 88 | } 89 | } 90 | 91 | fun getItem(position: Int): Friend { 92 | return filtered_items[position] 93 | } 94 | 95 | private fun setAnimation(viewToAnimate: View, position: Int) { 96 | // If the bound view wasn't previously displayed on screen, it's animated 97 | if (position > lastPosition) { 98 | val animation = AnimationUtils.loadAnimation(mContext, R.anim.slide_in_bottom) 99 | viewToAnimate.startAnimation(animation) 100 | lastPosition = position 101 | } 102 | } 103 | 104 | // Return the size of your dataset (invoked by the layout manager) 105 | override fun getItemCount(): Int { 106 | return filtered_items.size 107 | } 108 | 109 | 110 | private inner class ItemFilter : Filter() { 111 | override fun performFiltering(constraint: CharSequence): Filter.FilterResults { 112 | val query = constraint.toString().toLowerCase() 113 | 114 | val results = Filter.FilterResults() 115 | val list = original_items 116 | val result_list = ArrayList(list.size) 117 | 118 | for (i in list.indices) { 119 | val str_title = list[i].name 120 | if (str_title.toLowerCase().contains(query)) { 121 | result_list.add(list[i]) 122 | } 123 | } 124 | 125 | results.values = result_list 126 | results.count = result_list.size 127 | 128 | return results 129 | } 130 | 131 | override fun publishResults(constraint: CharSequence, results: Filter.FilterResults) { 132 | filtered_items = results.values as List 133 | notifyDataSetChanged() 134 | } 135 | } 136 | 137 | } -------------------------------------------------------------------------------- /app/src/main/java/com/app/sample/fchat/data/ParseFirebaseData.java: -------------------------------------------------------------------------------- 1 | package com.app.sample.fchat.data; 2 | 3 | import android.content.Context; 4 | 5 | import com.app.sample.fchat.model.ChatMessage; 6 | import com.app.sample.fchat.model.Friend; 7 | import com.app.sample.fchat.util.Constants; 8 | import com.google.firebase.database.DataSnapshot; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | import static com.app.sample.fchat.util.Constants.NODE_IS_READ; 14 | import static com.app.sample.fchat.util.Constants.NODE_NAME; 15 | import static com.app.sample.fchat.util.Constants.NODE_PHOTO; 16 | import static com.app.sample.fchat.util.Constants.NODE_RECEIVER_ID; 17 | import static com.app.sample.fchat.util.Constants.NODE_RECEIVER_NAME; 18 | import static com.app.sample.fchat.util.Constants.NODE_RECEIVER_PHOTO; 19 | import static com.app.sample.fchat.util.Constants.NODE_SENDER_ID; 20 | import static com.app.sample.fchat.util.Constants.NODE_SENDER_NAME; 21 | import static com.app.sample.fchat.util.Constants.NODE_SENDER_PHOTO; 22 | import static com.app.sample.fchat.util.Constants.NODE_TEXT; 23 | import static com.app.sample.fchat.util.Constants.NODE_TIMESTAMP; 24 | import static com.app.sample.fchat.util.Constants.NODE_USER_ID; 25 | 26 | /** 27 | * Created by Bibaswann on 23-06-2017. 28 | */ 29 | 30 | public class ParseFirebaseData { 31 | 32 | private SettingsAPI set; 33 | 34 | public ParseFirebaseData(Context context) { 35 | set = new SettingsAPI(context); 36 | } 37 | 38 | public ArrayList getAllUser(DataSnapshot dataSnapshot) { 39 | ArrayList frnds = new ArrayList<>(); 40 | String name = null, id = null, photo = null; 41 | for (DataSnapshot data : dataSnapshot.getChildren()) { 42 | name = data.child(NODE_NAME).getValue().toString(); 43 | id = data.child(NODE_USER_ID).getValue().toString(); 44 | photo = data.child(NODE_PHOTO).getValue().toString(); 45 | 46 | if (!set.readSetting(Constants.PREF_MY_ID).equals(id)) { 47 | frnds.add(new Friend(id, name, photo)); 48 | } 49 | } 50 | return frnds; 51 | } 52 | 53 | public List getMessagesForSingleUser(DataSnapshot dataSnapshot) { 54 | List chats = new ArrayList<>(); 55 | String text = null, msgTime = null, senderId = null, senderName = null, senderPhoto = null, 56 | receiverId = null, receiverName = null, receiverPhoto = null; 57 | Boolean read = Boolean.TRUE; 58 | for (DataSnapshot data : dataSnapshot.getChildren()) { 59 | text = data.child(NODE_TEXT).getValue().toString(); 60 | msgTime = data.child(NODE_TIMESTAMP).getValue().toString(); 61 | senderId = data.child(NODE_SENDER_ID).getValue().toString(); 62 | senderName = data.child(NODE_SENDER_NAME).getValue().toString(); 63 | senderPhoto = data.child(NODE_SENDER_PHOTO).getValue().toString(); 64 | receiverId = data.child(NODE_RECEIVER_ID).getValue().toString(); 65 | receiverName = data.child(NODE_RECEIVER_NAME).getValue().toString(); 66 | receiverPhoto = data.child(NODE_RECEIVER_PHOTO).getValue().toString(); 67 | //Node isRead is added later, may be null 68 | read = data.child(NODE_IS_READ).getValue() == null || 69 | Boolean.parseBoolean(data.child(NODE_IS_READ).getValue().toString()); 70 | 71 | chats.add( 72 | new ChatMessage(text, msgTime, receiverId, receiverName, receiverPhoto, senderId, 73 | senderName, senderPhoto, read)); 74 | } 75 | return chats; 76 | } 77 | 78 | public ArrayList getAllLastMessages(DataSnapshot dataSnapshot) { 79 | // TODO: 11/09/18 Return only last messages of every conversation current user is 80 | // involved in 81 | ArrayList lastChats = new ArrayList<>(); 82 | ArrayList tempMsgList; 83 | long lastTimeStamp; 84 | String text = null, msgTime = null, senderId = null, senderName = null, senderPhoto = null, 85 | receiverId = null, receiverName = null, receiverPhoto = null; 86 | Boolean read = Boolean.TRUE; 87 | for (DataSnapshot wholeChatData : dataSnapshot.getChildren()) { 88 | 89 | tempMsgList = new ArrayList<>(); 90 | lastTimeStamp = 0; 91 | 92 | for (DataSnapshot data : wholeChatData.getChildren()) { 93 | msgTime = data.child(NODE_TIMESTAMP).getValue().toString(); 94 | if (Long.parseLong(msgTime) > lastTimeStamp) { 95 | lastTimeStamp = Long.parseLong(msgTime); 96 | } 97 | text = data.child(NODE_TEXT).getValue().toString(); 98 | senderId = data.child(NODE_SENDER_ID).getValue().toString(); 99 | senderName = data.child(NODE_SENDER_NAME).getValue().toString(); 100 | senderPhoto = data.child(NODE_SENDER_PHOTO).getValue().toString(); 101 | receiverId = data.child(NODE_RECEIVER_ID).getValue().toString(); 102 | receiverName = data.child(NODE_RECEIVER_NAME).getValue().toString(); 103 | receiverPhoto = data.child(NODE_RECEIVER_PHOTO).getValue().toString(); 104 | //Node isRead is added later, may be null 105 | read = data.child(NODE_IS_READ).getValue() == null || 106 | Boolean.parseBoolean(data.child(NODE_IS_READ).getValue().toString()); 107 | 108 | tempMsgList.add( 109 | new ChatMessage(text, msgTime, receiverId, receiverName, receiverPhoto, 110 | senderId, senderName, senderPhoto, read)); 111 | } 112 | 113 | for (ChatMessage oneTemp : tempMsgList) { 114 | if ((set.readSetting(Constants.PREF_MY_ID).equals(oneTemp.getReceiver().getId())) || 115 | (set.readSetting("myid").equals(oneTemp.getSender().getId()))) { 116 | if (oneTemp.getTimestamp().equals(String.valueOf(lastTimeStamp))) { 117 | lastChats.add(oneTemp); 118 | } 119 | } 120 | } 121 | } 122 | return lastChats; 123 | } 124 | 125 | public ArrayList getAllUnreadReceivedMessages(DataSnapshot dataSnapshot) { 126 | ArrayList lastChats = new ArrayList<>(); 127 | ArrayList tempMsgList; 128 | long lastTimeStamp; 129 | String text, msgTime, senderId, senderName, senderPhoto, receiverId, receiverName, 130 | receiverPhoto; 131 | Boolean read; 132 | for (DataSnapshot wholeChatData : dataSnapshot.getChildren()) { 133 | 134 | tempMsgList = new ArrayList<>(); 135 | lastTimeStamp = 0; 136 | 137 | for (DataSnapshot data : wholeChatData.getChildren()) { 138 | msgTime = data.child(NODE_TIMESTAMP).getValue().toString(); 139 | if (Long.parseLong(msgTime) > lastTimeStamp) { 140 | lastTimeStamp = Long.parseLong(msgTime); 141 | } 142 | text = data.child(NODE_TEXT).getValue().toString(); 143 | senderId = data.child(NODE_SENDER_ID).getValue().toString(); 144 | senderName = data.child(NODE_SENDER_NAME).getValue().toString(); 145 | senderPhoto = data.child(NODE_SENDER_PHOTO).getValue().toString(); 146 | receiverId = data.child(NODE_RECEIVER_ID).getValue().toString(); 147 | receiverName = data.child(NODE_RECEIVER_NAME).getValue().toString(); 148 | receiverPhoto = data.child(NODE_RECEIVER_PHOTO).getValue().toString(); 149 | //Node isRead is added later, may be null 150 | read = data.child(NODE_IS_READ).getValue() == null || 151 | Boolean.parseBoolean(data.child(NODE_IS_READ).getValue().toString()); 152 | 153 | tempMsgList.add( 154 | new ChatMessage(text, msgTime, receiverId, receiverName, receiverPhoto, 155 | senderId, senderName, senderPhoto, read)); 156 | } 157 | 158 | for (ChatMessage oneTemp : tempMsgList) { 159 | if ((set.readSetting(Constants.PREF_MY_ID).equals(oneTemp.getReceiver().getId()))) { 160 | if (oneTemp.getTimestamp().equals(String.valueOf(lastTimeStamp)) && 161 | !oneTemp.isRead()) { 162 | lastChats.add(oneTemp); 163 | } 164 | } 165 | } 166 | } 167 | return lastChats; 168 | } 169 | 170 | private String encodeText(String msg) { 171 | return msg.replace(",", "#comma#").replace("{", "#braceopen#").replace("}", "#braceclose#") 172 | .replace("=", "#equals#"); 173 | } 174 | 175 | private String decodeText(String msg) { 176 | return msg.replace("#comma#", ",").replace("#braceopen#", "{").replace("#braceclose#", "}") 177 | .replace("#equals#", "="); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /app/src/main/java/com/app/sample/fchat/data/SettingsAPI.java: -------------------------------------------------------------------------------- 1 | package com.app.sample.fchat.data; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | 6 | import com.app.sample.fchat.R; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | /** 13 | * Created by Bibaswann on 20-02-2017. 14 | */ 15 | 16 | public class SettingsAPI { 17 | Context mContext; 18 | private SharedPreferences sharedSettings; 19 | 20 | public SettingsAPI(Context context) { 21 | mContext = context; 22 | sharedSettings = mContext.getSharedPreferences(mContext.getString(R.string.settings_file_name), Context.MODE_PRIVATE); 23 | } 24 | 25 | public String readSetting(String key) { 26 | return sharedSettings.getString(key, "na"); 27 | } 28 | 29 | public void addUpdateSettings(String key, String value) { 30 | SharedPreferences.Editor editor = sharedSettings.edit(); 31 | editor.putString(key, value); 32 | editor.apply(); 33 | } 34 | 35 | public void deleteAllSettings() { 36 | sharedSettings.edit().clear().apply(); 37 | } 38 | 39 | public List readAll() { 40 | List allUser = new ArrayList<>(); 41 | Map allEntries = sharedSettings.getAll(); 42 | for (Map.Entry entry : allEntries.entrySet()) { 43 | if (entry.getKey().contains("@")) 44 | allUser.add(entry.getKey() + " (" + entry.getValue() + ")"); 45 | } 46 | return allUser; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/app/sample/fchat/fragment/ConversationListFragment.kt: -------------------------------------------------------------------------------- 1 | package com.app.sample.fchat.fragment 2 | 3 | import android.os.Bundle 4 | import android.util.Log 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.fragment.app.Fragment 9 | import androidx.recyclerview.widget.LinearLayoutManager 10 | import com.app.sample.fchat.R 11 | import com.app.sample.fchat.activity.ChatActivity 12 | import com.app.sample.fchat.activity.MainActivity 13 | import com.app.sample.fchat.adapter.ChatsListAdapter 14 | import com.app.sample.fchat.data.ParseFirebaseData 15 | import com.app.sample.fchat.data.SettingsAPI 16 | import com.app.sample.fchat.model.ChatMessage 17 | import com.app.sample.fchat.ui.ViewHelper 18 | import com.app.sample.fchat.util.Constants 19 | import com.app.sample.fchat.widget.DividerItemDecoration 20 | import com.google.firebase.database.* 21 | import kotlinx.android.synthetic.main.fragment_chat.* 22 | 23 | class ConversationListFragment : Fragment() { 24 | private var mLayoutManager: LinearLayoutManager? = null 25 | var mAdapter: ChatsListAdapter? = null 26 | var valueEventListener: ValueEventListener? = null 27 | var ref: DatabaseReference? = null 28 | var viewHelper: ViewHelper? = null 29 | var pfbd: ParseFirebaseData? = null 30 | var set: SettingsAPI? = null 31 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, 32 | savedInstanceState: Bundle?): View? { 33 | val view = inflater.inflate(R.layout.fragment_chat, container, false) 34 | pfbd = ParseFirebaseData(context) 35 | set = SettingsAPI(context) 36 | viewHelper = ViewHelper(context!!) 37 | return view 38 | } 39 | 40 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 41 | super.onViewCreated(view, savedInstanceState) 42 | // activate fragment menu 43 | setHasOptionsMenu(true) 44 | // use a linear layout manager 45 | mLayoutManager = LinearLayoutManager(activity) 46 | recyclerView.layoutManager = mLayoutManager 47 | recyclerView.setHasFixedSize(true) 48 | recyclerView.addItemDecoration( 49 | DividerItemDecoration(activity, DividerItemDecoration.VERTICAL_LIST)) 50 | viewHelper!!.showProgressDialog() 51 | valueEventListener = object : ValueEventListener { 52 | override fun onDataChange(dataSnapshot: DataSnapshot) { 53 | Log.d(Constants.LOG_TAG, "Data changed from fragment") 54 | if (dataSnapshot.value != null) { 55 | if (pfbd!!.getAllLastMessages(dataSnapshot).size <= 0) { 56 | recyclerView.visibility = View.GONE 57 | llChatNotFound.visibility = View.VISIBLE 58 | } 59 | mAdapter = ChatsListAdapter(context!!, pfbd!!.getAllLastMessages(dataSnapshot)) 60 | } 61 | recyclerView.adapter = mAdapter 62 | mAdapter!!.setOnItemClickListener(object : ChatsListAdapter.OnItemClickListener { 63 | override fun onItemClick(v: View, obj: ChatMessage, position: Int) { 64 | if (obj.receiver.id == set!!.readSetting(Constants.PREF_MY_ID)) { 65 | ChatActivity 66 | .navigate(activity as MainActivity?, v.findViewById(R.id.lyt_parent), 67 | obj.sender) 68 | } else if (obj.sender.id 69 | == set!!.readSetting(Constants.PREF_MY_ID)) { 70 | ChatActivity 71 | .navigate(activity as MainActivity?, v.findViewById(R.id.lyt_parent), 72 | obj.receiver) 73 | } 74 | } 75 | }) 76 | bindView() 77 | } 78 | 79 | override fun onCancelled(databaseError: DatabaseError) {} 80 | } 81 | ref = FirebaseDatabase.getInstance().getReference(Constants.MESSAGE_CHILD) 82 | ref!!.addValueEventListener(valueEventListener!!) 83 | } 84 | 85 | fun bindView() { 86 | try { 87 | mAdapter!!.notifyDataSetChanged() 88 | viewHelper!!.dismissProgressDialog() 89 | } catch (e: Exception) { 90 | } 91 | } 92 | 93 | override fun onDestroy() { 94 | //Remove the listener, otherwise it will continue listening in the background 95 | //We have service to run in the background 96 | ref!!.removeEventListener(valueEventListener!!) 97 | super.onDestroy() 98 | } 99 | } -------------------------------------------------------------------------------- /app/src/main/java/com/app/sample/fchat/fragment/FragmentAdapter.java: -------------------------------------------------------------------------------- 1 | package com.app.sample.fchat.fragment; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import androidx.fragment.app.Fragment; 7 | import androidx.fragment.app.FragmentManager; 8 | import androidx.fragment.app.FragmentPagerAdapter; 9 | 10 | public class FragmentAdapter extends FragmentPagerAdapter { 11 | private final List mFragments = new ArrayList<>(); 12 | private final List mFragmentTitles = new ArrayList<>(); 13 | 14 | public FragmentAdapter(FragmentManager fm) { 15 | super(fm); 16 | } 17 | 18 | public void addFragment(Fragment fragment, String title) { 19 | mFragments.add(fragment); 20 | mFragmentTitles.add(title); 21 | } 22 | 23 | @Override 24 | public Fragment getItem(int position) { 25 | return mFragments.get(position); 26 | } 27 | 28 | 29 | @Override 30 | public int getCount() { 31 | return mFragments.size(); 32 | } 33 | 34 | @Override 35 | public CharSequence getPageTitle(int position) { 36 | return mFragmentTitles.get(position); 37 | } 38 | } -------------------------------------------------------------------------------- /app/src/main/java/com/app/sample/fchat/model/ChatMessage.kt: -------------------------------------------------------------------------------- 1 | package com.app.sample.fchat.model 2 | 3 | import com.app.sample.fchat.util.Tools 4 | 5 | class ChatMessage(var text: String?, var timestamp: String, var friendId: String?, var friendName: String?, var friendPhoto: String?, var senderId: String?, var senderName: String?, var senderPhoto: String?, var isRead: Boolean?) { 6 | 7 | val readableTime: String? 8 | get() { 9 | return try { 10 | Tools.formatTime(java.lang.Long.valueOf(timestamp)) 11 | } catch (ignored: NumberFormatException) { 12 | null 13 | } 14 | 15 | } 16 | 17 | val receiver: Friend 18 | get() = Friend(friendId!!, friendName!!, friendPhoto!!) 19 | 20 | val sender: Friend 21 | get() = Friend(senderId!!, senderName!!, senderPhoto!!) 22 | 23 | val comparableTimestamp: Long 24 | get() = java.lang.Long.parseLong(timestamp) 25 | 26 | 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/app/sample/fchat/model/Friend.kt: -------------------------------------------------------------------------------- 1 | package com.app.sample.fchat.model 2 | 3 | import java.io.Serializable 4 | 5 | class Friend(val id: String, val name: String, val photo: String) : Serializable 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/app/sample/fchat/service/MyFirebaseMessagingService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.app.sample.fchat.service; 17 | 18 | import android.util.Log; 19 | 20 | import com.google.firebase.iid.FirebaseInstanceId; 21 | import com.google.firebase.messaging.FirebaseMessaging; 22 | import com.google.firebase.messaging.FirebaseMessagingService; 23 | import com.google.firebase.messaging.RemoteMessage; 24 | 25 | import androidx.annotation.NonNull; 26 | 27 | import static com.app.sample.fchat.util.Constants.LOG_TAG; 28 | 29 | public class MyFirebaseMessagingService extends FirebaseMessagingService { 30 | 31 | private static final String FRIENDLY_ENGAGE_TOPIC = "friendly_engage"; 32 | 33 | @Override 34 | public void onMessageReceived(RemoteMessage remoteMessage) { 35 | // Handle data payload of FCM messages. 36 | Log.d(LOG_TAG, "FCM Message Id: " + remoteMessage.getMessageId()); 37 | Log.d(LOG_TAG, "FCM Notification Message: " + remoteMessage.getNotification()); 38 | Log.d(LOG_TAG, "FCM Data Message: " + remoteMessage.getData()); 39 | } 40 | 41 | @Override 42 | public void onNewToken(@NonNull String s) { 43 | super.onNewToken(s); 44 | String token = FirebaseInstanceId.getInstance().getToken(); 45 | 46 | // Once a token is generated, we subscribe to topic. 47 | FirebaseMessaging.getInstance().subscribeToTopic(FRIENDLY_ENGAGE_TOPIC); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/app/sample/fchat/service/NotificationService.kt: -------------------------------------------------------------------------------- 1 | package com.app.sample.fchat.service 2 | 3 | import android.app.NotificationChannel 4 | import android.app.NotificationManager 5 | import android.app.PendingIntent 6 | import android.app.job.JobParameters 7 | import android.app.job.JobService 8 | import android.content.Context 9 | import android.content.Intent 10 | import android.os.Build 11 | import android.util.Log 12 | import androidx.annotation.RequiresApi 13 | import androidx.core.app.NotificationCompat 14 | import com.app.sample.fchat.R 15 | import com.app.sample.fchat.activity.MainActivity 16 | import com.app.sample.fchat.data.ParseFirebaseData 17 | import com.app.sample.fchat.model.ChatMessage 18 | import com.app.sample.fchat.util.Constants 19 | import com.google.firebase.database.DataSnapshot 20 | import com.google.firebase.database.DatabaseError 21 | import com.google.firebase.database.FirebaseDatabase 22 | import com.google.firebase.database.ValueEventListener 23 | 24 | 25 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 26 | /** 27 | Created by bibaswann on 26/09/18. 28 | */ 29 | 30 | 31 | class NotificationService : JobService() { 32 | 33 | var mNotificationManager: NotificationManager? = null 34 | 35 | //Todo: do something about multiple notification. 36 | override fun onStartJob(params: JobParameters?): Boolean { 37 | val ref = FirebaseDatabase.getInstance().getReference(Constants.MESSAGE_CHILD) 38 | ref.addValueEventListener(object : ValueEventListener { 39 | override fun onDataChange(dataSnapshot: DataSnapshot) { 40 | Log.d(Constants.LOG_TAG, "Data changed from service") 41 | for (oneChat: ChatMessage in ParseFirebaseData(this@NotificationService).getAllUnreadReceivedMessages(dataSnapshot)) { 42 | // Log.e(Constants.LOG_TAG, oneChat.text + "\n") 43 | showNotification(oneChat.senderName.toString(), oneChat.text.toString()) 44 | } 45 | } 46 | 47 | override fun onCancelled(p0: DatabaseError) { 48 | } 49 | }) 50 | 51 | return true 52 | } 53 | 54 | override fun onStopJob(params: JobParameters?): Boolean { 55 | return true 56 | } 57 | 58 | fun showNotification(title: String, content: String) { 59 | //Todo: notification grouping 60 | mNotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager 61 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 62 | val channel = NotificationChannel("default", "Fchat-Message", NotificationManager.IMPORTANCE_DEFAULT) 63 | channel.description = "New message notification for fchat" 64 | mNotificationManager!!.createNotificationChannel(channel) 65 | } 66 | val mBuilder = NotificationCompat.Builder(applicationContext, "default") 67 | .setSmallIcon(R.drawable.ic_logo_white) // notification icon 68 | .setContentTitle(title) // title for notification 69 | .setContentText(content)// message for notification 70 | .setAutoCancel(true) // clear notification after click 71 | val intent = Intent(applicationContext, MainActivity::class.java) 72 | val pi = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) 73 | mBuilder.setContentIntent(pi) 74 | mNotificationManager!!.notify(0, mBuilder.build()) 75 | } 76 | 77 | fun clearNotification() { 78 | mNotificationManager!!.cancelAll() 79 | } 80 | 81 | } -------------------------------------------------------------------------------- /app/src/main/java/com/app/sample/fchat/ui/CustomToast.kt: -------------------------------------------------------------------------------- 1 | package com.app.sample.fchat.ui 2 | 3 | import android.content.Context 4 | import android.view.Gravity 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.widget.ImageView 8 | import android.widget.TextView 9 | import android.widget.Toast 10 | import androidx.core.content.ContextCompat 11 | import com.app.sample.fchat.R 12 | 13 | class CustomToast(val context: Context) { 14 | val text: TextView 15 | val icon: ImageView 16 | val layout: View 17 | 18 | init { 19 | val inflater: LayoutInflater = LayoutInflater.from(context) 20 | layout = inflater.inflate(R.layout.custom_meaasge, null) 21 | text = layout.findViewById(R.id.text) 22 | icon = layout.findViewById(R.id.icon) 23 | } 24 | 25 | private fun showActualToast() { 26 | val toast = Toast(context) 27 | toast.setGravity(Gravity.CENTER_VERTICAL, 0, 0) 28 | toast.duration = Toast.LENGTH_LONG 29 | toast.view = layout 30 | toast.show() 31 | 32 | //More subtle way 33 | // with(Toast(context)) { 34 | // setGravity(Gravity.CENTER_VERTICAL, 0, 0) 35 | // duration = Toast.LENGTH_LONG 36 | // view = layout 37 | // show() 38 | // } 39 | } 40 | 41 | fun showError(msg: String) { 42 | text.text = msg 43 | icon.setImageResource(R.drawable.round_error_24) 44 | icon.setColorFilter(ContextCompat.getColor(context, R.color.error), android.graphics.PorterDuff.Mode.MULTIPLY) 45 | 46 | showActualToast() 47 | } 48 | 49 | fun showInfo(msg: String) { 50 | text.text = msg 51 | icon.setImageResource(R.drawable.round_info_24) 52 | icon.setColorFilter(ContextCompat.getColor(context, R.color.info), android.graphics.PorterDuff.Mode.MULTIPLY) 53 | 54 | showActualToast() 55 | } 56 | 57 | fun showSuccess(msg: String) { 58 | text.text = msg 59 | icon.setImageResource(R.drawable.round_success_24) 60 | icon.setColorFilter(ContextCompat.getColor(context, R.color.success), android.graphics.PorterDuff.Mode.MULTIPLY) 61 | 62 | showActualToast() 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/com/app/sample/fchat/ui/ViewHelper.kt: -------------------------------------------------------------------------------- 1 | package com.app.sample.fchat.ui 2 | 3 | import android.app.NotificationManager 4 | import android.app.ProgressDialog 5 | import android.content.Context 6 | 7 | 8 | /** 9 | *Created by Bibaswann Bandyopadhyay on 20-02-2020. 10 | */ 11 | class ViewHelper(private val mContext: Context) { 12 | private var progressDialog: ProgressDialog? = null 13 | public fun showProgressDialog() { 14 | progressDialog = ProgressDialog(mContext) 15 | progressDialog?.setCancelable(false) 16 | progressDialog?.show() 17 | } 18 | 19 | public fun dismissProgressDialog() { 20 | progressDialog?.hide() 21 | } 22 | 23 | fun clearNotofication() { 24 | val notificationManager = mContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager 25 | notificationManager.cancelAll() 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/java/com/app/sample/fchat/util/Constants.kt: -------------------------------------------------------------------------------- 1 | package com.app.sample.fchat.util 2 | 3 | object Constants { 4 | //Chat nodes 5 | const val NODE_TEXT = "text" 6 | const val NODE_TIMESTAMP = "timestamp" 7 | const val NODE_RECEIVER_ID = "receiverid" 8 | const val NODE_RECEIVER_NAME = "receivername" 9 | const val NODE_RECEIVER_PHOTO = "receiverphoto" 10 | const val NODE_SENDER_ID = "senderid" 11 | const val NODE_SENDER_NAME = "sendername" 12 | const val NODE_SENDER_PHOTO = "senderphoto" 13 | const val NODE_IS_READ = "isread" 14 | //User nodes 15 | const val NODE_USER_ID = "id" 16 | const val NODE_NAME = "name" 17 | const val NODE_PHOTO = "photo" 18 | const val LOG_TAG = "fchat" 19 | const val MESSAGE_CHILD = "messages" 20 | const val PREF_MY_ID = "myid" 21 | const val PREF_MY_NAME = "myname" 22 | const val PREF_MY_DP = "mydp" 23 | 24 | //Fragment tags 25 | const val TAG_CHAT_HISTORY = "Chat History" 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/app/sample/fchat/util/Tools.java: -------------------------------------------------------------------------------- 1 | package com.app.sample.fchat.util; 2 | 3 | import android.app.Activity; 4 | import android.os.Build; 5 | import android.view.Window; 6 | import android.view.WindowManager; 7 | 8 | import com.app.sample.fchat.R; 9 | 10 | import java.text.SimpleDateFormat; 11 | import java.util.Calendar; 12 | import java.util.Locale; 13 | 14 | import androidx.annotation.RequiresApi; 15 | 16 | 17 | public class Tools { 18 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 19 | public static void systemBarLolipop(Activity act) { 20 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { 21 | Window window = act.getWindow(); 22 | window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); 23 | window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 24 | window.setStatusBarColor(act.getResources().getColor(R.color.colorPrimaryDark)); 25 | } 26 | } 27 | 28 | public static String formatTime(long time) { 29 | // income time 30 | Calendar date = Calendar.getInstance(); 31 | date.setTimeInMillis(time); 32 | 33 | // current time 34 | Calendar curDate = Calendar.getInstance(); 35 | curDate.setTimeInMillis(System.currentTimeMillis()); 36 | 37 | SimpleDateFormat dateFormat = null; 38 | if (date.get(Calendar.YEAR) == curDate.get(Calendar.YEAR)) { 39 | if (date.get(Calendar.DAY_OF_YEAR) == curDate.get(Calendar.DAY_OF_YEAR)) { 40 | dateFormat = new SimpleDateFormat("h:mm a", Locale.US); 41 | } else { 42 | dateFormat = new SimpleDateFormat("MMM d", Locale.US); 43 | } 44 | } else { 45 | dateFormat = new SimpleDateFormat("MMM yyyy", Locale.US); 46 | } 47 | return dateFormat.format(time); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/app/sample/fchat/widget/CircleTransform.java: -------------------------------------------------------------------------------- 1 | 2 | 3 | package com.app.sample.fchat.widget; 4 | 5 | import android.graphics.Bitmap; 6 | import android.graphics.BitmapShader; 7 | import android.graphics.Canvas; 8 | import android.graphics.Paint; 9 | 10 | import com.squareup.picasso.Transformation; 11 | 12 | public class CircleTransform implements Transformation { 13 | 14 | @Override 15 | public Bitmap transform(Bitmap source) { 16 | int size = Math.min(source.getWidth(), source.getHeight()); 17 | 18 | int x = (source.getWidth() - size) / 2; 19 | int y = (source.getHeight() - size) / 2; 20 | 21 | Bitmap squaredBitmap = Bitmap.createBitmap(source, x, y, size, size); 22 | if (squaredBitmap != source) { 23 | source.recycle(); 24 | } 25 | 26 | Bitmap bitmap = Bitmap.createBitmap(size, size, source.getConfig()); 27 | 28 | Canvas canvas = new Canvas(bitmap); 29 | Paint paint = new Paint(); 30 | BitmapShader shader = new BitmapShader(squaredBitmap, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP); 31 | paint.setShader(shader); 32 | paint.setAntiAlias(true); 33 | 34 | float r = size / 2f; 35 | canvas.drawCircle(r, r, r, paint); 36 | 37 | squaredBitmap.recycle(); 38 | return bitmap; 39 | } 40 | 41 | // public Drawable circleDrawable(Drawable d){ 42 | // Bitmap bitmap = ((BitmapDrawable)d).getBitmap(); 43 | // Drawable drawable=new BitmapDrawable(transform(bitmap)); 44 | // return drawable; 45 | // } 46 | 47 | @Override 48 | public String key() { 49 | return "circle"; 50 | } 51 | } -------------------------------------------------------------------------------- /app/src/main/java/com/app/sample/fchat/widget/DividerItemDecoration.java: -------------------------------------------------------------------------------- 1 | package com.app.sample.fchat.widget; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Canvas; 6 | import android.graphics.Rect; 7 | import android.graphics.drawable.Drawable; 8 | import android.view.View; 9 | 10 | import androidx.recyclerview.widget.LinearLayoutManager; 11 | import androidx.recyclerview.widget.RecyclerView; 12 | 13 | public class DividerItemDecoration extends RecyclerView.ItemDecoration { 14 | 15 | private static final int[] ATTRS = new int[]{ 16 | android.R.attr.listDivider 17 | }; 18 | 19 | public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL; 20 | 21 | public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL; 22 | 23 | private Drawable mDivider; 24 | 25 | private int mOrientation; 26 | 27 | public DividerItemDecoration(Context context, int orientation) { 28 | final TypedArray a = context.obtainStyledAttributes(ATTRS); 29 | mDivider = a.getDrawable(0); 30 | a.recycle(); 31 | setOrientation(orientation); 32 | } 33 | 34 | public void setOrientation(int orientation) { 35 | if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) { 36 | throw new IllegalArgumentException("invalid orientation"); 37 | } 38 | mOrientation = orientation; 39 | } 40 | 41 | @Override 42 | public void onDraw(Canvas c, RecyclerView parent) { 43 | if (mOrientation == VERTICAL_LIST) { 44 | drawVertical(c, parent); 45 | } else { 46 | drawHorizontal(c, parent); 47 | } 48 | } 49 | 50 | public void drawVertical(Canvas c, RecyclerView parent) { 51 | final int left = parent.getPaddingLeft(); 52 | final int right = parent.getWidth() - parent.getPaddingRight(); 53 | 54 | final int childCount = parent.getChildCount(); 55 | for (int i = 0; i < childCount; i++) { 56 | final View child = parent.getChildAt(i); 57 | final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); 58 | final int top = child.getBottom() + params.bottomMargin; 59 | final int bottom = top + mDivider.getIntrinsicHeight(); 60 | mDivider.setBounds(left, top, right, bottom); 61 | mDivider.draw(c); 62 | } 63 | } 64 | 65 | public void drawHorizontal(Canvas c, RecyclerView parent) { 66 | final int top = parent.getPaddingTop(); 67 | final int bottom = parent.getHeight() - parent.getPaddingBottom(); 68 | 69 | final int childCount = parent.getChildCount(); 70 | for (int i = 0; i < childCount; i++) { 71 | final View child = parent.getChildAt(i); 72 | final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); 73 | final int left = child.getRight() + params.rightMargin; 74 | final int right = left + mDivider.getIntrinsicHeight(); 75 | mDivider.setBounds(left, top, right, bottom); 76 | mDivider.draw(c); 77 | } 78 | } 79 | 80 | @Override 81 | public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) { 82 | if (mOrientation == VERTICAL_LIST) { 83 | outRect.set(0, 0, 0, mDivider.getIntrinsicHeight()); 84 | } else { 85 | outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0); 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /app/src/main/res/anim/slide_in_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_done_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_done_all_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_exit_to_app_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_send.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/circle_blue.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_discuss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g0g0l/FChat/383578eb73ab35e11cb322568dfa32d6ff04dea6/app/src/main/res/drawable/ic_discuss.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_logo_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g0g0l/FChat/383578eb73ab35e11cb322568dfa32d6ff04dea6/app/src/main/res/drawable/ic_logo_white.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_not_found.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g0g0l/FChat/383578eb73ab35e11cb322568dfa32d6ff04dea6/app/src/main/res/drawable/ic_not_found.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_people.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g0g0l/FChat/383578eb73ab35e11cb322568dfa32d6ff04dea6/app/src/main/res/drawable/ic_people.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_send.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g0g0l/FChat/383578eb73ab35e11cb322568dfa32d6ff04dea6/app/src/main/res/drawable/ic_send.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/layout_click.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/round_error_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/round_info_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/round_success_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/round_warning_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/thread_bg_me.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/thread_bg_you.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/toolbar_gradient.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/unknown_avatar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_chat.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 15 | 16 | 26 | 27 | 28 | 29 | 37 | 38 | 39 | 46 | 47 | 58 | 59 | 67 | 68 | 69 | 70 |