├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── mayurrokade │ │ └── chatapp │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-web.png │ ├── java │ │ └── com │ │ │ └── mayurrokade │ │ │ └── chatapp │ │ │ ├── BaseApplication.java │ │ │ ├── BasePresenter.java │ │ │ ├── BaseView.java │ │ │ ├── about │ │ │ └── AboutActivity.java │ │ │ ├── chat │ │ │ ├── ChatActivity.java │ │ │ ├── ChatContract.java │ │ │ ├── ChatMessagesAdapter.java │ │ │ └── ChatPresenter.java │ │ │ ├── data │ │ │ ├── ChatMessage.java │ │ │ └── source │ │ │ │ ├── DataSource.java │ │ │ │ ├── Repository.java │ │ │ │ ├── local │ │ │ │ └── LocalDataSource.java │ │ │ │ └── remote │ │ │ │ └── RemoteDataSource.java │ │ │ ├── eventservice │ │ │ ├── EventListener.java │ │ │ ├── EventService.java │ │ │ └── EventServiceImpl.java │ │ │ └── util │ │ │ ├── AppLifeCycleObserver.java │ │ │ ├── Injection.java │ │ │ ├── TextUtils.java │ │ │ ├── User.java │ │ │ └── schedulers │ │ │ ├── BaseSchedulerProvider.java │ │ │ └── SchedulerProvider.java │ └── res │ │ ├── drawable-hdpi │ │ ├── ic_account.png │ │ ├── ic_back.png │ │ ├── ic_edit.png │ │ ├── ic_info.png │ │ ├── ic_logo_no_background.png │ │ └── ic_send.png │ │ ├── drawable-mdpi │ │ ├── ic_account.png │ │ ├── ic_back.png │ │ ├── ic_edit.png │ │ ├── ic_info.png │ │ ├── ic_logo_no_background.png │ │ └── ic_send.png │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable-xhdpi │ │ ├── ic_account.png │ │ ├── ic_back.png │ │ ├── ic_edit.png │ │ ├── ic_info.png │ │ ├── ic_logo_no_background.png │ │ └── ic_send.png │ │ ├── drawable-xxhdpi │ │ ├── ic_account.png │ │ ├── ic_back.png │ │ ├── ic_edit.png │ │ ├── ic_info.png │ │ ├── ic_logo_no_background.png │ │ └── ic_send.png │ │ ├── drawable-xxxhdpi │ │ ├── about_icon_copy_right.xml │ │ └── ic_logo_no_background.png │ │ ├── drawable │ │ ├── bg_received_message.xml │ │ ├── bg_rounded_border.xml │ │ ├── bg_rounded_filled.xml │ │ ├── bg_sent_message.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_linkedin_logo.xml │ │ ├── ic_medium_logo.xml │ │ └── profile_image_round.png │ │ ├── layout │ │ ├── activity_about.xml │ │ ├── activity_chat.xml │ │ ├── dialog_set_username.xml │ │ ├── item_message_received.xml │ │ └── item_message_sent.xml │ │ ├── menu │ │ └── menu_options.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── mayurrokade │ └── chatapp │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── Android MVP_realtime.png ├── demo.gif ├── logo_socket_chat_circle.png ├── realtime_android_architecture.png ├── socket_chat.png └── socket_chat_backdrop.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .idea/* 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Mayur Rokade 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 |

2 | 3 |

4 | 5 |

Socket Chat

6 |

A demo app to showcase Android MVP Realtime architecture.

7 | 8 | Socket Chat is a demo app built using Android MVP Realtime architecture. You can read more about the architecture on my Medium article https://bit.ly/2KXcTan . 9 | 10 | This app connects to Socket.IO chat room at https://socket-io-chat.now.sh. Suggestions for further improvements are most welcome. 11 | 12 | ## Screenshot 13 |

14 | 15 |

16 | 17 | ## Problem Statement 18 | Most of us Android developers have created apps using the MVP architecture. A regular android app involves making HTTP API calls to the server, getting some data and rendering the view. Using these APIs, you can accomplish things like 19 | 20 | - Booking a cab 21 | - Ordering lunch 22 | - Transferring money etc. 23 | 24 | As you can see, HTTP APIs are quite simple, scalable and you can get a lot of things done. Once you learn MVP with RxJava and Retrofit you are ready to take on the world with your amazing app. Yay!! 25 | 26 | This feeling of awesomeness is quickly replaced by confusion the moment you try to build realtime apps like 27 | 28 | - Chat app 29 | - Multiplayer gaming app 30 | - Realtime stock price updates app etc. 31 | 32 | ## Limitations of Android MVP 33 | When I say Android MVP, I am referring to the [Todo-MVP-RxJava example from googlesamples](https://github.com/googlesamples/android-architecture/tree/todo-mvp-rxjava/). 34 | 35 | In this Android MVP example, 36 | 37 | - View is responsible for rendering the UI 38 | - Presenter is responsible for presenting the data 39 | - Repository is responsible for getting the data 40 | 41 | This architecuture is based on request-response model, where the client requests for some resource from the server and the server sends back a response. But the other way round, where the server sends data to the client and the client handles the data, is not possible in the architecture. Simply because, the client is not actively listening for incoming data from the server. 42 | 43 | ## Proposed Solution 44 | So for the app to be realtime app, it should be able to send and receive events/data from the server. Based on this, if we try to draw an architecture, it would look some thing like this. 45 | 46 |

47 | 48 |

49 | 50 | This Android project is an working implementation of the proposed Android MVP Realtime architecture. 51 | 52 | ## Usage 53 | If you want to try out the app, follow the below steps 54 | 55 | - Clone the repo 56 | - Open the project in Android Studio 57 | - Run it on your Android phone 58 | - In the web browser, open https://socket-io-chat.now.sh. 59 | 60 | This way you can send message between the app and the web chatroom. 61 | 62 | ## Quick Demo 63 | Check out this video on YouTube to see the app in action. 64 | 65 | [![IMAGE ALT TEXT](http://img.youtube.com/vi/BJ70gpBXBcU/0.jpg)](http://www.youtube.com/watch?v=BJ70gpBXBcU "Socket Chat - Android MVP Realtime architecture") 66 | 67 | **NOTE:** Sometimes, when you join a Socket.IO chat room on web, it might be a different chat room than the one to which the app is connected. I don't know what causes this since I am not the developer of Socket.IO web chat room. 68 | 69 | ## Disclaimer 70 | This is a side project app to experiment/play around with Android framework. This is not a production app. 71 | So there is no guarantee that issues/feature-requests/enhancements will be worked upon. 72 | 73 | ## License 74 | MIT 75 | 76 | --- 77 | > Github [@mayuroks](https://github.com/mayuroks) ~ Twitter [@mayuroks](https://twitter.com/mayuroks) ~ Medium [@mayuroks](https://medium.com/@mayuroks) 78 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | *.idea/ -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 27 5 | defaultConfig { 6 | applicationId "com.mayurrokade.chatapp" 7 | minSdkVersion 16 8 | targetSdkVersion 27 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation fileTree(dir: 'libs', include: ['*.jar']) 23 | implementation 'com.android.support:appcompat-v7:27.1.1' 24 | implementation 'com.android.support:design:27.1.1' 25 | implementation 'com.android.support.constraint:constraint-layout:1.1.2' 26 | implementation 'com.android.support:recyclerview-v7:27.1.1' 27 | implementation 'io.reactivex.rxjava2:rxandroid:2.0.1' 28 | implementation 'io.reactivex.rxjava2:rxjava:2.1.7' 29 | implementation('io.socket:socket.io-client:1.0.0') { 30 | // excluding org.json which is provided by Android 31 | exclude group: 'org.json', module: 'json' 32 | } 33 | implementation 'android.arch.lifecycle:extensions:1.1.1' 34 | implementation 'com.wang.avi:library:2.1.3' 35 | implementation 'com.github.medyo:android-about-page:1.2.4' 36 | testImplementation 'junit:junit:4.12' 37 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 38 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 39 | } 40 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/mayurrokade/chatapp/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.mayurrokade.chatapp; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.mayurrokade.chatapp", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayuroks/android-mvp-realtime-chat/188f9df083f2e0ecfab2c441782913723e9a2fc1/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/com/mayurrokade/chatapp/BaseApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Mayur Rokade 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | package com.mayurrokade.chatapp; 24 | 25 | import android.app.Application; 26 | import android.arch.lifecycle.ProcessLifecycleOwner; 27 | 28 | import com.mayurrokade.chatapp.util.AppLifeCycleObserver; 29 | import com.mayurrokade.chatapp.util.User; 30 | 31 | import java.util.UUID; 32 | 33 | public class BaseApplication extends Application { 34 | @Override 35 | public void onCreate() { 36 | super.onCreate(); 37 | 38 | // Observer to detect if the app is in background or foreground. 39 | AppLifeCycleObserver lifeCycleObserver 40 | = new AppLifeCycleObserver(getApplicationContext()); 41 | 42 | // Adding the above observer to process lifecycle 43 | ProcessLifecycleOwner.get() 44 | .getLifecycle() 45 | .addObserver(lifeCycleObserver); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/com/mayurrokade/chatapp/BasePresenter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Mayur Rokade 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | package com.mayurrokade.chatapp; 24 | 25 | public interface BasePresenter { 26 | 27 | void subscribe(); 28 | 29 | void unsubscribe(); 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/mayurrokade/chatapp/BaseView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Mayur Rokade 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | package com.mayurrokade.chatapp; 24 | 25 | public interface BaseView { 26 | 27 | void initView(); 28 | 29 | void setPresenter(T presenter); 30 | 31 | void showAlert(String message, boolean isError); 32 | 33 | void hideAlert(); 34 | 35 | void showProgress(); 36 | 37 | void hideProgress(); 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/mayurrokade/chatapp/about/AboutActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Mayur Rokade 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | package com.mayurrokade.chatapp.about; 24 | 25 | import android.content.Intent; 26 | import android.net.Uri; 27 | import android.os.Bundle; 28 | import android.support.v7.app.AppCompatActivity; 29 | import android.view.Gravity; 30 | import android.view.MenuItem; 31 | import android.view.View; 32 | import android.widget.Toast; 33 | 34 | import com.mayurrokade.chatapp.R; 35 | 36 | import java.util.Calendar; 37 | 38 | import mehdi.sakout.aboutpage.AboutPage; 39 | import mehdi.sakout.aboutpage.Element; 40 | 41 | public class AboutActivity extends AppCompatActivity { 42 | 43 | @Override 44 | protected void onCreate(Bundle savedInstanceState) { 45 | super.onCreate(savedInstanceState); 46 | setContentView(R.layout.activity_about); 47 | 48 | getSupportActionBar().setTitle("About Me"); 49 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 50 | getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_back); 51 | 52 | View aboutPage = new AboutPage(this) 53 | .isRTL(false) 54 | .setImage(R.drawable.profile_image_round) 55 | .addItem(new Element().setTitle("Get in touch")) 56 | .addItem(new Element().setTitle("LinkedIn") 57 | .setIconDrawable(R.drawable.ic_linkedin_logo) 58 | .setIconTint(R.color.colorTextRegular) 59 | .setOnClickListener(new View.OnClickListener() { 60 | @Override 61 | public void onClick(View view) { 62 | Uri webpage = Uri.parse("https://linkedin.com/in/mayurrokade/"); 63 | startActivity(new Intent(Intent.ACTION_VIEW, webpage)); 64 | } 65 | })) 66 | .addItem(new Element().setTitle("Medium") 67 | .setIconDrawable(R.drawable.ic_medium_logo) 68 | .setIconTint(R.color.colorTextRegular) 69 | .setOnClickListener(new View.OnClickListener() { 70 | @Override 71 | public void onClick(View view) { 72 | Uri webpage = Uri.parse("https://medium.com/@mayuroks"); 73 | startActivity(new Intent(Intent.ACTION_VIEW, webpage)); 74 | } 75 | })) 76 | .addGitHub("mayuroks") 77 | .addFacebook("mayurzenith") 78 | .addInstagram("mayurzenith") 79 | .addTwitter("mayuroks") 80 | .addEmail("mayurzenith@gmail.com") 81 | .addItem(getCopyRightsElement()) 82 | .create(); 83 | 84 | setContentView(aboutPage); 85 | } 86 | 87 | @Override 88 | public boolean onOptionsItemSelected(MenuItem item) { 89 | if (item.getItemId() == android.R.id.home) { 90 | onBackPressed(); 91 | } 92 | 93 | return super.onOptionsItemSelected(item); 94 | } 95 | 96 | Element getCopyRightsElement() { 97 | Element copyRightsElement = new Element(); 98 | final String copyrights = String.format(getString(R.string.copy_right), 99 | Calendar.getInstance().get(Calendar.YEAR)); 100 | copyRightsElement.setTitle(copyrights); 101 | copyRightsElement.setIconDrawable(R.drawable.about_icon_copy_right); 102 | copyRightsElement.setIconTint(mehdi.sakout.aboutpage.R.color.about_item_icon_color); 103 | copyRightsElement.setIconNightTint(android.R.color.white); 104 | copyRightsElement.setGravity(Gravity.CENTER); 105 | copyRightsElement.setOnClickListener(new View.OnClickListener() { 106 | @Override 107 | public void onClick(View v) { 108 | Toast.makeText(AboutActivity.this, copyrights, Toast.LENGTH_SHORT).show(); 109 | } 110 | }); 111 | return copyRightsElement; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /app/src/main/java/com/mayurrokade/chatapp/chat/ChatActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Mayur Rokade 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | package com.mayurrokade.chatapp.chat; 24 | 25 | import android.animation.Animator; 26 | import android.animation.AnimatorListenerAdapter; 27 | import android.content.Intent; 28 | import android.os.Bundle; 29 | import android.os.Handler; 30 | import android.support.v4.content.ContextCompat; 31 | import android.support.v7.app.AlertDialog; 32 | import android.support.v7.app.AppCompatActivity; 33 | import android.support.v7.widget.LinearLayoutManager; 34 | import android.support.v7.widget.RecyclerView; 35 | import android.text.Editable; 36 | import android.text.TextWatcher; 37 | import android.util.Log; 38 | import android.view.Menu; 39 | import android.view.MenuInflater; 40 | import android.view.MenuItem; 41 | import android.view.View; 42 | import android.widget.Button; 43 | import android.widget.EditText; 44 | import android.widget.ImageView; 45 | import android.widget.LinearLayout; 46 | import android.widget.TextView; 47 | 48 | import com.mayurrokade.chatapp.R; 49 | import com.mayurrokade.chatapp.about.AboutActivity; 50 | import com.mayurrokade.chatapp.data.ChatMessage; 51 | import com.mayurrokade.chatapp.util.Injection; 52 | import com.mayurrokade.chatapp.util.TextUtils; 53 | import com.mayurrokade.chatapp.util.User; 54 | 55 | import org.json.JSONException; 56 | import org.json.JSONObject; 57 | 58 | import java.util.ArrayList; 59 | 60 | public class ChatActivity 61 | extends AppCompatActivity 62 | implements ChatContract.View { 63 | 64 | private static final String TAG = ChatActivity.class.getSimpleName(); 65 | private static final long TYPING_TIMER_LENGTH = 3000; 66 | private static final long ALERT_LENGTH = 2000; 67 | private RecyclerView rvChatMessages; 68 | private RecyclerView.LayoutManager mLayoutManager; 69 | private ChatMessagesAdapter mChatMessagesAdapter; 70 | private EditText etSendMessage; 71 | private ImageView ivSendMessage; 72 | private LinearLayout llTyping; 73 | private TextView tvTyping, tvAlert; 74 | private ChatContract.Presenter mPresenter; 75 | private boolean mTyping = false; 76 | private Handler mTypingHandler = new Handler(); 77 | private int mAlerterHeight; 78 | 79 | @Override 80 | protected void onCreate(Bundle savedInstanceState) { 81 | super.onCreate(savedInstanceState); 82 | setContentView(R.layout.activity_chat); 83 | } 84 | 85 | @Override 86 | protected void onResume() { 87 | super.onResume(); 88 | 89 | new ChatPresenter(this, this, 90 | Injection.provideSchedulerProvider(), 91 | Injection.providesRepository(this)); 92 | } 93 | 94 | @Override 95 | protected void onPause() { 96 | super.onPause(); 97 | mPresenter.unsubscribe(); 98 | } 99 | 100 | @Override 101 | public boolean onCreateOptionsMenu(Menu menu) { 102 | MenuInflater inflater = getMenuInflater(); 103 | inflater.inflate(R.menu.menu_options, menu); 104 | return true; 105 | } 106 | 107 | @Override 108 | public boolean onOptionsItemSelected(MenuItem item) { 109 | switch (item.getItemId()) { 110 | case R.id.change_name: 111 | askUsername(); 112 | break; 113 | case R.id.info: 114 | showInfo(); 115 | break; 116 | default: 117 | break; 118 | } 119 | 120 | return super.onOptionsItemSelected(item); 121 | } 122 | 123 | @Override 124 | public void initView() { 125 | // Init UI elements 126 | rvChatMessages = findViewById(R.id.rvChatMessages); 127 | etSendMessage = findViewById(R.id.etSendMessage); 128 | ivSendMessage = findViewById(R.id.btnSendMessage); 129 | tvTyping = findViewById(R.id.tvTyping); 130 | llTyping = findViewById(R.id.llTyping); 131 | tvAlert = findViewById(R.id.tvAlert); 132 | tvAlert.setTranslationY(-100); 133 | 134 | getSupportActionBar().setTitle("Realtime MVP Chat"); 135 | 136 | // Ask the user to set a username, 137 | // when the app opens up. 138 | if (!User.isUsernameUpdated()) { 139 | askUsername(); 140 | } 141 | 142 | setupChatMessages(); 143 | setupSendButton(); 144 | setupTextWatcher(); 145 | } 146 | 147 | @Override 148 | public void setPresenter(ChatContract.Presenter presenter) { 149 | mPresenter = presenter; 150 | } 151 | 152 | @Override 153 | public void showAlert(final String message, final boolean isError) { 154 | Log.i(TAG, "showAlert: " + message); 155 | final int successColor = ContextCompat.getColor(this, R.color.colorSuccess); 156 | final int errorColor = ContextCompat.getColor(this, R.color.colorError); 157 | 158 | runOnUiThread(new Runnable() { 159 | @Override 160 | public void run() { 161 | tvAlert.setText(message); 162 | 163 | if (isError) { 164 | tvAlert.setBackgroundColor(errorColor); 165 | } else { 166 | tvAlert.setBackgroundColor(successColor); 167 | } 168 | 169 | mAlerterHeight = tvAlert.getHeight(); 170 | tvAlert.setTranslationY(-1 * mAlerterHeight); 171 | 172 | tvAlert.animate() 173 | .translationY(0) 174 | .setDuration(500) 175 | .setListener(new AnimatorListenerAdapter() { 176 | @Override 177 | public void onAnimationEnd(Animator animation) { 178 | super.onAnimationEnd(animation); 179 | final Handler handler = new Handler(); 180 | handler.postDelayed(new Runnable() { 181 | @Override 182 | public void run() { 183 | hideAlert(); 184 | } 185 | }, ALERT_LENGTH); 186 | } 187 | }); 188 | } 189 | }); 190 | } 191 | 192 | @Override 193 | public void hideAlert() { 194 | tvAlert.animate() 195 | .translationY(-1 * mAlerterHeight) 196 | .setDuration(500) 197 | .setListener(new AnimatorListenerAdapter() { 198 | @Override 199 | public void onAnimationEnd(Animator animation) { 200 | super.onAnimationEnd(animation); 201 | tvAlert.setText(""); 202 | } 203 | }); 204 | } 205 | 206 | @Override 207 | public void showProgress() { 208 | 209 | } 210 | 211 | @Override 212 | public void hideProgress() { 213 | 214 | } 215 | 216 | private void setupChatMessages() { 217 | mChatMessagesAdapter = new ChatMessagesAdapter(new ArrayList(), this); 218 | mLayoutManager = new LinearLayoutManager(this); 219 | rvChatMessages.setAdapter(mChatMessagesAdapter); 220 | rvChatMessages.setLayoutManager(mLayoutManager); 221 | } 222 | 223 | private void setupSendButton() { 224 | ivSendMessage.setOnClickListener(new View.OnClickListener() { 225 | @Override 226 | public void onClick(View view) { 227 | sendMessage(); 228 | } 229 | }); 230 | } 231 | 232 | private void sendMessage() { 233 | String message = etSendMessage.getText().toString().trim(); 234 | 235 | if (TextUtils.isValidString(message)) { 236 | ChatMessage chatMessage = new ChatMessage( 237 | User.getUsername(), message, ChatMessage.TYPE_MESSAGE_SENT); 238 | mPresenter.sendMessage(chatMessage); 239 | addMessage(chatMessage); 240 | etSendMessage.setText(""); 241 | } 242 | } 243 | 244 | private void addMessage(ChatMessage chatMessage) { 245 | mChatMessagesAdapter.addNewMessage(chatMessage); 246 | rvChatMessages.scrollToPosition(mChatMessagesAdapter.getItemCount() - 1); 247 | } 248 | 249 | private void askUsername() { 250 | AlertDialog.Builder builder = new AlertDialog.Builder(this); 251 | View view = getLayoutInflater().inflate( 252 | R.layout.dialog_set_username, null); 253 | final AlertDialog dialog = builder.setView(view).setCancelable(false).create(); 254 | 255 | Button btnSave = view.findViewById(R.id.btnSave); 256 | Button btnClose = view.findViewById(R.id.btnClose); 257 | final EditText etUsername = view.findViewById(R.id.etUsername); 258 | etUsername.setText(User.getUsername()); 259 | 260 | btnSave.setOnClickListener(new View.OnClickListener() { 261 | @Override 262 | public void onClick(View view) { 263 | String username = etUsername.getText().toString().trim(); 264 | if (TextUtils.isValidString(username)) { 265 | mPresenter.changeUsername(username); 266 | dialog.dismiss(); 267 | } 268 | } 269 | }); 270 | 271 | btnClose.setOnClickListener(new View.OnClickListener() { 272 | @Override 273 | public void onClick(View view) { 274 | dialog.dismiss(); 275 | } 276 | }); 277 | 278 | dialog.show(); 279 | } 280 | 281 | private void showInfo() { 282 | startActivity(new Intent(this, AboutActivity.class)); 283 | } 284 | 285 | private void setupTextWatcher() { 286 | etSendMessage.addTextChangedListener(new TextWatcher() { 287 | @Override 288 | public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { 289 | 290 | } 291 | 292 | @Override 293 | public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { 294 | if (!mTyping) { 295 | mTyping = true; 296 | mPresenter.onTyping(); 297 | } 298 | 299 | mTypingHandler.removeCallbacks(onTypingTimeout); 300 | mTypingHandler.postDelayed(onTypingTimeout, TYPING_TIMER_LENGTH); 301 | } 302 | 303 | @Override 304 | public void afterTextChanged(Editable editable) { 305 | 306 | } 307 | }); 308 | } 309 | 310 | @Override 311 | public void onConnect(final Object... args) { 312 | showAlert("Connected", false); 313 | } 314 | 315 | @Override 316 | public void onDisconnect(final Object... args) { 317 | showAlert("Disconnected", false); 318 | } 319 | 320 | @Override 321 | public void onConnectError(final Object... args) { 322 | showAlert("No internet connection", true); 323 | } 324 | 325 | @Override 326 | public void onConnectTimeout(final Object... args) { 327 | showAlert("Connection Timeout", false); 328 | 329 | } 330 | 331 | @Override 332 | public void onNewMessage(final Object... args) { 333 | runOnUiThread(new Runnable() { 334 | @Override 335 | public void run() { 336 | JSONObject data = (JSONObject) args[0]; 337 | String username; 338 | String message; 339 | try { 340 | username = data.getString("username"); 341 | message = data.getString("message"); 342 | ChatMessage chatMessage = new ChatMessage( 343 | username, message, ChatMessage.TYPE_MESSAGE_RECEIVED); 344 | addMessage(chatMessage); 345 | } catch (JSONException e) { 346 | Log.e(TAG, e.getMessage()); 347 | return; 348 | } 349 | } 350 | }); 351 | } 352 | 353 | @Override 354 | public void onUserJoined(Object... args) { 355 | JSONObject data = (JSONObject) args[0]; 356 | String username; 357 | int numUsers; 358 | try { 359 | username = data.getString("username"); 360 | numUsers = data.getInt("numUsers"); 361 | } catch (JSONException e) { 362 | Log.e(TAG, e.getMessage()); 363 | return; 364 | } 365 | 366 | showAlert(username + " has joined", false); 367 | } 368 | 369 | @Override 370 | public void onUserLeft(Object... args) { 371 | JSONObject data = (JSONObject) args[0]; 372 | String username; 373 | int numUsers; 374 | try { 375 | username = data.getString("username"); 376 | numUsers = data.getInt("numUsers"); 377 | } catch (JSONException e) { 378 | Log.e(TAG, e.getMessage()); 379 | return; 380 | } 381 | 382 | showAlert(username + " has left", false); 383 | } 384 | 385 | @Override 386 | public void onTyping(Object... args) { 387 | JSONObject data = (JSONObject) args[0]; 388 | String username; 389 | try { 390 | username = data.getString("username"); 391 | } catch (JSONException e) { 392 | Log.e(TAG, e.getMessage()); 393 | return; 394 | } 395 | 396 | addTyping(username); 397 | Log.i(TAG, "onTyping: " + username); 398 | } 399 | 400 | @Override 401 | public void onStopTyping(Object... args) { 402 | JSONObject data = (JSONObject) args[0]; 403 | String username; 404 | try { 405 | username = data.getString("username"); 406 | } catch (JSONException e) { 407 | Log.e(TAG, e.getMessage()); 408 | return; 409 | } 410 | removeTyping(username); 411 | } 412 | 413 | @Override 414 | public void onMessageDelivered(ChatMessage chatMessage) { 415 | // Update UI to show the message has been delivered 416 | } 417 | 418 | @Override 419 | public void updateUsername(String username) { 420 | User.setUsername(username); 421 | } 422 | 423 | private Runnable onTypingTimeout = new Runnable() { 424 | @Override 425 | public void run() { 426 | if (!mTyping) return; 427 | 428 | mTyping = false; 429 | mPresenter.onStopTyping(); 430 | } 431 | }; 432 | 433 | private void addTyping(final String username) { 434 | runOnUiThread(new Runnable() { 435 | @Override 436 | public void run() { 437 | tvTyping.setText(username + " is typing"); 438 | llTyping.setVisibility(View.VISIBLE); 439 | } 440 | }); 441 | } 442 | 443 | private void removeTyping(String username) { 444 | runOnUiThread(new Runnable() { 445 | @Override 446 | public void run() { 447 | tvTyping.setText(""); 448 | llTyping.setVisibility(View.GONE); 449 | } 450 | }); 451 | } 452 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mayurrokade/chatapp/chat/ChatContract.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Mayur Rokade 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | package com.mayurrokade.chatapp.chat; 24 | 25 | import com.mayurrokade.chatapp.BasePresenter; 26 | import com.mayurrokade.chatapp.BaseView; 27 | import com.mayurrokade.chatapp.data.ChatMessage; 28 | import com.mayurrokade.chatapp.eventservice.EventListener; 29 | 30 | /** 31 | * This is a contract between chat view and chat presenter. 32 | * 33 | */ 34 | public interface ChatContract { 35 | 36 | interface View extends BaseView, EventListener { 37 | 38 | void onMessageDelivered(ChatMessage chatMessage); 39 | 40 | void updateUsername(String username); 41 | } 42 | 43 | interface Presenter extends BasePresenter, EventListener { 44 | 45 | void sendMessage(ChatMessage chatMessage); 46 | 47 | void changeUsername(String username); 48 | 49 | void onTyping(); 50 | 51 | void onStopTyping(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/mayurrokade/chatapp/chat/ChatMessagesAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Mayur Rokade 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | package com.mayurrokade.chatapp.chat; 24 | 25 | import android.content.Context; 26 | import android.support.annotation.NonNull; 27 | import android.support.v7.widget.RecyclerView; 28 | import android.view.LayoutInflater; 29 | import android.view.View; 30 | import android.view.ViewGroup; 31 | import android.widget.TextView; 32 | 33 | import com.mayurrokade.chatapp.R; 34 | import com.mayurrokade.chatapp.data.ChatMessage; 35 | 36 | import java.util.List; 37 | 38 | /** 39 | * ChatMessages adapter. 40 | * 41 | */ 42 | public class ChatMessagesAdapter extends RecyclerView.Adapter { 43 | 44 | private List mItems; 45 | private Context mContext; 46 | 47 | /** 48 | * Constructor to create a new ChatMessagesAdapter 49 | * 50 | * @param items 51 | * @param context 52 | */ 53 | public ChatMessagesAdapter(List items, Context context) { 54 | mItems = items; 55 | mContext = context; 56 | } 57 | 58 | @NonNull 59 | @Override 60 | public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 61 | RecyclerView.ViewHolder viewHolder; 62 | View view; 63 | 64 | if (viewType == ChatMessage.TYPE_MESSAGE_RECEIVED) { 65 | view = LayoutInflater.from(mContext) 66 | .inflate(R.layout.item_message_received, parent, false); 67 | viewHolder = new ReceivedMessageViewHolder(view); 68 | } else { 69 | view = LayoutInflater.from(mContext) 70 | .inflate(R.layout.item_message_sent, parent, false); 71 | viewHolder = new SentMessageViewHolder(view); 72 | } 73 | 74 | return viewHolder; 75 | } 76 | 77 | @Override 78 | public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { 79 | ChatMessage chatMessage = mItems.get(position); 80 | 81 | if (chatMessage.getType() == ChatMessage.TYPE_MESSAGE_RECEIVED) { 82 | ((ReceivedMessageViewHolder) holder).tvUsername.setText(chatMessage.getUsername()); 83 | ((ReceivedMessageViewHolder) holder).tvMessage.setText(chatMessage.getMessage()); 84 | } else { 85 | ((SentMessageViewHolder) holder).tvUsername.setText(chatMessage.getUsername()); 86 | ((SentMessageViewHolder) holder).tvMessage.setText(chatMessage.getMessage()); 87 | } 88 | } 89 | 90 | @Override 91 | public int getItemCount() { 92 | return mItems.size(); 93 | } 94 | 95 | @Override 96 | public int getItemViewType(int position) { 97 | return mItems.get(position).getType(); 98 | } 99 | 100 | /** 101 | * Use this method to add new chat message to to the RecyclerView. 102 | * 103 | * @param chatMessage 104 | */ 105 | public void addNewMessage(@NonNull ChatMessage chatMessage) { 106 | mItems.add(chatMessage); 107 | notifyItemInserted(mItems.size() - 1); 108 | } 109 | 110 | static class ReceivedMessageViewHolder extends RecyclerView.ViewHolder { 111 | TextView tvUsername, tvMessage; 112 | 113 | public ReceivedMessageViewHolder(View itemView) { 114 | super(itemView); 115 | tvUsername = itemView.findViewById(R.id.tvUsername); 116 | tvMessage = itemView.findViewById(R.id.tvMessage); 117 | } 118 | } 119 | 120 | static class SentMessageViewHolder extends RecyclerView.ViewHolder { 121 | TextView tvUsername, tvMessage; 122 | 123 | public SentMessageViewHolder(View itemView) { 124 | super(itemView); 125 | tvUsername = itemView.findViewById(R.id.tvUsername); 126 | tvMessage = itemView.findViewById(R.id.tvMessage); 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /app/src/main/java/com/mayurrokade/chatapp/chat/ChatPresenter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Mayur Rokade 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | package com.mayurrokade.chatapp.chat; 24 | 25 | import android.support.annotation.NonNull; 26 | 27 | import com.mayurrokade.chatapp.data.ChatMessage; 28 | import com.mayurrokade.chatapp.data.source.Repository; 29 | import com.mayurrokade.chatapp.eventservice.EventListener; 30 | import com.mayurrokade.chatapp.util.schedulers.BaseSchedulerProvider; 31 | 32 | import java.net.URISyntaxException; 33 | 34 | import io.reactivex.disposables.CompositeDisposable; 35 | import io.reactivex.disposables.Disposable; 36 | import io.reactivex.functions.Consumer; 37 | 38 | /** 39 | * Listens to user actions and sends data to remote data source. 40 | *

41 | * Presenter implements EventListener. Whenever the server sends events 42 | * to the Repository, it passes those events to the Presenter via EventListener. 43 | */ 44 | public class ChatPresenter implements ChatContract.Presenter { 45 | 46 | @NonNull 47 | private final BaseSchedulerProvider mSchedulerProvider; 48 | 49 | @NonNull 50 | private final CompositeDisposable mCompositeDisposable; 51 | 52 | @NonNull 53 | private final Repository mRepository; 54 | 55 | @NonNull 56 | private final ChatContract.View mView; 57 | 58 | @NonNull 59 | private final EventListener mViewEventListener; 60 | 61 | /** 62 | * Use this constructor to create a new ChatPresenter. 63 | * 64 | * @param view {@link ChatContract.View} 65 | * @param eventListener {@link EventListener} listens to server events. 66 | * @param schedulerProvider {@link BaseSchedulerProvider} 67 | * @param repository {@link Repository} 68 | */ 69 | public ChatPresenter(@NonNull ChatContract.View view, 70 | @NonNull EventListener eventListener, 71 | @NonNull BaseSchedulerProvider schedulerProvider, 72 | @NonNull Repository repository) { 73 | mView = view; 74 | mViewEventListener = eventListener; 75 | mSchedulerProvider = schedulerProvider; 76 | mRepository = repository; 77 | 78 | // Setting the view's eventListener in the repository so that 79 | // when server sends events to repository, it passes the 80 | // events to the view 81 | mRepository.setEventListener(this); 82 | 83 | mCompositeDisposable = new CompositeDisposable(); 84 | 85 | mView.setPresenter(this); 86 | mView.initView(); 87 | } 88 | 89 | @Override 90 | public void subscribe() { 91 | 92 | } 93 | 94 | @Override 95 | public void unsubscribe() { 96 | mCompositeDisposable.clear(); 97 | } 98 | 99 | @Override 100 | public void sendMessage(ChatMessage chatMessage) { 101 | Disposable disposable = 102 | mRepository.sendMessage(chatMessage) 103 | .subscribeOn(mSchedulerProvider.io()) 104 | .observeOn(mSchedulerProvider.ui()) 105 | .subscribe(new Consumer() { 106 | @Override 107 | public void accept(ChatMessage chatMessage) throws Exception { 108 | mView.onMessageDelivered(chatMessage); 109 | } 110 | }, new Consumer() { 111 | @Override 112 | public void accept(Throwable throwable) throws Exception { 113 | mView.showAlert(throwable.getMessage(), true); 114 | } 115 | }); 116 | 117 | mCompositeDisposable.add(disposable); 118 | } 119 | 120 | @Override 121 | public void changeUsername(String username) { 122 | try { 123 | mRepository.disconnect(); 124 | mRepository.connect(username); 125 | mView.updateUsername(username); 126 | mView.showAlert("Username set", false); 127 | } catch (URISyntaxException e) { 128 | mView.showAlert("Changing username failed", true); 129 | e.printStackTrace(); 130 | } 131 | } 132 | 133 | @Override 134 | public void onTyping() { 135 | mRepository.onTyping(); 136 | } 137 | 138 | @Override 139 | public void onStopTyping() { 140 | mRepository.onStopTyping(); 141 | } 142 | 143 | @Override 144 | public void onConnect(Object... args) { 145 | mViewEventListener.onConnect(args); 146 | } 147 | 148 | @Override 149 | public void onDisconnect(Object... args) { 150 | mViewEventListener.onDisconnect(args); 151 | } 152 | 153 | @Override 154 | public void onConnectError(Object... args) { 155 | mViewEventListener.onConnectError(args); 156 | } 157 | 158 | @Override 159 | public void onConnectTimeout(Object... args) { 160 | mViewEventListener.onConnectTimeout(args); 161 | } 162 | 163 | @Override 164 | public void onNewMessage(Object... args) { 165 | mViewEventListener.onNewMessage(args); 166 | } 167 | 168 | @Override 169 | public void onUserJoined(Object... args) { 170 | mViewEventListener.onUserJoined(args); 171 | } 172 | 173 | @Override 174 | public void onUserLeft(Object... args) { 175 | mViewEventListener.onUserLeft(args); 176 | } 177 | 178 | @Override 179 | public void onTyping(Object... args) { 180 | mViewEventListener.onTyping(args); 181 | } 182 | 183 | @Override 184 | public void onStopTyping(Object... args) { 185 | mViewEventListener.onStopTyping(args); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /app/src/main/java/com/mayurrokade/chatapp/data/ChatMessage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Mayur Rokade 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | package com.mayurrokade.chatapp.data; 24 | 25 | /** 26 | * ChatMessage model 27 | */ 28 | public class ChatMessage { 29 | public static final int TYPE_MESSAGE_SENT = 393; 30 | public static final int TYPE_MESSAGE_RECEIVED = 529; 31 | 32 | private String username; 33 | private String message; 34 | private int type; 35 | 36 | /** 37 | * Use this constructor to create a new ChatMessage. 38 | * 39 | * @param username Username of the user 40 | * @param message The text message user wants to send 41 | * @param type Type of message. Whether it's a SENT or RECEIVED message 42 | */ 43 | public ChatMessage(String username, String message, int type) { 44 | this.username = username; 45 | this.message = message; 46 | this.type = type; 47 | } 48 | 49 | /** 50 | * Get username from the chat message. 51 | * 52 | * @return username 53 | */ 54 | public String getUsername() { 55 | return username; 56 | } 57 | 58 | /** 59 | * Set username for the chat message. 60 | * 61 | * @param username 62 | */ 63 | public void setUsername(String username) { 64 | this.username = username; 65 | } 66 | 67 | /** 68 | * Get text message from the chat message. 69 | * 70 | * @return message 71 | */ 72 | public String getMessage() { 73 | return message; 74 | } 75 | 76 | /** 77 | * Set text message for the chat message. 78 | * 79 | * @param message 80 | */ 81 | public void setMessage(String message) { 82 | this.message = message; 83 | } 84 | 85 | /** 86 | * Get type of the chat message. 87 | * 88 | * @return type 89 | */ 90 | public int getType() { 91 | return type; 92 | } 93 | 94 | /** 95 | * Set type for the chat message. 96 | * 97 | * @param type 98 | */ 99 | public void setType(int type) { 100 | this.type = type; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /app/src/main/java/com/mayurrokade/chatapp/data/source/DataSource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Mayur Rokade 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | package com.mayurrokade.chatapp.data.source; 24 | 25 | import com.mayurrokade.chatapp.data.ChatMessage; 26 | import com.mayurrokade.chatapp.eventservice.EventListener; 27 | 28 | import java.net.URISyntaxException; 29 | 30 | import io.reactivex.Flowable; 31 | 32 | /** 33 | * Main interface for accessing data. It extends EventListener to receive 34 | * incoming events from a remote data source. In this case, a chat server. 35 | */ 36 | public interface DataSource extends EventListener { 37 | 38 | void setEventListener(EventListener eventListener); 39 | 40 | void connect(String username) throws URISyntaxException; 41 | 42 | void disconnect(); 43 | 44 | Flowable sendMessage(ChatMessage chatMessage); 45 | 46 | void onTyping(); 47 | 48 | void onStopTyping(); 49 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mayurrokade/chatapp/data/source/Repository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Mayur Rokade 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | package com.mayurrokade.chatapp.data.source; 24 | 25 | import android.support.annotation.NonNull; 26 | 27 | import com.mayurrokade.chatapp.data.ChatMessage; 28 | import com.mayurrokade.chatapp.eventservice.EventListener; 29 | 30 | import java.net.URISyntaxException; 31 | 32 | import io.reactivex.Flowable; 33 | 34 | /** 35 | * Repository can send events to a remote data source and can listen 36 | * for incoming events from remote data source as well. This bidirectional 37 | * flow of events is what makes the app realtime. 38 | * 39 | * Repository implements {@link DataSource} which can send and receive events. 40 | */ 41 | public class Repository implements DataSource { 42 | 43 | private static Repository INSTANCE = null; 44 | private final DataSource mRemoteDataSource; 45 | private final DataSource mLocalDataSource; 46 | private EventListener mPresenterEventListener; 47 | 48 | // Prevent direct instantiation 49 | private Repository(@NonNull DataSource remoteDataSource, 50 | @NonNull DataSource localDataSource) { 51 | mLocalDataSource = localDataSource; 52 | mRemoteDataSource = remoteDataSource; 53 | mRemoteDataSource.setEventListener(this); 54 | } 55 | 56 | /** 57 | * Returns single instance of this class, creating it if necessary. 58 | * 59 | * @param remoteDataSource 60 | * @param localDataSource 61 | * @return 62 | */ 63 | public static Repository getInstance(@NonNull DataSource remoteDataSource, 64 | @NonNull DataSource localDataSource) { 65 | if (INSTANCE == null) { 66 | INSTANCE = new Repository(remoteDataSource, localDataSource); 67 | } 68 | 69 | return INSTANCE; 70 | } 71 | 72 | @Override 73 | public void setEventListener(EventListener eventListener) { 74 | mPresenterEventListener = eventListener; 75 | } 76 | 77 | /** 78 | * Connect to remote chat server. 79 | * 80 | * @param username 81 | * @throws URISyntaxException 82 | */ 83 | @Override 84 | public void connect(String username) throws URISyntaxException { 85 | mRemoteDataSource.connect(username); 86 | } 87 | 88 | /** 89 | * Disconnect from remote chat server. 90 | * 91 | */ 92 | @Override 93 | public void disconnect() { 94 | mRemoteDataSource.disconnect(); 95 | } 96 | 97 | /** 98 | * Send chat message. 99 | * 100 | * @param chatMessage 101 | * @return 102 | */ 103 | @Override 104 | public Flowable sendMessage(ChatMessage chatMessage) { 105 | return mRemoteDataSource.sendMessage(chatMessage); 106 | } 107 | 108 | @Override 109 | public void onTyping() { 110 | mRemoteDataSource.onTyping(); 111 | } 112 | 113 | @Override 114 | public void onStopTyping() { 115 | mRemoteDataSource.onStopTyping(); 116 | } 117 | 118 | @Override 119 | public void onConnect(Object... args) { 120 | if (mPresenterEventListener != null) 121 | mPresenterEventListener.onConnect(args); 122 | } 123 | 124 | @Override 125 | public void onDisconnect(Object... args) { 126 | if (mPresenterEventListener != null) 127 | mPresenterEventListener.onDisconnect(args); 128 | } 129 | 130 | @Override 131 | public void onConnectError(Object... args) { 132 | if (mPresenterEventListener != null) 133 | mPresenterEventListener.onConnectError(args); 134 | } 135 | 136 | @Override 137 | public void onConnectTimeout(Object... args) { 138 | if (mPresenterEventListener != null) 139 | mPresenterEventListener.onConnectTimeout(args); 140 | } 141 | 142 | @Override 143 | public void onNewMessage(Object... args) { 144 | if (mPresenterEventListener != null) 145 | mPresenterEventListener.onNewMessage(args); 146 | } 147 | 148 | @Override 149 | public void onUserJoined(Object... args) { 150 | if (mPresenterEventListener != null) 151 | mPresenterEventListener.onUserJoined(args); 152 | } 153 | 154 | @Override 155 | public void onUserLeft(Object... args) { 156 | if (mPresenterEventListener != null) 157 | mPresenterEventListener.onUserLeft(args); 158 | } 159 | 160 | @Override 161 | public void onTyping(Object... args) { 162 | if (mPresenterEventListener != null) 163 | mPresenterEventListener.onTyping(args); 164 | } 165 | 166 | @Override 167 | public void onStopTyping(Object... args) { 168 | if (mPresenterEventListener != null) 169 | mPresenterEventListener.onStopTyping(args); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /app/src/main/java/com/mayurrokade/chatapp/data/source/local/LocalDataSource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Mayur Rokade 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | package com.mayurrokade.chatapp.data.source.local; 24 | 25 | import com.mayurrokade.chatapp.data.ChatMessage; 26 | import com.mayurrokade.chatapp.data.source.DataSource; 27 | import com.mayurrokade.chatapp.eventservice.EventListener; 28 | 29 | import java.net.URISyntaxException; 30 | 31 | import io.reactivex.Flowable; 32 | 33 | /** 34 | * Local data source. 35 | */ 36 | public class LocalDataSource implements DataSource { 37 | 38 | private static LocalDataSource INSTANCE; 39 | 40 | public static LocalDataSource getInstance() { 41 | if (INSTANCE == null) { 42 | INSTANCE = new LocalDataSource(); 43 | } 44 | 45 | return INSTANCE; 46 | } 47 | 48 | @Override 49 | public void onConnect(Object... args) { 50 | 51 | } 52 | 53 | @Override 54 | public void onDisconnect(Object... args) { 55 | 56 | } 57 | 58 | @Override 59 | public void onConnectError(Object... args) { 60 | 61 | } 62 | 63 | @Override 64 | public void onConnectTimeout(Object... args) { 65 | 66 | } 67 | 68 | @Override 69 | public void onNewMessage(Object... args) { 70 | 71 | } 72 | 73 | @Override 74 | public void onUserJoined(Object... args) { 75 | 76 | } 77 | 78 | @Override 79 | public void onUserLeft(Object... args) { 80 | 81 | } 82 | 83 | @Override 84 | public void onTyping(Object... args) { 85 | 86 | } 87 | 88 | @Override 89 | public void onStopTyping(Object... args) { 90 | 91 | } 92 | 93 | @Override 94 | public void setEventListener(EventListener eventListener) { 95 | 96 | } 97 | 98 | @Override 99 | public Flowable sendMessage(ChatMessage chatMessage) { 100 | return null; 101 | } 102 | 103 | @Override 104 | public void onTyping() { 105 | 106 | } 107 | 108 | @Override 109 | public void onStopTyping() { 110 | 111 | } 112 | 113 | @Override 114 | public void connect(String username) throws URISyntaxException { 115 | 116 | } 117 | 118 | @Override 119 | public void disconnect() { 120 | 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /app/src/main/java/com/mayurrokade/chatapp/data/source/remote/RemoteDataSource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Mayur Rokade 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | package com.mayurrokade.chatapp.data.source.remote; 24 | 25 | import com.mayurrokade.chatapp.data.ChatMessage; 26 | import com.mayurrokade.chatapp.data.source.DataSource; 27 | import com.mayurrokade.chatapp.eventservice.EventListener; 28 | import com.mayurrokade.chatapp.eventservice.EventService; 29 | import com.mayurrokade.chatapp.eventservice.EventServiceImpl; 30 | 31 | import java.net.URISyntaxException; 32 | 33 | import io.reactivex.Flowable; 34 | 35 | /** 36 | * Remote data source. 37 | * 38 | */ 39 | public class RemoteDataSource implements DataSource { 40 | 41 | private static RemoteDataSource INSTANCE; 42 | private static EventService mEventService = EventServiceImpl.getInstance(); 43 | private EventListener mRepoEventListener; 44 | 45 | private RemoteDataSource() { 46 | mEventService.setEventListener(this); 47 | } 48 | 49 | public static RemoteDataSource getInstance() { 50 | if (INSTANCE == null) { 51 | INSTANCE = new RemoteDataSource(); 52 | } 53 | 54 | return INSTANCE; 55 | } 56 | 57 | @Override 58 | public void onConnect(Object... args) { 59 | if (mRepoEventListener != null) 60 | mRepoEventListener.onConnect(args); 61 | } 62 | 63 | @Override 64 | public void onDisconnect(Object... args) { 65 | if (mRepoEventListener != null) 66 | mRepoEventListener.onDisconnect(args); 67 | } 68 | 69 | @Override 70 | public void onConnectError(Object... args) { 71 | if (mRepoEventListener != null) 72 | mRepoEventListener.onConnectError(args); 73 | } 74 | 75 | @Override 76 | public void onConnectTimeout(Object... args) { 77 | if (mRepoEventListener != null) 78 | mRepoEventListener.onConnectTimeout(args); 79 | } 80 | 81 | @Override 82 | public void onNewMessage(Object... args) { 83 | if (mRepoEventListener != null) 84 | mRepoEventListener.onNewMessage(args); 85 | } 86 | 87 | @Override 88 | public void onUserJoined(Object... args) { 89 | if (mRepoEventListener != null) 90 | mRepoEventListener.onUserJoined(args); 91 | } 92 | 93 | @Override 94 | public void onUserLeft(Object... args) { 95 | if (mRepoEventListener != null) 96 | mRepoEventListener.onUserLeft(args); 97 | } 98 | 99 | @Override 100 | public void onTyping(Object... args) { 101 | if (mRepoEventListener != null) 102 | mRepoEventListener.onTyping(args); 103 | } 104 | 105 | @Override 106 | public void onStopTyping(Object... args) { 107 | if (mRepoEventListener != null) 108 | mRepoEventListener.onStopTyping(args); 109 | } 110 | 111 | @Override 112 | public void setEventListener(EventListener eventListener) { 113 | mRepoEventListener = eventListener; 114 | } 115 | 116 | @Override 117 | public Flowable sendMessage(ChatMessage chatMessage) { 118 | return mEventService.sendMessage(chatMessage); 119 | } 120 | 121 | @Override 122 | public void onTyping() { 123 | mEventService.onTyping(); 124 | } 125 | 126 | @Override 127 | public void onStopTyping() { 128 | mEventService.onStopTyping(); 129 | } 130 | 131 | @Override 132 | public void connect(String username) throws URISyntaxException { 133 | mEventService.connect(username); 134 | } 135 | 136 | @Override 137 | public void disconnect() { 138 | mEventService.disconnect(); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /app/src/main/java/com/mayurrokade/chatapp/eventservice/EventListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Mayur Rokade 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | package com.mayurrokade.chatapp.eventservice; 24 | 25 | /** 26 | * Main interface to listen to server events. 27 | * 28 | */ 29 | public interface EventListener { 30 | 31 | void onConnect(Object... args); 32 | 33 | void onDisconnect(Object... args); 34 | 35 | void onConnectError(Object... args); 36 | 37 | void onConnectTimeout(Object... args); 38 | 39 | void onNewMessage(Object... args); 40 | 41 | void onUserJoined(Object... args); 42 | 43 | void onUserLeft(Object... args); 44 | 45 | void onTyping(Object... args); 46 | 47 | void onStopTyping(Object... args); 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/mayurrokade/chatapp/eventservice/EventService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Mayur Rokade 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | package com.mayurrokade.chatapp.eventservice; 24 | 25 | import com.mayurrokade.chatapp.data.ChatMessage; 26 | 27 | import java.net.URISyntaxException; 28 | 29 | import io.reactivex.Flowable; 30 | 31 | /** 32 | * Service layer that connects/disconnects to the server and 33 | * sends and receives events too. 34 | */ 35 | public interface EventService { 36 | 37 | void connect(String username) throws URISyntaxException; 38 | 39 | void disconnect(); 40 | 41 | void setEventListener(EventListener listener); 42 | 43 | Flowable sendMessage(ChatMessage chatMessage); 44 | 45 | void onTyping(); 46 | 47 | void onStopTyping(); 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/mayurrokade/chatapp/eventservice/EventServiceImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Mayur Rokade 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | package com.mayurrokade.chatapp.eventservice; 24 | 25 | import android.support.annotation.NonNull; 26 | import android.util.Log; 27 | 28 | import com.mayurrokade.chatapp.data.ChatMessage; 29 | 30 | import java.net.URISyntaxException; 31 | 32 | import io.reactivex.BackpressureStrategy; 33 | import io.reactivex.Flowable; 34 | import io.reactivex.FlowableEmitter; 35 | import io.reactivex.FlowableOnSubscribe; 36 | import io.socket.client.IO; 37 | import io.socket.client.Socket; 38 | import io.socket.emitter.Emitter; 39 | 40 | /** 41 | * Implementation of {@link EventService} which connects and disconnects to the server. 42 | * It also sends and receives events from the server. 43 | */ 44 | public class EventServiceImpl implements EventService { 45 | 46 | private static final String TAG = EventServiceImpl.class.getSimpleName(); 47 | private static final String SOCKET_URL = "https://socket-io-chat.now.sh"; 48 | private static final String EVENT_CONNECT = Socket.EVENT_CONNECT; 49 | private static final String EVENT_DISCONNECT = Socket.EVENT_DISCONNECT; 50 | private static final String EVENT_CONNECT_ERROR = Socket.EVENT_CONNECT_ERROR; 51 | private static final String EVENT_CONNECT_TIMEOUT = Socket.EVENT_CONNECT_TIMEOUT; 52 | private static final String EVENT_NEW_MESSAGE = "new message"; 53 | private static final String EVENT_USER_JOINED = "user joined"; 54 | private static final String EVENT_USER_LEFT = "user left"; 55 | private static final String EVENT_TYPING = "typing"; 56 | private static final String EVENT_STOP_TYPING = "stop typing"; 57 | private static EventService INSTANCE; 58 | private static EventListener mEventListener; 59 | private static Socket mSocket; 60 | private String mUsername; 61 | 62 | // Prevent direct instantiation 63 | private EventServiceImpl() {} 64 | 65 | /** 66 | * Returns single instance of this class, creating it if necessary. 67 | * 68 | * @return 69 | */ 70 | public static EventService getInstance() { 71 | if (INSTANCE == null) { 72 | INSTANCE = new EventServiceImpl(); 73 | } 74 | 75 | return INSTANCE; 76 | } 77 | 78 | /** 79 | * Connect to the server. 80 | * 81 | * @param username 82 | * @throws URISyntaxException 83 | */ 84 | @Override 85 | public void connect(String username) throws URISyntaxException { 86 | mUsername = username; 87 | mSocket = IO.socket(SOCKET_URL); 88 | 89 | // Register the incoming events and their listeners 90 | // on the socket. 91 | mSocket.on(EVENT_CONNECT, onConnect); 92 | mSocket.on(EVENT_DISCONNECT, onDisconnect); 93 | mSocket.on(EVENT_CONNECT_ERROR, onConnectError); 94 | mSocket.on(EVENT_CONNECT_TIMEOUT, onConnectError); 95 | mSocket.on(EVENT_NEW_MESSAGE, onNewMessage); 96 | mSocket.on(EVENT_USER_JOINED, onUserJoined); 97 | mSocket.on(EVENT_USER_LEFT, onUserLeft); 98 | mSocket.on(EVENT_TYPING, onTyping); 99 | mSocket.on(EVENT_STOP_TYPING, onStopTyping); 100 | 101 | mSocket.connect(); 102 | } 103 | 104 | /** 105 | * Disconnect from the server. 106 | * 107 | */ 108 | @Override 109 | public void disconnect() { 110 | if (mSocket != null) mSocket.disconnect(); 111 | } 112 | 113 | /** 114 | * Send chat message to the server. 115 | * 116 | * @param chatMessage 117 | * @return 118 | */ 119 | @Override 120 | public Flowable sendMessage(@NonNull final ChatMessage chatMessage) { 121 | return Flowable.create(new FlowableOnSubscribe() { 122 | @Override 123 | public void subscribe(FlowableEmitter emitter) throws Exception { 124 | /* 125 | * Socket.io supports acking messages. 126 | * This feature can be used as 127 | * mSocket.emit("EVENT_NEW_MESSAGE", chatMessage.getMessage(), new Ack() { 128 | * @Override 129 | * public void call(Object... args) { 130 | * // Do something with args 131 | * 132 | * // On success 133 | * emitter.onNext(chatMessage); 134 | * 135 | * // On error 136 | * emitter.onError(new Exception("Sending message failed.")); 137 | * } 138 | * }); 139 | * 140 | * */ 141 | 142 | mSocket.emit(EVENT_NEW_MESSAGE, chatMessage.getMessage()); 143 | emitter.onNext(chatMessage); 144 | } 145 | }, BackpressureStrategy.BUFFER); 146 | } 147 | 148 | /** 149 | * Send typing event to the server. 150 | * 151 | */ 152 | @Override 153 | public void onTyping() { 154 | mSocket.emit(EVENT_TYPING); 155 | } 156 | 157 | /** 158 | * Send stop typing event to the server. 159 | * 160 | */ 161 | @Override 162 | public void onStopTyping() { 163 | mSocket.emit(EVENT_STOP_TYPING); 164 | } 165 | 166 | /** 167 | * Set eventListener. 168 | * 169 | * When server sends events to the socket, those events are passed to the 170 | * RemoteDataSource -> Repository -> Presenter -> View using EventListener. 171 | * 172 | * @param eventListener 173 | */ 174 | @Override 175 | public void setEventListener(EventListener eventListener) { 176 | mEventListener = eventListener; 177 | } 178 | 179 | private Emitter.Listener onConnect = new Emitter.Listener() { 180 | @Override 181 | public void call(Object... args) { 182 | Log.i(TAG, "call: onConnect"); 183 | mSocket.emit("add user", mUsername); 184 | if (mEventListener != null) mEventListener.onConnect(args); 185 | } 186 | }; 187 | 188 | private Emitter.Listener onDisconnect = new Emitter.Listener() { 189 | @Override 190 | public void call(Object... args) { 191 | Log.i(TAG, "call: onDisconnect"); 192 | if (mEventListener != null) mEventListener.onDisconnect(args); 193 | } 194 | }; 195 | 196 | private Emitter.Listener onConnectError = new Emitter.Listener() { 197 | @Override 198 | public void call(Object... args) { 199 | Log.i(TAG, "call: onConnectError"); 200 | if (mEventListener != null) mEventListener.onConnectError(args); 201 | } 202 | }; 203 | 204 | private Emitter.Listener onNewMessage = new Emitter.Listener() { 205 | @Override 206 | public void call(final Object... args) { 207 | Log.i(TAG, "call: onNewMessage"); 208 | if (mEventListener != null) mEventListener.onNewMessage(args); 209 | } 210 | }; 211 | 212 | private Emitter.Listener onUserJoined = new Emitter.Listener() { 213 | @Override 214 | public void call(final Object... args) { 215 | Log.i(TAG, "call: onNewMessage"); 216 | if (mEventListener != null) mEventListener.onUserJoined(args); 217 | } 218 | }; 219 | 220 | private Emitter.Listener onUserLeft = new Emitter.Listener() { 221 | @Override 222 | public void call(final Object... args) { 223 | Log.i(TAG, "call: onNewMessage"); 224 | if (mEventListener != null) mEventListener.onUserLeft(args); 225 | } 226 | }; 227 | 228 | private Emitter.Listener onTyping = new Emitter.Listener() { 229 | @Override 230 | public void call(final Object... args) { 231 | Log.i(TAG, "call: onNewMessage"); 232 | if (mEventListener != null) mEventListener.onTyping(args); 233 | } 234 | }; 235 | 236 | private Emitter.Listener onStopTyping = new Emitter.Listener() { 237 | @Override 238 | public void call(final Object... args) { 239 | Log.i(TAG, "call: onNewMessage"); 240 | if (mEventListener != null) mEventListener.onStopTyping(args); 241 | } 242 | }; 243 | } 244 | -------------------------------------------------------------------------------- /app/src/main/java/com/mayurrokade/chatapp/util/AppLifeCycleObserver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Mayur Rokade 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | package com.mayurrokade.chatapp.util; 24 | 25 | import android.arch.lifecycle.Lifecycle; 26 | import android.arch.lifecycle.LifecycleObserver; 27 | import android.arch.lifecycle.OnLifecycleEvent; 28 | import android.content.Context; 29 | import android.widget.Toast; 30 | 31 | import com.mayurrokade.chatapp.eventservice.EventServiceImpl; 32 | 33 | import java.net.URISyntaxException; 34 | 35 | /** 36 | * Closes the socket connection when app is in background and 37 | * connects to socket when the app is in foreground. 38 | */ 39 | public class AppLifeCycleObserver implements LifecycleObserver { 40 | 41 | private Context mContext; 42 | 43 | /** 44 | * Use this constructor to create a new AppLifeCycleObserver 45 | * 46 | * @param context 47 | */ 48 | public AppLifeCycleObserver(Context context) { 49 | mContext = context; 50 | } 51 | 52 | /** 53 | * When app enters foreground 54 | */ 55 | @OnLifecycleEvent(Lifecycle.Event.ON_START) 56 | public void onEnterForeground() { 57 | try { 58 | EventServiceImpl.getInstance().connect(User.getUsername()); 59 | } catch (URISyntaxException e) { 60 | Toast.makeText(mContext, "Failed to connect to chat server.", Toast.LENGTH_LONG).show(); 61 | e.printStackTrace(); 62 | } 63 | } 64 | 65 | /** 66 | * When app enters background 67 | */ 68 | @OnLifecycleEvent(Lifecycle.Event.ON_STOP) 69 | public void onEnterBackground() { 70 | EventServiceImpl.getInstance().disconnect(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/src/main/java/com/mayurrokade/chatapp/util/Injection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Mayur Rokade 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | package com.mayurrokade.chatapp.util; 24 | 25 | import android.content.Context; 26 | 27 | import com.mayurrokade.chatapp.data.source.Repository; 28 | import com.mayurrokade.chatapp.data.source.local.LocalDataSource; 29 | import com.mayurrokade.chatapp.data.source.remote.RemoteDataSource; 30 | import com.mayurrokade.chatapp.util.schedulers.BaseSchedulerProvider; 31 | import com.mayurrokade.chatapp.util.schedulers.SchedulerProvider; 32 | 33 | public class Injection { 34 | public static Repository providesRepository(Context context) { 35 | return Repository.getInstance(RemoteDataSource.getInstance(), 36 | LocalDataSource.getInstance()); 37 | } 38 | 39 | public static BaseSchedulerProvider provideSchedulerProvider() { 40 | return SchedulerProvider.getInstance(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/mayurrokade/chatapp/util/TextUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Mayur Rokade 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | package com.mayurrokade.chatapp.util; 24 | 25 | public class TextUtils { 26 | 27 | /** 28 | * @param str - Check if the string is not null or empty. 29 | * @return boolean - Returns true if the string is valid. 30 | */ 31 | public static boolean isValidString(String str) { 32 | if (str != null 33 | && str.length() > 0 34 | && !str.isEmpty() 35 | && !str.equalsIgnoreCase("")) { 36 | return true; 37 | } 38 | 39 | return false; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/mayurrokade/chatapp/util/User.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Mayur Rokade 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | package com.mayurrokade.chatapp.util; 24 | 25 | import java.util.UUID; 26 | 27 | /** 28 | * Utility to get and set username. 29 | */ 30 | public class User { 31 | 32 | public static final String USER_SUFFIX = "_User"; 33 | private static boolean isUpdated = false; 34 | 35 | // Set random username 36 | private static String USER_NAME 37 | = UUID.randomUUID().toString().substring(0,5) + USER_SUFFIX; 38 | 39 | /** 40 | * Get username 41 | * 42 | * @return String username 43 | */ 44 | public static String getUsername() { 45 | return USER_NAME; 46 | } 47 | 48 | /** 49 | * Set username 50 | * 51 | * @param String username 52 | */ 53 | public static void setUsername(String username) { 54 | USER_NAME = username; 55 | isUpdated = true; 56 | } 57 | 58 | /** 59 | * Check if the username has been updated by the user 60 | * 61 | * @return boolean isUsernameUpdated 62 | */ 63 | public static boolean isUsernameUpdated() { 64 | return isUpdated; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/mayurrokade/chatapp/util/schedulers/BaseSchedulerProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Mayur Rokade 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | package com.mayurrokade.chatapp.util.schedulers; 24 | 25 | import android.support.annotation.NonNull; 26 | 27 | import io.reactivex.Scheduler; 28 | 29 | public interface BaseSchedulerProvider { 30 | 31 | @NonNull 32 | Scheduler computation(); 33 | 34 | @NonNull 35 | Scheduler io(); 36 | 37 | @NonNull 38 | Scheduler ui(); 39 | 40 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mayurrokade/chatapp/util/schedulers/SchedulerProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Mayur Rokade 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | package com.mayurrokade.chatapp.util.schedulers; 24 | 25 | import android.support.annotation.NonNull; 26 | import android.support.annotation.Nullable; 27 | 28 | import io.reactivex.Scheduler; 29 | import io.reactivex.android.schedulers.AndroidSchedulers; 30 | import io.reactivex.schedulers.Schedulers; 31 | 32 | public class SchedulerProvider implements BaseSchedulerProvider { 33 | 34 | @Nullable 35 | private static SchedulerProvider INSTANCE; 36 | 37 | // Prevent direct instantiation. 38 | private SchedulerProvider() { 39 | } 40 | 41 | public static synchronized SchedulerProvider getInstance() { 42 | if (INSTANCE == null) { 43 | INSTANCE = new SchedulerProvider(); 44 | } 45 | return INSTANCE; 46 | } 47 | 48 | 49 | @NonNull 50 | @Override 51 | public Scheduler computation() { 52 | return Schedulers.computation(); 53 | } 54 | 55 | @NonNull 56 | @Override 57 | public Scheduler io() { 58 | return Schedulers.io(); 59 | } 60 | 61 | @NonNull 62 | @Override 63 | public Scheduler ui() { 64 | return AndroidSchedulers.mainThread(); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_account.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayuroks/android-mvp-realtime-chat/188f9df083f2e0ecfab2c441782913723e9a2fc1/app/src/main/res/drawable-hdpi/ic_account.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayuroks/android-mvp-realtime-chat/188f9df083f2e0ecfab2c441782913723e9a2fc1/app/src/main/res/drawable-hdpi/ic_back.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayuroks/android-mvp-realtime-chat/188f9df083f2e0ecfab2c441782913723e9a2fc1/app/src/main/res/drawable-hdpi/ic_edit.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayuroks/android-mvp-realtime-chat/188f9df083f2e0ecfab2c441782913723e9a2fc1/app/src/main/res/drawable-hdpi/ic_info.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_logo_no_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayuroks/android-mvp-realtime-chat/188f9df083f2e0ecfab2c441782913723e9a2fc1/app/src/main/res/drawable-hdpi/ic_logo_no_background.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_send.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayuroks/android-mvp-realtime-chat/188f9df083f2e0ecfab2c441782913723e9a2fc1/app/src/main/res/drawable-hdpi/ic_send.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_account.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayuroks/android-mvp-realtime-chat/188f9df083f2e0ecfab2c441782913723e9a2fc1/app/src/main/res/drawable-mdpi/ic_account.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayuroks/android-mvp-realtime-chat/188f9df083f2e0ecfab2c441782913723e9a2fc1/app/src/main/res/drawable-mdpi/ic_back.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayuroks/android-mvp-realtime-chat/188f9df083f2e0ecfab2c441782913723e9a2fc1/app/src/main/res/drawable-mdpi/ic_edit.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayuroks/android-mvp-realtime-chat/188f9df083f2e0ecfab2c441782913723e9a2fc1/app/src/main/res/drawable-mdpi/ic_info.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_logo_no_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayuroks/android-mvp-realtime-chat/188f9df083f2e0ecfab2c441782913723e9a2fc1/app/src/main/res/drawable-mdpi/ic_logo_no_background.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_send.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayuroks/android-mvp-realtime-chat/188f9df083f2e0ecfab2c441782913723e9a2fc1/app/src/main/res/drawable-mdpi/ic_send.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_account.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayuroks/android-mvp-realtime-chat/188f9df083f2e0ecfab2c441782913723e9a2fc1/app/src/main/res/drawable-xhdpi/ic_account.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayuroks/android-mvp-realtime-chat/188f9df083f2e0ecfab2c441782913723e9a2fc1/app/src/main/res/drawable-xhdpi/ic_back.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayuroks/android-mvp-realtime-chat/188f9df083f2e0ecfab2c441782913723e9a2fc1/app/src/main/res/drawable-xhdpi/ic_edit.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayuroks/android-mvp-realtime-chat/188f9df083f2e0ecfab2c441782913723e9a2fc1/app/src/main/res/drawable-xhdpi/ic_info.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_logo_no_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayuroks/android-mvp-realtime-chat/188f9df083f2e0ecfab2c441782913723e9a2fc1/app/src/main/res/drawable-xhdpi/ic_logo_no_background.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_send.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayuroks/android-mvp-realtime-chat/188f9df083f2e0ecfab2c441782913723e9a2fc1/app/src/main/res/drawable-xhdpi/ic_send.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_account.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayuroks/android-mvp-realtime-chat/188f9df083f2e0ecfab2c441782913723e9a2fc1/app/src/main/res/drawable-xxhdpi/ic_account.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayuroks/android-mvp-realtime-chat/188f9df083f2e0ecfab2c441782913723e9a2fc1/app/src/main/res/drawable-xxhdpi/ic_back.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayuroks/android-mvp-realtime-chat/188f9df083f2e0ecfab2c441782913723e9a2fc1/app/src/main/res/drawable-xxhdpi/ic_edit.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayuroks/android-mvp-realtime-chat/188f9df083f2e0ecfab2c441782913723e9a2fc1/app/src/main/res/drawable-xxhdpi/ic_info.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_logo_no_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayuroks/android-mvp-realtime-chat/188f9df083f2e0ecfab2c441782913723e9a2fc1/app/src/main/res/drawable-xxhdpi/ic_logo_no_background.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_send.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayuroks/android-mvp-realtime-chat/188f9df083f2e0ecfab2c441782913723e9a2fc1/app/src/main/res/drawable-xxhdpi/ic_send.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/about_icon_copy_right.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_logo_no_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayuroks/android-mvp-realtime-chat/188f9df083f2e0ecfab2c441782913723e9a2fc1/app/src/main/res/drawable-xxxhdpi/ic_logo_no_background.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_received_message.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_rounded_border.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_rounded_filled.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_sent_message.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_linkedin_logo.xml: -------------------------------------------------------------------------------- 1 | 22 | 23 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_medium_logo.xml: -------------------------------------------------------------------------------- 1 | 22 | 23 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/profile_image_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayuroks/android-mvp-realtime-chat/188f9df083f2e0ecfab2c441782913723e9a2fc1/app/src/main/res/drawable/profile_image_round.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_about.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_chat.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 24 | 25 | 32 | 33 | 43 | 44 | 51 | 52 | 57 | 58 | 59 | 72 | 73 | 81 | 82 | 93 | 94 | 95 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_set_username.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 15 | 16 | 28 | 29 | 37 | 38 |