├── .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 | 
5 | 
6 | 
7 | 
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 |
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
11 |
17 |
18 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_new_chat.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_splash.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
19 |
20 |
26 |
27 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/custom_meaasge.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
17 |
24 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_chat.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
20 |
21 |
35 |
36 |
41 |
42 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/row_chat_details.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
18 |
19 |
27 |
35 |
36 |
43 |
44 |
51 |
52 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/row_chats.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
18 |
19 |
25 |
26 |
34 |
35 |
43 |
44 |
54 |
55 |
56 |
57 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/row_friends.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
17 |
18 |
24 |
25 |
32 |
33 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/toolbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/g0g0l/FChat/383578eb73ab35e11cb322568dfa32d6ff04dea6/app/src/main/res/mipmap/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | #9b59b6
6 | #8e44ad
7 | #d7ccc8
8 |
9 | #2ecc71
10 | #27ae60
11 | #2ecc71
12 | @color/grey_hard
13 |
14 | @color/background_material_light
15 |
16 | #767676
17 |
18 | #E0E0E0
19 |
20 | #96989A
21 | #BDBFC1
22 | #F5F5F5
23 |
24 | #EBEFF0
25 | #222D31
26 | #FFE0B2
27 |
28 | #ff9933
29 | #8b0000
30 | #000080
31 | #008000
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 | 10dp
6 | 26dp
7 |
8 | 3dp
9 | 2dp
10 | 4dp
11 | 10dp
12 | 16dp
13 | 20dp
14 | 32dp
15 |
16 | 9dp
17 | 12dp
18 | 25dp
19 | 16dp
20 |
21 |
22 | 192dp
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | FChat
4 |
5 |
6 | Press again to exit
7 |
8 | com.app.sample.fchat.temp
9 | Authentication failed.
10 | Could not connect
11 | Login failed
12 | No Chat found
13 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
14 |
23 |
24 |
31 |
34 |
35 |
36 |
43 |
44 |
48 |
49 |
58 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext.kotlin_version = '1.3.61'
5 | repositories {
6 | jcenter()
7 | google()
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.5.3'
11 | classpath 'com.google.gms:google-services:4.3.3'
12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
13 | // NOTE: Do not place your application dependencies here; they belong
14 | // in the individual module build.gradle files
15 | }
16 | }
17 |
18 | allprojects {
19 | repositories {
20 | jcenter()
21 | google()
22 | maven {
23 | url "https://maven.google.com"
24 | }
25 | }
26 | }
27 |
28 | task clean(type: Delete) {
29 | delete rootProject.buildDir
30 | }
31 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 | android.enableJetifier=true
20 | android.useAndroidX=true
21 | org.gradle.jvmargs=-XX\:MaxHeapSize\=1024m -Xmx512m
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/g0g0l/FChat/383578eb73ab35e11cb322568dfa32d6ff04dea6/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Feb 19 16:08:22 IST 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/screenshots/screenshot_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/g0g0l/FChat/383578eb73ab35e11cb322568dfa32d6ff04dea6/screenshots/screenshot_1.png
--------------------------------------------------------------------------------
/screenshots/screenshot_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/g0g0l/FChat/383578eb73ab35e11cb322568dfa32d6ff04dea6/screenshots/screenshot_2.png
--------------------------------------------------------------------------------
/screenshots/screenshot_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/g0g0l/FChat/383578eb73ab35e11cb322568dfa32d6ff04dea6/screenshots/screenshot_3.png
--------------------------------------------------------------------------------
/screenshots/screenshot_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/g0g0l/FChat/383578eb73ab35e11cb322568dfa32d6ff04dea6/screenshots/screenshot_4.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------