├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── release │ └── output.json └── src │ ├── androidTest │ └── java │ │ └── io │ │ └── github │ │ └── veeshostak │ │ └── aichat │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher_fiona-web.png │ ├── java │ │ └── io │ │ │ └── github │ │ │ └── veeshostak │ │ │ └── aichat │ │ │ ├── activities │ │ │ ├── ChatListActivity.java │ │ │ ├── SignInActivity.java │ │ │ └── TermsOfService.java │ │ │ ├── aws │ │ │ ├── cognito │ │ │ │ └── CognitoHelper.java │ │ │ └── dynamodb │ │ │ │ ├── DynamoDBClientAndMapper.java │ │ │ │ └── model │ │ │ │ ├── User.java │ │ │ │ └── UserConversation.java │ │ │ ├── database │ │ │ ├── AppDatabase.java │ │ │ ├── dao │ │ │ │ └── ChatPostDao.java │ │ │ └── entity │ │ │ │ └── ChatPost.java │ │ │ ├── models │ │ │ └── ChatMessage.java │ │ │ ├── recyclerview │ │ │ ├── adapter │ │ │ │ └── MessageListAdapter.java │ │ │ └── viewholder │ │ │ │ ├── ReceivedMessageHolder.java │ │ │ │ └── SentMessageHolder.java │ │ │ ├── utils │ │ │ └── Installation.java │ │ │ └── viewmodels │ │ │ ├── AddChatPostsViewModel.java │ │ │ ├── DeleteAllChatPostsViewModel.java │ │ │ ├── ListAllChatPostsViewModel.java │ │ │ ├── ListNotInRemoteChatPostsViewModel.java │ │ │ └── UpdateChatPostsViewModel.java │ └── res │ │ ├── drawable │ │ ├── circle.xml │ │ ├── fiona_chat_icon.png │ │ ├── in_message_bg.9.png │ │ └── out_message_bg.9.png │ │ ├── layout │ │ ├── activity_chat_list.xml │ │ ├── activity_sign_in.xml │ │ ├── activity_terms_of_service.xml │ │ ├── item_message_recieved.xml │ │ ├── item_message_sent.xml │ │ └── list_item_chat_message.xml │ │ ├── log4j2.component.properties │ │ ├── menu │ │ ├── menu_chat.xml │ │ └── menu_main.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_fiona.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_fiona.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_fiona.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_fiona.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_fiona.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── io │ └── github │ └── veeshostak │ └── aichat │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenshots ├── Nexus-6P-1.png └── Nexus-6P-2.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .externalNativeBuild 2 | 3 | # Built application files 4 | *.apk 5 | *.ap_ 6 | 7 | # release 8 | /signing.properties 9 | *.jks 10 | 11 | # keys 12 | /app/src/main/res/values/secrets.xml 13 | 14 | # Generated files 15 | bin/ 16 | gen/ 17 | app/extra/ 18 | *.iml 19 | 20 | # Files for the Dalvik VM 21 | *.dex 22 | 23 | # F-Droid files 24 | fdroid/ 25 | app/libs/ 26 | 27 | # Gradle files 28 | .gradle/ 29 | build/ 30 | /*/build/ 31 | 32 | # Local configuration file (sdk path, etc) 33 | local.properties 34 | 35 | # Log Files 36 | *.log 37 | 38 | # Java class files 39 | *.class 40 | 41 | # Proguard folder generated by Eclipse 42 | proguard/ 43 | 44 | # Others 45 | /.idea/workspace.xml 46 | /.idea/libraries 47 | .DS_Store 48 | /captures 49 | .idea 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Vlad Shostak 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 | # android-aws-architecture-components-dialogflow-chatbot 2 | 3 | Android chatbot using: 4 | 5 | * ***Dialogflow (Api.ai)*** for machine learning & NLP 6 | 7 | * ***AWS DynamoDB NoSQL database*** for storage with ***DynamoDB Object Mapper*** (lets us map client-side classes to DynamoDB tables, perform CRUD operations, and execute queries) 8 | 9 | * ***AWS Cognito Identity*** to create unique identities for users and authenticate them for secure access to DynamoDB 10 | 11 | * ***Android Architecture Components:*** 12 | 13 | * ***Room Persistence Library*** (provides an abstraction layer over SQLite) (compile-time verification of raw SQL queries, supports observable queries which return LiveData objects) 14 | 15 | * ***ViewModel*** (exposes the required data and interested parties can listen to it) (ViewModels do not contain code related to the UI. This helps in the decoupling of our app components) (also allows data to survive configuration changes such as screen rotations. 16 | ) 17 | 18 | * ***LiveData*** (an observable data holder class) (it's lifecycle-aware, ensuring LiveData only updates app component observers that are in an active lifecycle state.) 19 | 20 | 21 | # Fiona AI Chat 22 | 23 | 24 | Keep yourself entertained! Talk about anything with Fiona, as she grows smarter with every interaction.
25 | 26 |
27 | 28 | Get it on Google Play 31 | 32 | 33 |
34 |

35 | 36 | 37 | 38 | ## Screenshots 39 | 40 |
41 | 42 | 43 | 44 |
45 | 46 | #### Contributing 47 | 48 | ###### Code & Issues 49 | If you wish to contribute to the app please fork the project 50 | and submit a pull request. If possible make the PR on the [develop branch](https://github.com/VeeShostak/android-aws-architecture-components-dialogflow-chatbot/tree/develop). 51 | 52 | You can trace the status of known issues on [Github Issues](https://github.com/VeeShostak/android-aws-architecture-components-dialogflow-chatbot/issues), 53 | 54 | #### Licensing 55 | Fiona AI Chat is licensed under the [MIT License](https://github.com/VeeShostak/android-aws-architecture-components-dialogflow-chatbot/blob/master/LICENSE). 56 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | packagingOptions { 5 | exclude 'META-INF/DEPENDENCIES.txt' 6 | exclude 'META-INF/LICENSE.txt' 7 | exclude 'META-INF/NOTICE.txt' 8 | exclude 'META-INF/NOTICE' 9 | exclude 'META-INF/LICENSE' 10 | exclude 'META-INF/DEPENDENCIES' 11 | exclude 'META-INF/notice.txt' 12 | exclude 'META-INF/license.txt' 13 | exclude 'META-INF/dependencies.txt' 14 | exclude 'META-INF/LGPL2.1' 15 | } 16 | compileSdkVersion 26 17 | buildToolsVersion "26.0.2" 18 | defaultConfig { 19 | applicationId "io.github.veeshostak.aichat" 20 | minSdkVersion 21 21 | targetSdkVersion 26 22 | versionCode 2 23 | versionName "2.0" 24 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 25 | javaCompileOptions { 26 | annotationProcessorOptions { 27 | includeCompileClasspath true 28 | } 29 | } 30 | } 31 | buildTypes { 32 | release { 33 | minifyEnabled false 34 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 35 | } 36 | } 37 | productFlavors { 38 | } 39 | } 40 | 41 | dependencies { 42 | 43 | // todo: replace compile with "implementation" or "api" accordingly 44 | 45 | compile fileTree(include: ['*.jar'], dir: 'libs') 46 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 47 | exclude group: 'com.android.support', module: 'support-annotations' 48 | }) 49 | compile 'com.android.support:appcompat-v7:26.1.0' 50 | testCompile 'junit:junit:4.12' 51 | 52 | // Dialog Flow (Api.ai) API 53 | //compile 'ai.api:libai:1.4.8' 54 | compile('ai.api:libai:1.4.8') { 55 | exclude module: 'log4j-core' 56 | } 57 | compile 'ai.api:sdk:2.0.7@aar' 58 | 59 | // other 60 | compile 'com.google.code.gson:gson:2.8.1' 61 | compile 'commons-io:commons-io:20030203.000550' 62 | 63 | // RecyclerView 64 | compile 'com.android.support:recyclerview-v7:26.1.0' 65 | 66 | // constraint layout 67 | compile 'com.android.support.constraint:constraint-layout:1.0.2' 68 | 69 | // START Architecture Components 70 | // 1)Manage our UI components lifecycle 2)Persist data over configuration changes 71 | 72 | // Room Persistence Library (abstraction layer over SQLite). 73 | compile 'android.arch.persistence.room:runtime:1.0.0' 74 | annotationProcessor 'android.arch.persistence.room:compiler:1.0.0' 75 | 76 | // livedata, viewmodels 77 | compile "android.arch.lifecycle:extensions:1.0.0" 78 | annotationProcessor "android.arch.lifecycle:compiler:1.0.0" 79 | // END Architecture Components 80 | 81 | 82 | // aws 83 | compile 'com.amazonaws:aws-android-sdk-core:2.2.22' 84 | compile 'com.amazonaws:aws-android-sdk-s3:2.2.22' 85 | compile 'com.amazonaws:aws-android-sdk-ddb:2.2.22' 86 | compile 'com.amazonaws:aws-android-sdk-ddb-mapper:2.2.22' 87 | 88 | compile 'com.amazonaws:aws-android-sdk-cognitoidentityprovider:2.6.8' 89 | 90 | 91 | 92 | } 93 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/vladshostak/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/release/output.json: -------------------------------------------------------------------------------- 1 | [{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":2},"path":"app-release.apk","properties":{"packageId":"io.github.veeshostak.aichat","split":"","minSdkVersion":"21"}}] -------------------------------------------------------------------------------- /app/src/androidTest/java/io/github/veeshostak/aichat/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package io.github.veeshostak.aichat; 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 | * Instrumentation 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() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("io.github.veeshostak.aichat", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher_fiona-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VeeShostak/android-aws-architecture-components-dialogflow-chatbot/29c095c4bd476b757e456640bf1160fd599b8e01/app/src/main/ic_launcher_fiona-web.png -------------------------------------------------------------------------------- /app/src/main/java/io/github/veeshostak/aichat/activities/ChatListActivity.java: -------------------------------------------------------------------------------- 1 | package io.github.veeshostak.aichat.activities; 2 | 3 | import android.arch.lifecycle.Observer; 4 | import android.arch.lifecycle.ViewModelProviders; 5 | 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.content.SharedPreferences; 9 | import android.os.AsyncTask; 10 | import android.support.annotation.Nullable; 11 | import android.support.v7.app.AppCompatActivity; 12 | import android.os.Bundle; 13 | 14 | // start api.ai imports 15 | 16 | import ai.api.AIServiceException; 17 | import ai.api.RequestExtras; 18 | import ai.api.android.AIConfiguration; 19 | import ai.api.android.AIDataService; 20 | //import ai.api.AIListener; 21 | //import ai.api.android.AIService; 22 | 23 | import ai.api.model.AIContext; 24 | import ai.api.model.AIError; 25 | import ai.api.model.AIEvent; 26 | import ai.api.model.AIRequest; 27 | import ai.api.model.AIResponse; 28 | import ai.api.model.Result; 29 | 30 | import io.github.veeshostak.aichat.aws.cognito.CognitoHelper; 31 | import io.github.veeshostak.aichat.database.entity.ChatPost; 32 | import io.github.veeshostak.aichat.models.ChatMessage; 33 | import io.github.veeshostak.aichat.aws.dynamodb.DynamoDBClientAndMapper; 34 | import io.github.veeshostak.aichat.recyclerview.adapter.MessageListAdapter; 35 | import io.github.veeshostak.aichat.utils.Installation; 36 | import io.github.veeshostak.aichat.R; 37 | import io.github.veeshostak.aichat.aws.dynamodb.model.UserConversation; 38 | import io.github.veeshostak.aichat.viewmodels.AddChatPostsViewModel; 39 | import io.github.veeshostak.aichat.viewmodels.DeleteAllChatPostsViewModel; 40 | import io.github.veeshostak.aichat.viewmodels.ListAllChatPostsViewModel; 41 | import io.github.veeshostak.aichat.viewmodels.ListNotInRemoteChatPostsViewModel; 42 | import io.github.veeshostak.aichat.viewmodels.UpdateChatPostsViewModel; 43 | // end api.ai imports 44 | 45 | import java.lang.ref.WeakReference; 46 | import java.text.DateFormat; 47 | import java.util.Collections; 48 | import java.util.Date; 49 | import java.util.HashMap; 50 | import java.util.List; 51 | 52 | import android.support.v7.widget.LinearLayoutManager; 53 | import android.support.v7.widget.RecyclerView; 54 | import android.text.TextUtils; 55 | import android.util.Log; 56 | import android.view.Menu; 57 | import android.view.MenuItem; 58 | import android.view.View; 59 | import android.widget.Button; 60 | import android.widget.EditText; 61 | import android.widget.Toast; 62 | 63 | import java.util.ArrayList; 64 | import java.util.Locale; 65 | import java.util.Map; 66 | import java.util.Random; 67 | import java.util.UUID; 68 | 69 | import com.amazonaws.AmazonClientException; 70 | import com.amazonaws.auth.CognitoCachingCredentialsProvider; 71 | import com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUserSession; 72 | import com.amazonaws.services.dynamodbv2.*; 73 | import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.*; 74 | 75 | 76 | // for TTS: implement AIListener 77 | public class ChatListActivity extends AppCompatActivity implements View.OnClickListener { 78 | 79 | private static final String TAG = "ChatActivity"; 80 | //private final int REQUEST_INTERNET = 1; 81 | 82 | // when pushing to DynamoDb, push list of String messages prefixed accordingly 83 | private static final String USER_QUERY_PREFIX = "u:"; 84 | private static final String MACHINE_RESPONSE_PREFIX = "m:"; 85 | 86 | public static final String PREFS_IS_FIRST_TIME = "isFirstTime"; 87 | 88 | // ViewModels with Room Persistent DB 89 | private AddChatPostsViewModel addChatPostsViewModel; 90 | private ListAllChatPostsViewModel listAllChatPostsViewModel; 91 | private ListNotInRemoteChatPostsViewModel listNotInRemoteChatPostsViewModel; 92 | private UpdateChatPostsViewModel updateChatPostsViewModel; 93 | private DeleteAllChatPostsViewModel deleteAllChatPostsViewModel; 94 | 95 | // all local chatPosts from local Room persistent db 96 | ArrayList allLocalChatPosts; 97 | ArrayList allLocalNotInRemoteChatPosts; 98 | 99 | // user id 100 | private String uniqueId; 101 | 102 | private EditText userMessage; 103 | private Button sendBtn; 104 | 105 | private RecyclerView mMessageRecycler; 106 | private MessageListAdapter mMessageAdapter; 107 | private LinearLayoutManager mManager; 108 | 109 | // Dialogflow vars 110 | //private AIService aiService; 111 | private AIDataService aiDataService; 112 | 113 | //aws 114 | private AmazonDynamoDBClient ddbClient; 115 | private DynamoDBMapper mapper; 116 | private CognitoCachingCredentialsProvider credentialsProvider; // uses shared prefs to cache creds 117 | 118 | // shared prefs 119 | private SharedPreferences isFirstTimeSharedPref; 120 | 121 | 122 | // ================================================ 123 | 124 | @Override 125 | protected void onCreate(Bundle savedInstanceState) { 126 | super.onCreate(savedInstanceState); 127 | setContentView(R.layout.activity_chat_list); 128 | 129 | final AIConfiguration config = new AIConfiguration(getString(R.string.dialog_dlow_client_token), 130 | AIConfiguration.SupportedLanguages.English, 131 | AIConfiguration.RecognitionEngine.System); 132 | 133 | /* 134 | // permissions for listening 135 | if (ContextCompat.checkSelfPermission(ChatListActivity.this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { 136 | ActivityCompat.requestPermissions(ChatListActivity.this, new String[]{Manifest.permission.RECORD_AUDIO}, REQUEST_INTERNET); 137 | } 138 | 139 | aiService = AIService.getService(this, config); 140 | aiService.setListener(this); 141 | */ 142 | 143 | aiDataService = new AIDataService(this, config); 144 | 145 | initControls(); 146 | 147 | // observe all chatPosts 148 | listAllChatPostsViewModel.getData().observe(ChatListActivity.this, new Observer>() { 149 | @Override 150 | public void onChanged(@Nullable List chatPosts) { 151 | //recyclerViewAdapter.addItems(chatPost); 152 | allLocalChatPosts = new ArrayList<>(chatPosts); 153 | mMessageAdapter.addItems(fromChatPostListToChatMessageList(allLocalChatPosts)); 154 | scroll(); 155 | } 156 | }); 157 | 158 | // observe chatPosts that are not in remote db (have not benn pushed back) 159 | listNotInRemoteChatPostsViewModel.getData().observe(ChatListActivity.this, new Observer>() { 160 | @Override 161 | public void onChanged(@Nullable List chatPosts) { 162 | 163 | allLocalNotInRemoteChatPosts = new ArrayList<>(chatPosts); 164 | 165 | if (allLocalNotInRemoteChatPosts != null && !allLocalNotInRemoteChatPosts.isEmpty()) { 166 | // START: check to see if conversation has 5*2=10 messages, if yes push the messages and mark as pushed 167 | if (allLocalNotInRemoteChatPosts.size() >= 5) { 168 | addToRemoteDbHelper(); 169 | } 170 | // END: check to see if conversation has 5*2=10 messages, if yes push the messages and mark as pushed 171 | } 172 | } 173 | }); 174 | 175 | // START: Determine if it's the user's first time opening the app, and just welcome or welcome them back accordingly 176 | Boolean firstTime = isFirstTimeSharedPref.getBoolean("firstTime", true); 177 | 178 | // the user is back to using the app, welcome them back! or just welcome new user if it's their first time 179 | selectWelcomeChatMessage(firstTime); 180 | 181 | SharedPreferences.Editor editor = isFirstTimeSharedPref.edit(); 182 | editor.putBoolean("firstTime", false); 183 | editor.apply(); 184 | // END: Determine if it's the user's first time openeing the app, and just welcome or welcome them back accordingly 185 | } 186 | 187 | private void initControls() { 188 | 189 | userMessage = (EditText) findViewById(R.id.userMessageField); 190 | sendBtn = (Button) findViewById(R.id.sendMessageButton); 191 | sendBtn.setOnClickListener(this); 192 | 193 | // get reference to rec view 194 | mMessageRecycler = (RecyclerView) findViewById(R.id.reyclerview_message_list); 195 | 196 | // choose layOutManager 197 | mManager = new LinearLayoutManager(this); 198 | //mManager.setReverseLayout(true); 199 | //mManager.setStackFromEnd(true); 200 | mMessageRecycler.setLayoutManager(mManager); 201 | 202 | // populate adapter with dataSource 203 | mMessageAdapter = new MessageListAdapter(this, fromChatPostListToChatMessageList(allLocalChatPosts)); 204 | mMessageRecycler.setLayoutManager(mManager); 205 | 206 | // set adapter 207 | mMessageRecycler.setAdapter(mMessageAdapter); 208 | 209 | // START: scroll to end when keyboard opens 210 | mMessageRecycler.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { 211 | @Override 212 | public void onLayoutChange(View v, 213 | int left, int top, int right, int bottom, 214 | int oldLeft, int oldTop, int oldRight, int oldBottom) { 215 | if (bottom < oldBottom) { 216 | mMessageRecycler.postDelayed(new Runnable() { 217 | @Override 218 | public void run() { 219 | if (mMessageRecycler.getAdapter().getItemCount() > 0) { 220 | mMessageRecycler.smoothScrollToPosition( 221 | mMessageRecycler.getAdapter().getItemCount() - 1); 222 | } 223 | } 224 | }, 100); 225 | } 226 | } 227 | }); 228 | // END: scroll to end when keyboard opens 229 | 230 | 231 | // retrieve ID from file 232 | Installation GetId = new Installation(); 233 | uniqueId = GetId.id(getApplicationContext()); 234 | 235 | 236 | listAllChatPostsViewModel = ViewModelProviders.of(this).get(ListAllChatPostsViewModel.class); 237 | listNotInRemoteChatPostsViewModel = ViewModelProviders.of(this).get(ListNotInRemoteChatPostsViewModel.class); 238 | addChatPostsViewModel = ViewModelProviders.of(this).get(AddChatPostsViewModel.class); 239 | deleteAllChatPostsViewModel = ViewModelProviders.of(this).get(DeleteAllChatPostsViewModel.class); 240 | updateChatPostsViewModel = ViewModelProviders.of(this).get(UpdateChatPostsViewModel.class); 241 | 242 | Context context = this; 243 | isFirstTimeSharedPref = context.getSharedPreferences(PREFS_IS_FIRST_TIME, Context.MODE_PRIVATE); 244 | 245 | // START: get AWS credentials to access AWS resources 246 | 247 | CognitoUserSession cognitoUserSession = CognitoHelper.getCurrSession(); 248 | // Get id token from CognitoUserSession. 249 | String idToken = cognitoUserSession.getIdToken().getJWTToken(); 250 | 251 | // Create a credentials provider, or use the existing provider. 252 | credentialsProvider = new CognitoCachingCredentialsProvider(context, getApplicationContext().getString(R.string.cognito_identity_pool_id), CognitoHelper.getCognitoRegion()); 253 | 254 | // Set up as a credentials provider. 255 | Map logins = new HashMap(); 256 | logins.put(getApplicationContext().getString(R.string.cognito_login), idToken); 257 | credentialsProvider.setLogins(logins); 258 | // END: get AWS credentials to access AWS resources 259 | 260 | DynamoDBClientAndMapper dynamoDb = new DynamoDBClientAndMapper(getApplicationContext(), credentialsProvider); 261 | mapper = dynamoDb.getMapper(); 262 | 263 | 264 | 265 | } 266 | 267 | /* 268 | Use Weak Reference b/c: 269 | The inner class needs to be accessing the outside class during its entire lifetime. 270 | What happens when the Activity is destroyed? The AsyncTask is holding a reference to the 271 | Activity, and the Activity cannot be collected by the GC. We get a memory leak. 272 | 273 | a weak reference is a reference not strong enough to keep the object in memory, the object 274 | will be garbage-collected. 275 | When the Activity stops existing, since it is hold through a WeakReference, it can be collected. 276 | 277 | the Activity within the inner class is now referenced as WeakReference mainActivity; 278 | */ 279 | private static class TaskAddChatPostsToRemoteDb extends AsyncTask { 280 | 281 | private WeakReference activityReference; // weak ref to our ChatListActivity.java 282 | private ArrayList chatPostsToAdd; 283 | 284 | // only retain a weak reference to the activity 285 | private TaskAddChatPostsToRemoteDb(ChatListActivity context, ArrayList chatPostsToAdd) { 286 | activityReference = new WeakReference<>(context); 287 | this.chatPostsToAdd = new ArrayList<>(chatPostsToAdd); 288 | } 289 | 290 | // can pass params in .execute(Params...) 291 | 292 | @Override 293 | protected AmazonClientException doInBackground(final String... params) { 294 | 295 | // access params 296 | // params[0] 297 | 298 | try { 299 | // START: add conversation to User-Conversations Table 300 | String conversationDateTime = DateFormat.getDateTimeInstance().format(new Date()); 301 | 302 | // create chatPostId 303 | String conversationId = createUniqueId(); 304 | // add the conversation to conversation table 305 | UserConversation conversation = new UserConversation(); 306 | conversation.setConversationId(conversationId); 307 | conversation.setConversationDateTime(conversationDateTime); 308 | 309 | //conversation.setUserId(activityReference.get().uniqueId); 310 | 311 | // username is uuid geneated by aws cognito that is mapped to user's email and pass 312 | conversation.setUserId(CognitoHelper.getCurrSession().getUsername()); 313 | 314 | 315 | // create chatpost list of strings to push to NoSql db 316 | ArrayList tempChatPostsStringList = new ArrayList(); 317 | for (ChatPost chatPost : chatPostsToAdd) { 318 | tempChatPostsStringList.add(USER_QUERY_PREFIX + chatPost.getUserQuery()); 319 | tempChatPostsStringList.add(MACHINE_RESPONSE_PREFIX + chatPost.getResponse()); 320 | } 321 | conversation.setConversation(tempChatPostsStringList); 322 | // END: add conversation to User-Conversations Table 323 | 324 | // START: add conversationId to this User in the User Table 325 | /* 326 | // (User has many conversations: @One To Many) 327 | 328 | HashSet conversationIds; // to obtain stored conversations ids 329 | // get user with Id PK 330 | User selectedUser = activityReference.get().mapper.load(User.class, activityReference.get().uniqueId); 331 | 332 | 333 | // get existing conversationsIds from user, if they exist 334 | if (selectedUser != null && selectedUser.getConversationIds() != null && !selectedUser.getConversationIds().isEmpty() ) { 335 | // they exist, shallow copy them 336 | conversationIds = new HashSet(selectedUser.getConversationIds()); 337 | } else { 338 | // they DNE, initialize empty hashSet, we will add the current conversationId to it 339 | conversationIds = new HashSet(); 340 | } 341 | // add new conversation id to hashSet 342 | conversationIds.add(conversationId); 343 | // add updated hashSet to our loaded user 344 | 345 | selectedUser.setConversationIds(conversationIds); 346 | 347 | */ 348 | // END: add conversationId to this User in the User Table 349 | 350 | // save user with the updated conversationId 351 | // activityReference.get().mapper.save(selectedUser); 352 | 353 | // save conversation 354 | activityReference.get().mapper.save(conversation); 355 | 356 | } catch (AmazonClientException ace) { 357 | return ace; 358 | } 359 | 360 | return null; 361 | 362 | } 363 | 364 | @Override 365 | protected void onPostExecute(AmazonClientException ace) { 366 | if (ace == null) { 367 | onResult(); 368 | 369 | } else { 370 | onError(ace); 371 | } 372 | } 373 | 374 | private void onResult() { 375 | 376 | // START set pushedToRemoteDb boolean value of each retrieved chatPost to true, update local Room Db 377 | 378 | // note: since we might have had encountered an error when pushing to db (chatPosts wouldn't be pushed), 379 | // we cannot set pushedToRemoteDb to true when converting chatPosts to a temp chatPostsList of Strings. (to avoid iterating again) 380 | // (in case of an error we would be in onError() and then next time we will try to push again so we cannot mark it as pushed b4 hand) 381 | 382 | for (ChatPost chatPost: chatPostsToAdd) { 383 | chatPost.setPushedToRemoteDb(true); 384 | } 385 | // run update transaction on local room persistent db 386 | // note: pass in toArray(new ChatPost[0]) instead of toArray(new ChatPost[chatPostsToAdd.size()]) due to JVM optimizations 387 | activityReference.get().updateChatPostsViewModel.updateChatPosts((chatPostsToAdd.toArray(new ChatPost[0]))); 388 | // END set pushedToRemoteDb boolean value of each retrieved chatPost to true, update local Room Db 389 | 390 | } 391 | private void onError(AmazonClientException ace) { 392 | 393 | Log.d(TAG, ace.toString()); 394 | Toast.makeText(activityReference.get().getApplicationContext(), 395 | "Internal error occurred communicating with DynamoDB\n" + "Error Message: " + ace.getMessage(), 396 | Toast.LENGTH_LONG).show(); 397 | } 398 | 399 | private String createUniqueId() { 400 | // returns: 10/23/17 8:22AM 401 | String currentDateTimeString = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.US).format(new Date()); 402 | 403 | // format to 102317822AM 404 | currentDateTimeString = currentDateTimeString.replace("/", "").replace(" ", "").replace(":", ""); 405 | 406 | // uuid + datetime 407 | String id = UUID.randomUUID().toString() + currentDateTimeString; 408 | 409 | return id; 410 | } 411 | } 412 | 413 | public List fromChatPostListToChatMessageList(List chatPosts) { 414 | 415 | if (chatPosts == null ) { 416 | return null; 417 | } 418 | 419 | List mMessageList = new ArrayList<>(); 420 | 421 | for(ChatPost chatPost: chatPosts) { 422 | 423 | ChatMessage tempMsgMe = new ChatMessage(); 424 | ChatMessage tempMsgMachine = new ChatMessage(); 425 | 426 | tempMsgMe.setIsMe(true); 427 | tempMsgMe.setMessage(chatPost.getUserQuery()); 428 | tempMsgMe.setCreatedAt(chatPost.getCreatedAt()); 429 | 430 | tempMsgMachine.setIsMe(false); 431 | tempMsgMachine.setMessage(chatPost.getResponse()); 432 | tempMsgMachine.setCreatedAt(chatPost.getCreatedAt()); 433 | 434 | mMessageList.add(tempMsgMe); 435 | mMessageList.add(tempMsgMachine); 436 | } 437 | return mMessageList; 438 | 439 | } 440 | 441 | 442 | public void addToRemoteDbHelper() { 443 | 444 | // push them back to remoteDb. 445 | // then set pushedToRemoteDb value of each retrieved chatPost to true and run update transaction 446 | if (allLocalNotInRemoteChatPosts != null && !allLocalNotInRemoteChatPosts.isEmpty()) { 447 | // update db with stored chat history, on success update local (set pushedToRemoteDb to true) 448 | new TaskAddChatPostsToRemoteDb(this, allLocalNotInRemoteChatPosts).execute(); 449 | } 450 | 451 | } 452 | 453 | public void selectWelcomeChatMessage(Boolean isFirstTime){ 454 | 455 | Random r = new Random(); 456 | int randNUm = r.nextInt(6 - 1) + 1; // 1-5 457 | 458 | String userQuery; 459 | String response; 460 | 461 | if (!isFirstTime) { 462 | switch (randNUm) { 463 | case 1: 464 | userQuery = "Ahoy!"; 465 | response = "Hi! I'm Fiona. I’m sure we’ll get on really well."; 466 | break; 467 | case 2: 468 | userQuery = "I have returned!"; 469 | response = "Hello and welcome!"; 470 | break; 471 | case 3: 472 | userQuery = "Ahoy! I'm back!"; 473 | response = "I was so bored. Thank you for saving me!"; 474 | break; 475 | case 4: 476 | userQuery = "Hello! I'm back!"; 477 | response = "Great to see you! I was so bored."; 478 | break; 479 | case 5: 480 | userQuery = "I'm back!"; 481 | response = "Look who it is!"; 482 | break; 483 | default: 484 | userQuery = "I'm back!"; 485 | response = "Hi! I'm Fiona"; 486 | } 487 | 488 | } else { 489 | userQuery = "Hello Fiona!"; 490 | response = "Welcome! I’m excited to meet you!"; 491 | } 492 | 493 | ChatPost chatPost = new ChatPost(); 494 | chatPost.setCreatedAt(DateFormat.getDateTimeInstance().format(new Date())); 495 | chatPost.setUserQuery(userQuery); 496 | chatPost.setResponse(response); 497 | chatPost.setPushedToRemoteDb(false); 498 | 499 | addChatPostsViewModel.addChatPosts(chatPost); 500 | } 501 | 502 | public void scroll() { 503 | if (mMessageRecycler.getAdapter().getItemCount() > 0) { 504 | mMessageRecycler.smoothScrollToPosition( 505 | mMessageRecycler.getAdapter().getItemCount() - 1); 506 | } 507 | } 508 | 509 | 510 | @Override 511 | public boolean onCreateOptionsMenu(Menu menu) { 512 | // Inflate the menu; this adds items to the action bar if it is present. 513 | getMenuInflater().inflate(R.menu.menu_chat, menu); 514 | return true; 515 | } 516 | 517 | @Override 518 | public boolean onOptionsItemSelected(MenuItem item) { 519 | // Handle action bar item clicks here. The action bar will 520 | // automatically handle clicks on the Home/Up button, so long 521 | // as you specify a parent activity in AndroidManifest.xml. 522 | int id = item.getItemId(); 523 | 524 | 525 | if (id == R.id.action_terms) { 526 | // show terms of service 527 | startActivity(new Intent(ChatListActivity.this, TermsOfService.class)); 528 | return true; 529 | } else if (id == R.id.action_delete_chat) { 530 | // START: check to see if there are chatPosts that need to be pushed to RemoteDb, push them and mark as pushed 531 | // if they exist, push them back to remoteDb. else do nothing. 532 | // then set pushedToRemoteDb value of each retrieved chatPost to true and run update transaction 533 | 534 | ArrayList chatPostsToPush = new ArrayList(allLocalNotInRemoteChatPosts); 535 | if (chatPostsToPush != null && !chatPostsToPush.isEmpty()) { 536 | // update db with stored chat history, on success update local (set pushedToRemoteDb to true) 537 | new TaskAddChatPostsToRemoteDb(this, chatPostsToPush).execute(); 538 | } 539 | // if they do not exist, do nothing 540 | // END: check to see if there are chatPosts that need to be pushed to RemoteDb, push them and mark as pushed 541 | 542 | // clear chatPosts from local Room persisted db 543 | deleteAllChatPostsViewModel.deleteAllChatPosts(); 544 | 545 | // RecyclerView is cleared since we are obesrving livedata 546 | 547 | return true; 548 | } 549 | 550 | return super.onOptionsItemSelected(item); 551 | } 552 | 553 | @Override 554 | public void onClick(View v) { 555 | int i = v.getId(); 556 | if (i == R.id.sendMessageButton) { 557 | 558 | String messageText = userMessage.getText().toString().trim(); 559 | if (TextUtils.isEmpty(messageText)) { 560 | return; 561 | } 562 | userMessage.setText(""); // clear message field 563 | 564 | // not needed since we are observing live data, but to display right away we can call it now 565 | // since live data will only be updated when we have full ChatPost with response in AsyncTask 566 | // display message in RecyclerView 567 | // START: quickly display userQuery in RecyclerView 568 | ChatMessage chatMessage = new ChatMessage(); 569 | chatMessage.setMessage(messageText); 570 | chatMessage.setCreatedAt(DateFormat.getDateTimeInstance().format(new Date())); 571 | chatMessage.setIsMe(true); 572 | mMessageAdapter.add(chatMessage); // display in RecyclerView while waiting for response 573 | // END: quickly display userQuery in RecyclerView 574 | 575 | 576 | // disable send button until retrieve success of failure from API 577 | sendBtn.setClickable(false); 578 | 579 | // START: send userQuery to dialogFlow API, once obtain response: add to RecyclerView, add chatPost to local Room db 580 | // pass params 581 | final String eventString = null; 582 | final String contextString = null; 583 | new TaskSendUserQueryToDialogFlow(this, messageText).execute(eventString, contextString); 584 | // END: send userQuery to dialogFlow API, once obtain response: add to RecyclerView, add chatPost to local Room db 585 | 586 | //aiService.startListening(); 587 | } 588 | } 589 | 590 | /* 591 | // If implements AIListener (for tts) 592 | // start Dialogflow listening 593 | @Override 594 | public void onListeningStarted() {} 595 | 596 | @Override 597 | public void onListeningCanceled() {} 598 | 599 | @Override 600 | public void onListeningFinished() {} 601 | 602 | @Override 603 | public void onResult(AIResponse result) { 604 | 605 | } 606 | 607 | @Override 608 | public void onError(AIError error) { 609 | 610 | } 611 | 612 | @Override 613 | public void onAudioLevel(final float level) {} 614 | // end Dialogflow listening 615 | */ 616 | 617 | 618 | /* 619 | AIRequest should have query OR event 620 | sends request with userQuery to dialogFlow API, once obtain response: add to RecyclerView, add chatPost to local Room db 621 | */ 622 | private static class TaskSendUserQueryToDialogFlow extends AsyncTask { 623 | 624 | private WeakReference activityReference; // weak ref to our ChatListActivity.java 625 | private String userQuery; 626 | private AIError aiError; 627 | 628 | private TaskSendUserQueryToDialogFlow(ChatListActivity context, String userQuery) { 629 | activityReference = new WeakReference(context); 630 | this.userQuery = userQuery; 631 | } 632 | 633 | @Override 634 | protected AIResponse doInBackground(final String... params) { 635 | 636 | final AIRequest request = new AIRequest(); 637 | String query = userQuery; 638 | String event = params[0]; 639 | 640 | // set request variables 641 | if (!TextUtils.isEmpty(query)) 642 | request.setQuery(query); 643 | if (!TextUtils.isEmpty(event)) 644 | request.setEvent(new AIEvent(event)); 645 | 646 | final String contextString = params[1]; 647 | RequestExtras requestExtras = null; 648 | 649 | // specify additional contexts in the query using RequestExtras object 650 | if (!TextUtils.isEmpty(contextString)) { 651 | final List contexts = Collections.singletonList(new AIContext(contextString)); 652 | requestExtras = new RequestExtras(contexts, null); 653 | } 654 | 655 | try { 656 | // aiDataService returns an AIResponse 657 | return activityReference.get().aiDataService.request(request, requestExtras); 658 | } catch (final AIServiceException e) { 659 | aiError = new AIError(e); 660 | return null; 661 | } 662 | 663 | } 664 | 665 | @Override 666 | protected void onPostExecute(final AIResponse response) { 667 | if (response != null) { 668 | onResult(response); 669 | } else { 670 | onError(aiError); 671 | } 672 | } 673 | 674 | private void onResult(final AIResponse response) { 675 | 676 | Log.d(TAG, "onResult"); 677 | //resultTextView.setText(gson.toJson(response)); 678 | Log.i(TAG, "Received success response"); 679 | 680 | activityReference.get().sendBtn.setClickable(true); // enable button 681 | 682 | // get speech from the result object 683 | final Result result = response.getResult(); 684 | final String speech = result.getFulfillment().getSpeech(); 685 | 686 | //Log.i(TAG, "Speech: " + speech); 687 | //Toast.makeText(getApplicationContext(),"Speech:" + speech , Toast.LENGTH_LONG).show(); 688 | 689 | // add ChatPost to local Room persistent db 690 | ChatPost mChatPost = new ChatPost(); 691 | mChatPost.setUserQuery(userQuery); 692 | mChatPost.setResponse(speech); 693 | mChatPost.setPushedToRemoteDb(false); 694 | mChatPost.setCreatedAt(DateFormat.getDateTimeInstance().format(new Date())); 695 | activityReference.get().addChatPostsViewModel.addChatPosts(mChatPost); 696 | 697 | } 698 | 699 | private void onError(final AIError error) { 700 | Log.d(TAG, error.toString()); 701 | activityReference.get().sendBtn.setClickable(true); // enable button 702 | 703 | Toast.makeText(activityReference.get().getApplicationContext(), error.toString(), Toast.LENGTH_LONG).show(); 704 | //resultTextView.setText(error.toString()); 705 | } 706 | 707 | } 708 | 709 | /* 710 | @Override 711 | public void onPause() { 712 | super.onPause(); 713 | 714 | // use onPause because: 715 | // onStop may not always be called in low-memory situations, 716 | // as well as onDestroy, such as when Android is starved for 717 | // resources and cannot properly background the Activity. 718 | 719 | // determine whether the activity is simply pausing or completely finishing. 720 | if (isFinishing()) { 721 | // save conversation to the db 722 | 723 | } 724 | } 725 | */ 726 | 727 | } 728 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/veeshostak/aichat/activities/SignInActivity.java: -------------------------------------------------------------------------------- 1 | package io.github.veeshostak.aichat.activities; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.text.TextUtils; 7 | import android.view.View; 8 | import android.widget.Button; 9 | import android.widget.EditText; 10 | import android.widget.TextView; 11 | import android.widget.Toast; 12 | 13 | import com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoDevice; 14 | import com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUser; 15 | import com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUserAttributes; 16 | import com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUserCodeDeliveryDetails; 17 | import com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUserDetails; 18 | import com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUserSession; 19 | import com.amazonaws.mobileconnectors.cognitoidentityprovider.continuations.AuthenticationContinuation; 20 | import com.amazonaws.mobileconnectors.cognitoidentityprovider.continuations.AuthenticationDetails; 21 | import com.amazonaws.mobileconnectors.cognitoidentityprovider.continuations.ChallengeContinuation; 22 | import com.amazonaws.mobileconnectors.cognitoidentityprovider.continuations.MultiFactorAuthenticationContinuation; 23 | import com.amazonaws.mobileconnectors.cognitoidentityprovider.handlers.AuthenticationHandler; 24 | import com.amazonaws.mobileconnectors.cognitoidentityprovider.handlers.GetDetailsHandler; 25 | import com.amazonaws.mobileconnectors.cognitoidentityprovider.handlers.SignUpHandler; 26 | 27 | 28 | import java.text.DateFormat; 29 | import java.util.Date; 30 | import java.util.Locale; 31 | import java.util.UUID; 32 | 33 | import io.github.veeshostak.aichat.aws.cognito.CognitoHelper; 34 | import io.github.veeshostak.aichat.R; 35 | 36 | 37 | public class SignInActivity extends AppCompatActivity implements View.OnClickListener { 38 | 39 | private static final String TAG = "SignInActivity"; // for logcat 40 | 41 | private EditText mNameField; 42 | private EditText mEmailField; 43 | private EditText mPasswordField; 44 | 45 | private TextView mTermOfServiceLink; 46 | private Button mSignUpButton; 47 | private Button mSignInButton; 48 | 49 | //private String uniqueId; 50 | 51 | private String email; 52 | private String name; 53 | 54 | 55 | 56 | 57 | @Override 58 | protected void onCreate(Bundle savedInstanceState) { 59 | 60 | super.onCreate(savedInstanceState); 61 | setContentView(R.layout.activity_sign_in); 62 | 63 | initControls(); 64 | 65 | CognitoHelper.init(getApplicationContext()); 66 | 67 | // check if user is still authenticated (token still valid) 68 | // if yes sign them in automatically and go to ChatListActivity 69 | findCurrent(); 70 | 71 | } 72 | 73 | private void initControls() { 74 | 75 | mNameField = (EditText) findViewById(R.id.nameField); 76 | mEmailField = (EditText) findViewById(R.id.emailField); 77 | mPasswordField = (EditText) findViewById(R.id.passwordField); 78 | 79 | mTermOfServiceLink = (TextView) findViewById(R.id.termsLink); 80 | mSignUpButton = (Button) findViewById(R.id.signUpButton); 81 | mSignInButton = (Button) findViewById(R.id.signInButton); 82 | 83 | // listeners 84 | mSignUpButton.setOnClickListener(this); 85 | mSignInButton.setOnClickListener(this); 86 | mTermOfServiceLink.setOnClickListener(this); 87 | 88 | } 89 | 90 | private void findCurrent() { 91 | CognitoUser user = CognitoHelper.getUserPool().getCurrentUser(); 92 | email = user.getUserId(); 93 | if(email != null) { 94 | CognitoHelper.setUser(email); 95 | user.getSessionInBackground(authenticationHandler); 96 | } 97 | } 98 | 99 | 100 | @Override 101 | public void onClick(View v) { 102 | int i = v.getId(); 103 | if (i == R.id.signUpButton) { 104 | 105 | email = mEmailField.getText().toString(); 106 | if (!isValidEmail(email)) { 107 | mEmailField.setError("Please enter a valid email"); 108 | return; 109 | } 110 | 111 | name = mNameField.getText().toString(); 112 | if (name.isEmpty()) { 113 | mNameField.setError("Please enter a name"); 114 | return; 115 | } 116 | 117 | // Dont need to set user in db, since we can access user analytics in Cognito 118 | //setUserInDb(name, email); 119 | 120 | // Create a CognitoUserAttributes object and add user attributes 121 | CognitoUserAttributes userAttributes = new CognitoUserAttributes(); 122 | 123 | // Add the user attributes. Attributes are added as key-value pairs 124 | // Adding user's given name. 125 | // Note that the key is "name" which is the OIDC claim for name 126 | userAttributes.addAttribute("name", name); 127 | 128 | // Adding user's email 129 | userAttributes.addAttribute("email", email); 130 | 131 | // create a unique userId. // email is passed, id is created on server 132 | //uniqueId = createUniqueId(); 133 | 134 | // call the sign-up api. 135 | CognitoHelper.getUserPool().signUpInBackground(email, mPasswordField.getText().toString(), userAttributes, null, signupCallback); 136 | 137 | // after sign up, user confirms their email. then they can sign in 138 | 139 | } else if (i == R.id.signInButton) { 140 | 141 | 142 | email = mEmailField.getText().toString(); 143 | if (!isValidEmail(email)) { 144 | mEmailField.setError("Please enter a valid email"); 145 | return; 146 | } 147 | 148 | name = mNameField.getText().toString(); 149 | if (name.isEmpty()) { 150 | mNameField.setError("Please enter a name"); 151 | return; 152 | } 153 | 154 | // Sign in the user 155 | CognitoHelper.getUserPool().getUser(email).getSessionInBackground(authenticationHandler); 156 | 157 | } else if (i == R.id.termsLink) { 158 | startActivity(new Intent(SignInActivity.this, TermsOfService.class)); 159 | } 160 | } 161 | 162 | public void signInSuccess() { 163 | 164 | // Fetch the user details 165 | CognitoHelper.getUserPool().getUser(email).getDetailsInBackground(getDetailsHandler); 166 | 167 | } 168 | 169 | // ================== 170 | // START: Callbacks 171 | // ================== 172 | 173 | // Callback handler for the sign-in process 174 | AuthenticationHandler authenticationHandler = new AuthenticationHandler() { 175 | 176 | @Override 177 | public void onSuccess(CognitoUserSession cognitoUserSession, CognitoDevice cognitoDevice) { 178 | // Sign-in was successful, cognitoUserSession will contain tokens for the user 179 | 180 | CognitoHelper.setCurrSession(cognitoUserSession); 181 | 182 | 183 | signInSuccess(); 184 | } 185 | 186 | @Override 187 | public void getAuthenticationDetails(AuthenticationContinuation authenticationContinuation, String userId) { 188 | // The API needs user sign-in credentials to continue 189 | AuthenticationDetails authenticationDetails = new AuthenticationDetails(userId, mPasswordField.getText().toString(), null); 190 | 191 | // Pass the user sign-in credentials to the continuation 192 | authenticationContinuation.setAuthenticationDetails(authenticationDetails); 193 | 194 | // Allow the sign-in to continue 195 | authenticationContinuation.continueTask(); 196 | } 197 | 198 | 199 | @Override 200 | public void getMFACode(MultiFactorAuthenticationContinuation multiFactorAuthenticationContinuation) { 201 | // Multi-factor authentication is required; get the verification code from user 202 | multiFactorAuthenticationContinuation.setMfaCode("123"); 203 | // Allow the sign-in process to continue 204 | multiFactorAuthenticationContinuation.continueTask(); 205 | } 206 | 207 | 208 | @Override 209 | public void onFailure(Exception exception) { 210 | // Sign-in failed, check exception for the cause 211 | Toast.makeText(getApplicationContext(), 212 | "error: " + exception.getMessage(), 213 | Toast.LENGTH_SHORT).show(); 214 | } 215 | 216 | @Override 217 | public void authenticationChallenge(ChallengeContinuation continuation) { 218 | /** 219 | * For Custom authentication challenge, implement your logic to present challenge to the 220 | * user and pass the user's responses to the continuation. 221 | */ 222 | if ("NEW_PASSWORD_REQUIRED".equals(continuation.getChallengeName())) { 223 | // This is the first sign-in attempt for an admin created user 224 | // newPasswordContinuation = (NewPasswordContinuation) continuation; 225 | // AppHelper.setUserAttributeForDisplayFirstLogIn(newPasswordContinuation.getCurrentUserAttributes(), 226 | // newPasswordContinuation.getRequiredAttributes()); 227 | // closeWaitDialog(); 228 | // firstTimeSignIn(); 229 | } else if ("SELECT_MFA_TYPE".equals(continuation.getChallengeName())) { 230 | // closeWaitDialog(); 231 | // mfaOptionsContinuation = (ChooseMfaContinuation) continuation; 232 | // List mfaOptions = mfaOptionsContinuation.getMfaOptions(); 233 | // selectMfaToSignIn(mfaOptions, continuation.getParameters()); 234 | } 235 | } 236 | }; 237 | 238 | // === 239 | 240 | // Create a callback handler for sign-up. The onSuccess method is called when the sign-up is successful. 241 | SignUpHandler signupCallback = new SignUpHandler() { 242 | 243 | @Override 244 | public void onSuccess(CognitoUser cognitoUser, boolean userConfirmed, CognitoUserCodeDeliveryDetails cognitoUserCodeDeliveryDetails) { 245 | // Sign-up was successful 246 | 247 | // Check if this user (cognitoUser) needs to be confirmed 248 | if(!userConfirmed) { 249 | // This user must be confirmed and a confirmation link was sent to the user 250 | // cognitoUserCodeDeliveryDetails will indicate where the confirmation code was sent 251 | // Get the confirmation code from user if using confirmation code. else th user confirms an email link 252 | Toast.makeText(getApplicationContext(), 253 | "Please confirm your email address " + cognitoUserCodeDeliveryDetails.getDestination(), 254 | Toast.LENGTH_LONG).show(); 255 | } 256 | else { 257 | // The user has already been confirmed 258 | Toast.makeText(getApplicationContext(), 259 | "You have confirmed your email, please sign in! ", 260 | Toast.LENGTH_LONG).show(); 261 | } 262 | } 263 | 264 | @Override 265 | public void onFailure(Exception exception) { 266 | // Sign-up failed, check exception for the cause 267 | Toast.makeText(getApplicationContext(), 268 | "error: " + exception.getMessage(), 269 | Toast.LENGTH_SHORT).show(); 270 | } 271 | }; 272 | 273 | // === 274 | 275 | // Implement callback handler for getting details 276 | GetDetailsHandler getDetailsHandler = new GetDetailsHandler() { 277 | @Override 278 | public void onSuccess(CognitoUserDetails cognitoUserDetails) { 279 | // The user detail are in cognitoUserDetails 280 | 281 | CognitoHelper.setUserDetails(cognitoUserDetails); 282 | 283 | // Go to ChatListActivity 284 | startActivity(new Intent(SignInActivity.this, ChatListActivity.class)); 285 | finish(); // exit, remove current activity 286 | } 287 | 288 | @Override 289 | public void onFailure(Exception exception) { 290 | // Fetch user details failed, check exception for the cause 291 | Toast.makeText(getApplicationContext(), 292 | "error: " + exception.getMessage(), 293 | Toast.LENGTH_SHORT).show(); 294 | } 295 | }; 296 | 297 | // ================== 298 | // END: Callbacks 299 | // ================== 300 | 301 | public String createUniqueId() { 302 | // returns: 10/23/17 8:22AM 303 | String currentDateTimeString = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.US).format(new Date()); 304 | 305 | // format to 102317822AM 306 | currentDateTimeString = currentDateTimeString.replace("/", "").replace(" ", "").replace(":", ""); 307 | 308 | // uuid + datetime 309 | String id = UUID.randomUUID().toString() + currentDateTimeString; 310 | 311 | return id; 312 | } 313 | 314 | public final static boolean isValidEmail(CharSequence target) { 315 | if (TextUtils.isEmpty(target)) { 316 | return false; 317 | } else { 318 | return android.util.Patterns.EMAIL_ADDRESS.matcher(target).matches(); 319 | } 320 | } 321 | 322 | 323 | 324 | } 325 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/veeshostak/aichat/activities/TermsOfService.java: -------------------------------------------------------------------------------- 1 | package io.github.veeshostak.aichat.activities; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.text.method.ScrollingMovementMethod; 7 | import android.view.View; 8 | import android.widget.Button; 9 | import android.widget.TextView; 10 | 11 | import io.github.veeshostak.aichat.R; 12 | 13 | /** 14 | * Created by vladshostak on 2/2/17. 15 | */ 16 | 17 | public class TermsOfService extends AppCompatActivity implements View.OnClickListener { 18 | 19 | private Button mFinishButton; 20 | private TextView mFullTermsText; 21 | 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | setContentView(R.layout.activity_terms_of_service); 27 | 28 | mFinishButton = (Button) findViewById(R.id.button2); 29 | mFinishButton.setOnClickListener(this); 30 | 31 | mFullTermsText = (TextView) findViewById(R.id.textTerms); 32 | mFullTermsText.setMovementMethod(new ScrollingMovementMethod()); 33 | 34 | } 35 | 36 | @Override 37 | public void onClick(View v) { 38 | int i = v.getId(); 39 | if (i == R.id.button2) { 40 | finish(); 41 | } 42 | } 43 | 44 | 45 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/veeshostak/aichat/aws/cognito/CognitoHelper.java: -------------------------------------------------------------------------------- 1 | package io.github.veeshostak.aichat.aws.cognito; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | 6 | import com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUserDetails; 7 | import com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUserPool; 8 | import com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUserSession; 9 | import com.amazonaws.regions.Regions; 10 | 11 | import java.util.HashSet; 12 | import java.util.Set; 13 | 14 | import io.github.veeshostak.aichat.R; 15 | 16 | /** 17 | * Created by vladshostak on 12/30/17. 18 | */ 19 | 20 | public class CognitoHelper { 21 | 22 | // App settings 23 | private static CognitoHelper appHelper; 24 | private static CognitoUserPool userPool; 25 | private static String user; 26 | 27 | private static String userPoolId; 28 | private static String clientId; 29 | /** 30 | * App secret associated with your app id - if the App id does not have an associated App secret, 31 | * set the App secret to null. 32 | * e.g. clientSecret = null; 33 | */ 34 | private static String clientSecret; 35 | private static Regions cognitoRegion = Regions.US_WEST_2; 36 | 37 | // User details from the service 38 | private static CognitoUserSession currSession; 39 | private static CognitoUserDetails userDetails; 40 | 41 | 42 | public static void init(Context context) { 43 | 44 | userPoolId = context.getString(R.string.cognito_user_pool_id); 45 | clientId = context.getString(R.string.cognito_client_id); 46 | 47 | /** 48 | * App secret associated with your app id - if the App id does not have an associated App secret, 49 | * set the App secret to null. 50 | * e.g. clientSecret = null; 51 | */ 52 | clientSecret = context.getString(R.string.cognito_client_secret_id);; 53 | 54 | 55 | if (appHelper != null && userPool != null) { 56 | return; 57 | } 58 | 59 | if (appHelper == null) { 60 | appHelper = new CognitoHelper(); 61 | } 62 | 63 | if (userPool == null) { 64 | 65 | // Create a user pool with default ClientConfiguration 66 | userPool = new CognitoUserPool(context, userPoolId, clientId, clientSecret, cognitoRegion); 67 | 68 | // This will also work 69 | /* 70 | ClientConfiguration clientConfiguration = new ClientConfiguration(); 71 | AmazonCognitoIdentityProvider cipClient = new AmazonCognitoIdentityProviderClient(new AnonymousAWSCredentials(), clientConfiguration); 72 | cipClient.setRegion(Region.getRegion(cognitoRegion)); 73 | userPool = new CognitoUserPool(context, userPoolId, clientId, clientSecret, cipClient); 74 | */ 75 | 76 | 77 | } 78 | 79 | } 80 | 81 | public static CognitoUserPool getUserPool() { 82 | return userPool; 83 | } 84 | 85 | public static void setUserPool(CognitoUserPool userPool) { 86 | CognitoHelper.userPool = userPool; 87 | } 88 | 89 | public static CognitoUserSession getCurrSession() { 90 | return currSession; 91 | } 92 | 93 | public static void setCurrSession(CognitoUserSession currSession) { 94 | CognitoHelper.currSession = currSession; 95 | } 96 | 97 | public static CognitoUserDetails getUserDetails() { 98 | return userDetails; 99 | } 100 | 101 | public static void setUserDetails(CognitoUserDetails userDetails) { 102 | CognitoHelper.userDetails = userDetails; 103 | } 104 | 105 | public static String getUser() { 106 | return user; 107 | } 108 | 109 | public static void setUser(String user) { 110 | CognitoHelper.user = user; 111 | } 112 | 113 | 114 | public static String getUserPoolId() { 115 | return userPoolId; 116 | } 117 | 118 | public static Regions getCognitoRegion() { 119 | return cognitoRegion; 120 | } 121 | } 122 | 123 | 124 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/veeshostak/aichat/aws/dynamodb/DynamoDBClientAndMapper.java: -------------------------------------------------------------------------------- 1 | package io.github.veeshostak.aichat.aws.dynamodb; 2 | 3 | // aws 4 | 5 | import android.content.Context; 6 | 7 | import com.amazonaws.AmazonServiceException; 8 | import com.amazonaws.ClientConfiguration; 9 | import com.amazonaws.regions.Region; 10 | 11 | import com.amazonaws.auth.CognitoCachingCredentialsProvider; 12 | import com.amazonaws.regions.Regions; 13 | import com.amazonaws.services.dynamodbv2.*; 14 | import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.*; 15 | 16 | import io.github.veeshostak.aichat.R; 17 | //import com.amazonaws.services.dynamodbv2.model.*; 18 | 19 | 20 | public class DynamoDBClientAndMapper { 21 | 22 | // private AmazonDynamoDBClient ddbClient; 23 | private DynamoDBMapper mapper; 24 | 25 | public DynamoDBClientAndMapper(Context theApplicationContext, CognitoCachingCredentialsProvider credentialsProvider) { 26 | 27 | // ============== 28 | // START aws 29 | 30 | // Initialize the Amazon Cognito credentials provider 31 | // CognitoCachingCredentialsProvider credentialsProvider = new CognitoCachingCredentialsProvider( 32 | // theApplicationContext, 33 | // theApplicationContext.getString(R.string.dynamodb_identity_pool_id), // Identity pool ID 34 | // Regions.US_WEST_2 // Region 35 | // ); 36 | 37 | 38 | //String identityId = credentialsProvider.getIdentityId(); 39 | //Log.d("LogTag", "my ID is " + identityId); 40 | 41 | // NOTE: the default endpoint of the created AmazonDynamoDBClient is 42 | // us-east-1, we must set the correct region 43 | 44 | //ddbClient = new AmazonDynamoDBClient(credentialsProvider); // uses the default, wrong region 45 | 46 | AmazonDynamoDBClient ddbClient = Region.getRegion(Regions.US_WEST_2) 47 | .createClient( 48 | AmazonDynamoDBClient.class, 49 | credentialsProvider, 50 | new ClientConfiguration() 51 | ); 52 | 53 | mapper = new DynamoDBMapper(ddbClient); 54 | 55 | // ============== 56 | // END aws 57 | } 58 | 59 | public DynamoDBMapper getMapper() { return mapper; } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/veeshostak/aichat/aws/dynamodb/model/User.java: -------------------------------------------------------------------------------- 1 | package io.github.veeshostak.aichat.aws.dynamodb.model; 2 | 3 | 4 | import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.*; 5 | import java.util.HashSet; 6 | 7 | 8 | // Users 9 | // Hash Key: userId 10 | // Range Key: string signUpDateTime 11 | // conversationIds (String-set) 12 | // other attributes... 13 | 14 | 15 | @DynamoDBTable(tableName = "Users") 16 | public class User { 17 | 18 | private String userId; 19 | private String signUpDateTime; 20 | 21 | private HashSet conversationIds; 22 | private String fullName; 23 | private String email; 24 | 25 | 26 | @DynamoDBHashKey(attributeName = "UserId") 27 | public String getUserId() { 28 | return userId; 29 | } 30 | 31 | public void setUserId(String userId) { this.userId = userId; } 32 | 33 | // ===== 34 | 35 | @DynamoDBAttribute(attributeName="SignUpDateTime") 36 | public String getSignUpDateTime() { return signUpDateTime; } 37 | 38 | public void setSignUpDateTime(String signUpDateTime) { this.signUpDateTime = signUpDateTime; } 39 | 40 | 41 | @DynamoDBAttribute(attributeName = "ConversationIds") 42 | public HashSet getConversationIds() { 43 | return conversationIds; 44 | } 45 | 46 | public void setConversationIds(HashSet conversationIds) { 47 | // shallow copy, not independent 48 | 49 | // don't use clone, clone is broken 50 | //this.conversation = (ArrayList)conversation.clone(); 51 | 52 | this.conversationIds = new HashSet(conversationIds); 53 | } 54 | 55 | 56 | @DynamoDBAttribute(attributeName = "FullName") 57 | public String getFullName() { return fullName; } 58 | 59 | public void setFullName(String fullName) { 60 | this.fullName = fullName; 61 | } 62 | 63 | @DynamoDBAttribute(attributeName = "Email") 64 | public String getEmail() { return email; } 65 | 66 | public void setEmail(String email) { 67 | this.email = email; 68 | } 69 | 70 | 71 | } 72 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/veeshostak/aichat/aws/dynamodb/model/UserConversation.java: -------------------------------------------------------------------------------- 1 | package io.github.veeshostak.aichat.aws.dynamodb.model; 2 | 3 | 4 | import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.*; 5 | import java.util.ArrayList; 6 | 7 | import io.github.veeshostak.aichat.database.entity.ChatPost; 8 | 9 | 10 | // User Conversations 11 | // Hash Key: string conversationId 12 | // Range Key: string conversationDateTime 13 | // userId: string userId 14 | // other attributes... 15 | 16 | @DynamoDBTable(tableName = "User-Conversations") 17 | public class UserConversation { 18 | private String conversationId; 19 | private String conversationDateTime; 20 | 21 | private String userId; 22 | private ArrayList conversation; 23 | 24 | 25 | @DynamoDBHashKey(attributeName = "ConversationId") 26 | public String getConversationId() { 27 | return conversationId; 28 | } 29 | 30 | public void setConversationId(String conversationId) { this.conversationId = conversationId; } 31 | 32 | 33 | @DynamoDBRangeKey(attributeName="ConversationDateTime") // sort 34 | public String getConversationDateTime() { return conversationDateTime; } 35 | 36 | public void setConversationDateTime(String conversationDateTime) { this.conversationDateTime = conversationDateTime; } 37 | 38 | 39 | // ===== 40 | 41 | 42 | @DynamoDBAttribute(attributeName = "UserId") 43 | public String getUserId() { return userId; } 44 | 45 | public void setUserId(String userId) { 46 | this.userId = userId; 47 | } 48 | 49 | @DynamoDBAttribute(attributeName = "Conversation") 50 | public ArrayList getConversation() { 51 | return conversation; 52 | } 53 | 54 | public void setConversation(ArrayList conversation) { 55 | // shallow copy, not independent 56 | 57 | // don't use clone, clone is broken 58 | //this.conversation = (ArrayList)conversation.clone(); 59 | 60 | this.conversation = new ArrayList(conversation); 61 | } 62 | 63 | 64 | 65 | 66 | 67 | 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/veeshostak/aichat/database/AppDatabase.java: -------------------------------------------------------------------------------- 1 | package io.github.veeshostak.aichat.database; 2 | 3 | import android.arch.persistence.room.Database; 4 | import android.arch.persistence.room.Room; 5 | import android.arch.persistence.room.RoomDatabase; 6 | import android.content.Context; 7 | 8 | import io.github.veeshostak.aichat.database.dao.ChatPostDao; 9 | import io.github.veeshostak.aichat.database.entity.ChatPost; 10 | 11 | /** 12 | * Created by vladshostak on 12/23/17. 13 | */ 14 | 15 | @Database(entities = {ChatPost.class}, version = 1) 16 | public abstract class AppDatabase extends RoomDatabase { 17 | // create Db and get an instance of it 18 | private static AppDatabase INSTANCE; 19 | 20 | public static AppDatabase getDatabase(Context context) { 21 | if (INSTANCE == null) { 22 | INSTANCE = 23 | Room.databaseBuilder(context.getApplicationContext(), 24 | AppDatabase.class, "user-chat-posts") 25 | .build(); 26 | } 27 | return INSTANCE; 28 | } 29 | 30 | // Model 31 | public abstract ChatPostDao chatPostDao(); 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/veeshostak/aichat/database/dao/ChatPostDao.java: -------------------------------------------------------------------------------- 1 | package io.github.veeshostak.aichat.database.dao; 2 | 3 | import android.arch.lifecycle.LiveData; 4 | import android.arch.persistence.room.Dao; 5 | import android.arch.persistence.room.Delete; 6 | import android.arch.persistence.room.Insert; 7 | import android.arch.persistence.room.OnConflictStrategy; 8 | import android.arch.persistence.room.Query; 9 | import android.arch.persistence.room.Update; 10 | 11 | import java.util.List; 12 | 13 | import io.github.veeshostak.aichat.database.entity.ChatPost; 14 | 15 | /** 16 | * Created by vladshostak on 12/23/17. 17 | */ 18 | 19 | // @Insert: Room generates an implementation that inserts all parameters into the database in a 20 | // single transaction. 21 | 22 | @Dao 23 | public interface ChatPostDao { 24 | 25 | @Insert(onConflict = OnConflictStrategy.ABORT) 26 | void insertChatPosts(ChatPost... chatPosts); // array of chatPosts (0 or more) 27 | 28 | @Update 29 | void updateChatPosts(ChatPost... chatPosts); 30 | 31 | @Delete // uses the primary keys to find the entities to delete. 32 | void deleteChatPosts(ChatPost... chatPosts); 33 | 34 | // Queries: 35 | // Each @Query method is verified at compile time. (Room also verifies the return value of the query) 36 | 37 | @Query("SELECT * FROM chat_posts") 38 | LiveData> getAllChatPosts(); 39 | 40 | @Query("SELECT * FROM chat_posts WHERE pushed_to_remote_db = 0") 41 | LiveData> getAllChatPostsNotInRemoteDb(); 42 | 43 | @Query("DELETE FROM chat_posts") 44 | void deleteAllChatPosts(); 45 | 46 | // @Query("SELECT * FROM chat_posts WHERE user_query = :userQuery LIMIT 1") 47 | // ChatPost getChatPostWithUserQuery(String userQuery); 48 | 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/veeshostak/aichat/database/entity/ChatPost.java: -------------------------------------------------------------------------------- 1 | package io.github.veeshostak.aichat.database.entity; 2 | 3 | /** 4 | * Created by vladshostak on 12/23/17. 5 | */ 6 | 7 | import android.arch.persistence.room.ColumnInfo; 8 | import android.arch.persistence.room.Entity; 9 | import android.arch.persistence.room.PrimaryKey; 10 | 11 | 12 | @Entity(tableName = "chat_posts") 13 | public class ChatPost { 14 | 15 | @PrimaryKey(autoGenerate=true) 16 | @ColumnInfo(name = "id") 17 | private int id; 18 | 19 | @ColumnInfo(name = "user_query") 20 | private String userQuery; 21 | 22 | @ColumnInfo(name = "response") 23 | private String response; 24 | 25 | @ColumnInfo(name = "created_at") 26 | private String createdAt; 27 | 28 | @ColumnInfo(name = "pushed_to_remote_db") 29 | private Boolean pushedToRemoteDb; 30 | 31 | 32 | public int getId() { 33 | return id; 34 | } 35 | 36 | public void setId(int id) { 37 | this.id = id; 38 | } 39 | 40 | public String getUserQuery() { 41 | return userQuery; 42 | } 43 | 44 | public void setUserQuery(String userQuery) { 45 | this.userQuery = userQuery; 46 | } 47 | 48 | public String getResponse() { 49 | return response; 50 | } 51 | 52 | public void setResponse(String response) { 53 | this.response = response; 54 | } 55 | 56 | public String getCreatedAt() { 57 | return createdAt; 58 | } 59 | 60 | public void setCreatedAt(String createdAt) { 61 | this.createdAt = createdAt; 62 | } 63 | 64 | public Boolean getPushedToRemoteDb() { 65 | return pushedToRemoteDb; 66 | } 67 | 68 | public void setPushedToRemoteDb(Boolean pushedToRemoteDb) { 69 | this.pushedToRemoteDb = pushedToRemoteDb; 70 | } 71 | } 72 | 73 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/veeshostak/aichat/models/ChatMessage.java: -------------------------------------------------------------------------------- 1 | package io.github.veeshostak.aichat.models; 2 | 3 | 4 | public class ChatMessage { 5 | private boolean isMe; 6 | private String message; 7 | private String createdAt; 8 | private String userName; 9 | 10 | public String getUserName() { 11 | return userName; 12 | } 13 | 14 | public void setUserName(String userName) { 15 | this.userName = userName; 16 | } 17 | 18 | public boolean getIsMe() { 19 | return isMe; 20 | } 21 | 22 | public void setIsMe(boolean isMe) { 23 | this.isMe = isMe; 24 | } 25 | 26 | public String getMessage() { 27 | return message; 28 | } 29 | 30 | public void setMessage(String message) { 31 | this.message = message; 32 | } 33 | 34 | public String getCreatedAt() { return createdAt; } 35 | 36 | public void setCreatedAt(String createdAt) { this.createdAt = createdAt; } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/veeshostak/aichat/recyclerview/adapter/MessageListAdapter.java: -------------------------------------------------------------------------------- 1 | package io.github.veeshostak.aichat.recyclerview.adapter; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | import io.github.veeshostak.aichat.R; 13 | import io.github.veeshostak.aichat.models.ChatMessage; 14 | import io.github.veeshostak.aichat.recyclerview.viewholder.ReceivedMessageHolder; 15 | import io.github.veeshostak.aichat.recyclerview.viewholder.SentMessageHolder; 16 | 17 | /** 18 | * Created by vladshostak on 12/28/17. 19 | */ 20 | 21 | public class MessageListAdapter extends RecyclerView.Adapter { 22 | 23 | private static final int VIEW_TYPE_MESSAGE_SENT = 1; 24 | private static final int VIEW_TYPE_MESSAGE_RECEIVED = 2; 25 | 26 | private Context mContext; 27 | private List mMessageList; 28 | 29 | public MessageListAdapter(Context context, List messageList) { 30 | mContext = context; 31 | if (messageList == null) { 32 | mMessageList = new ArrayList(); 33 | } else { 34 | mMessageList = messageList; 35 | } 36 | } 37 | 38 | public void add(ChatMessage message) { 39 | mMessageList.add(message); 40 | notifyDataSetChanged(); 41 | } 42 | 43 | public void addItems(List chatMessages) { 44 | this.mMessageList = chatMessages; 45 | notifyDataSetChanged(); 46 | } 47 | 48 | public void clearMessages() { 49 | mMessageList.clear(); 50 | } 51 | 52 | @Override 53 | public int getItemCount() { 54 | return mMessageList.size(); 55 | } 56 | 57 | // Return the view type of the item at position for the purposes of view recycling. 58 | @Override 59 | public int getItemViewType(int position) { 60 | ChatMessage message = (ChatMessage) mMessageList.get(position); 61 | 62 | // Determines the appropriate ViewType according to the sender of the message. 63 | if (message.getIsMe()) { 64 | // If the current user is the sender of the message 65 | return VIEW_TYPE_MESSAGE_SENT; 66 | } else { 67 | // If some other user sent the message 68 | return VIEW_TYPE_MESSAGE_RECEIVED; 69 | } 70 | } 71 | 72 | /* The onCreateViewHolder() function is where a new, empty view (wrapped by a 73 | RecyclerView.ViewHolder) is created and added to the pool of views. */ 74 | // Inflates the appropriate layout according to the ViewType. 75 | @Override 76 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 77 | View view; 78 | if (viewType == VIEW_TYPE_MESSAGE_SENT) { 79 | view = LayoutInflater.from(parent.getContext()) 80 | .inflate(R.layout.item_message_sent, parent, false); 81 | return new SentMessageHolder(view); 82 | } else if (viewType == VIEW_TYPE_MESSAGE_RECEIVED) { 83 | view = LayoutInflater.from(parent.getContext()) 84 | .inflate(R.layout.item_message_recieved, parent, false); 85 | return new ReceivedMessageHolder(view); 86 | } 87 | return null; 88 | } 89 | 90 | // The onBindViewHolder() function gets a view from the empty pool and populates this view 91 | // using the data you supplied to the adapter. 92 | 93 | // Passes the message object to a ViewHolder so that the contents can be bound to UI. 94 | @Override 95 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 96 | ChatMessage message = (ChatMessage) mMessageList.get(position); 97 | 98 | switch (holder.getItemViewType()) { 99 | case VIEW_TYPE_MESSAGE_SENT: 100 | ((SentMessageHolder) holder).bind(message); 101 | break; 102 | case VIEW_TYPE_MESSAGE_RECEIVED: 103 | ((ReceivedMessageHolder) holder).bind(message); 104 | break; 105 | } 106 | } 107 | 108 | // You can use the onViewRecycled() method to perform specific actions like setting an 109 | // ImageView's bitmap to null (on detach) in order to reduce memory usage. 110 | 111 | } 112 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/veeshostak/aichat/recyclerview/viewholder/ReceivedMessageHolder.java: -------------------------------------------------------------------------------- 1 | package io.github.veeshostak.aichat.recyclerview.viewholder; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.text.format.DateUtils; 5 | import android.view.View; 6 | import android.widget.ImageView; 7 | import android.widget.TextView; 8 | 9 | import io.github.veeshostak.aichat.R; 10 | import io.github.veeshostak.aichat.models.ChatMessage; 11 | 12 | /** 13 | * Created by vladshostak on 12/28/17. 14 | */ 15 | 16 | public class ReceivedMessageHolder extends RecyclerView.ViewHolder { 17 | 18 | TextView messageText, timeText, nameText; 19 | ImageView profileImage; 20 | 21 | public ReceivedMessageHolder(View itemView) { 22 | super(itemView); 23 | 24 | messageText = (TextView) itemView.findViewById(R.id.message_body); 25 | timeText = (TextView) itemView.findViewById(R.id.message_time); 26 | nameText = (TextView) itemView.findViewById(R.id.message_name); 27 | //profileImage = (ImageView) itemView.findViewById(R.id.message_image_profile); 28 | } 29 | 30 | 31 | /* 32 | we implement a bind(object) method within the ViewHolder class. This gives view binding to the 33 | ViewHolder class rather than to onBindViewHolder. It therefore produces cleaner code amidst 34 | multiple ViewHolders and ViewTypes. It also allows us to add easily OnClickListeners, if necessary. 35 | */ 36 | public void bind(ChatMessage message) { 37 | messageText.setText(message.getMessage()); 38 | 39 | // Format the stored timestamp into a readable String using method. 40 | //timeText.setText(DateUtils.formatDateTime(messageText.getContext(), message.getCreatedAt(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NUMERIC_DATE)); // pass UTC milliseconds 41 | 42 | timeText.setText(message.getCreatedAt()); 43 | nameText.setText("Fiona"); 44 | 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/veeshostak/aichat/recyclerview/viewholder/SentMessageHolder.java: -------------------------------------------------------------------------------- 1 | package io.github.veeshostak.aichat.recyclerview.viewholder; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.text.format.DateUtils; 5 | import android.view.View; 6 | import android.widget.ImageView; 7 | import android.widget.TextView; 8 | 9 | import io.github.veeshostak.aichat.R; 10 | import io.github.veeshostak.aichat.models.ChatMessage; 11 | 12 | /** 13 | * Created by vladshostak on 12/28/17. 14 | */ 15 | 16 | public class SentMessageHolder extends RecyclerView.ViewHolder { 17 | 18 | TextView messageText, timeText; 19 | //TextView nameText; 20 | //ImageView profileImage; 21 | 22 | public SentMessageHolder(View itemView) { 23 | super(itemView); 24 | 25 | messageText = (TextView) itemView.findViewById(R.id.message_body); 26 | timeText = (TextView) itemView.findViewById(R.id.message_time); 27 | //profileImage = (ImageView) itemView.findViewById(R.id.message_image_profile); 28 | //nameText = (TextView) itemView.findViewById(R.id.message_name); 29 | 30 | // Set Listeners 31 | // ability to delete a single message? 32 | //messageText.setOnClickListener(this); 33 | 34 | //itemView.setOnClickListener(this); 35 | } 36 | 37 | // onClick(v: View) ... 38 | 39 | 40 | /* 41 | we implement a bind(object) method within the ViewHolder class. This gives view binding to the 42 | ViewHolder class rather than to onBindViewHolder. It therefore produces cleaner code amidst 43 | multiple ViewHolders and ViewTypes. It also allows us to add easily OnClickListeners, if necessary. 44 | */ 45 | public void bind(ChatMessage message) { 46 | messageText.setText(message.getMessage()); 47 | 48 | // Format the stored timestamp into a readable String using method. 49 | //timeText.setText(DateUtils.formatDateTime(messageText.getContext(), message.getCreatedAt(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NUMERIC_DATE)); // pass UTC milliseconds 50 | 51 | //nameText.setText(message.getUseName()); 52 | timeText.setText(message.getCreatedAt()); 53 | 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/veeshostak/aichat/utils/Installation.java: -------------------------------------------------------------------------------- 1 | package io.github.veeshostak.aichat.utils; 2 | 3 | import android.content.Context; 4 | 5 | import java.io.File; 6 | import java.io.FileOutputStream; 7 | import java.io.IOException; 8 | import java.io.RandomAccessFile; 9 | import java.text.DateFormat; 10 | import java.util.Date; 11 | import java.util.Locale; 12 | import java.util.UUID; 13 | 14 | /** 15 | * Created by vladshostak on 8/22/17. 16 | */ 17 | 18 | public class Installation { 19 | private static String sID = null; 20 | private static final String INSTALLATION = "INSTALLATION"; 21 | 22 | public synchronized static String id(Context context) { 23 | if (sID == null) { 24 | File installation = new File(context.getFilesDir(), INSTALLATION); 25 | try { 26 | if (!installation.exists()) { 27 | // if file doesnt exist, creat the file, and write to it the ID 28 | writeInstallationFile(installation); 29 | } 30 | // get the ID from the file 31 | sID = readInstallationFile(installation); 32 | 33 | } catch (Exception e) { 34 | throw new RuntimeException(e); 35 | } 36 | } 37 | return sID; 38 | } 39 | 40 | private static String readInstallationFile(File installation) throws IOException { 41 | RandomAccessFile f = new RandomAccessFile(installation, "r"); 42 | byte[] bytes = new byte[(int) f.length()]; 43 | f.readFully(bytes); 44 | f.close(); 45 | return new String(bytes); 46 | } 47 | 48 | private static void writeInstallationFile(File installation) throws IOException { 49 | FileOutputStream out = new FileOutputStream(installation); 50 | 51 | 52 | //String currentDateTimeString = DateFormat.getDateTimeInstance().format(new Date()); 53 | 54 | // returns: 10/23/17 8:22AM 55 | String currentDateTimeString = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.US).format(new Date()); 56 | 57 | // format to 102317822AM 58 | currentDateTimeString = currentDateTimeString.replace("/", "").replace(" ", "").replace(":", ""); 59 | 60 | // uuid + datetime 61 | String id = UUID.randomUUID().toString() + currentDateTimeString; 62 | 63 | out.write(id.getBytes()); 64 | out.close(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/veeshostak/aichat/viewmodels/AddChatPostsViewModel.java: -------------------------------------------------------------------------------- 1 | package io.github.veeshostak.aichat.viewmodels; 2 | 3 | import android.app.Application; 4 | import android.arch.lifecycle.AndroidViewModel; 5 | import android.os.AsyncTask; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | import io.github.veeshostak.aichat.database.AppDatabase; 11 | import io.github.veeshostak.aichat.database.entity.ChatPost; 12 | 13 | /** 14 | * Created by vladshostak on 12/26/17. 15 | */ 16 | 17 | public class AddChatPostsViewModel extends AndroidViewModel{ 18 | 19 | private AppDatabase appDatabase; 20 | 21 | public AddChatPostsViewModel(Application application) { 22 | super(application); 23 | // get instance of our db 24 | appDatabase = AppDatabase.getDatabase(this.getApplication()); 25 | } 26 | 27 | // create asyncTask sicne doesnt use liveData 28 | public void addChatPosts(ChatPost... chatPost) { 29 | new AddChatPostsAsyncTask(appDatabase).execute(chatPost); 30 | } 31 | private static class AddChatPostsAsyncTask extends AsyncTask { 32 | private AppDatabase appDb; 33 | AddChatPostsAsyncTask(AppDatabase appDatabase) { 34 | appDb = appDatabase; 35 | } 36 | 37 | @Override 38 | protected Void doInBackground(ChatPost... params) { 39 | appDb.chatPostDao().insertChatPosts(params); 40 | return null; 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/veeshostak/aichat/viewmodels/DeleteAllChatPostsViewModel.java: -------------------------------------------------------------------------------- 1 | package io.github.veeshostak.aichat.viewmodels; 2 | 3 | import android.app.Application; 4 | import android.arch.lifecycle.AndroidViewModel; 5 | import android.arch.lifecycle.LiveData; 6 | import android.os.AsyncTask; 7 | 8 | import java.util.List; 9 | 10 | import io.github.veeshostak.aichat.database.AppDatabase; 11 | import io.github.veeshostak.aichat.database.entity.ChatPost; 12 | 13 | /** 14 | * Created by vladshostak on 12/26/17. 15 | */ 16 | 17 | public class DeleteAllChatPostsViewModel extends AndroidViewModel { 18 | 19 | private AppDatabase appDatabase; 20 | 21 | public DeleteAllChatPostsViewModel(Application application) { 22 | super(application); 23 | // get instance of our db 24 | appDatabase = AppDatabase.getDatabase(this.getApplication()); 25 | } 26 | // create asyncTask since doesnt use live data. 27 | public void deleteAllChatPosts() { 28 | new DeleteAllChatPostsAsyncTask(appDatabase).execute(); 29 | } 30 | private static class DeleteAllChatPostsAsyncTask extends AsyncTask { 31 | private AppDatabase appDb; 32 | DeleteAllChatPostsAsyncTask(AppDatabase appDatabase) { 33 | appDb = appDatabase; 34 | } 35 | @Override 36 | protected Void doInBackground(Void... voids) { 37 | appDb.chatPostDao().deleteAllChatPosts(); 38 | return null; 39 | } 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/veeshostak/aichat/viewmodels/ListAllChatPostsViewModel.java: -------------------------------------------------------------------------------- 1 | package io.github.veeshostak.aichat.viewmodels; 2 | 3 | import android.app.Application; 4 | import android.arch.lifecycle.AndroidViewModel; 5 | import android.arch.lifecycle.LiveData; 6 | import android.os.AsyncTask; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | import io.github.veeshostak.aichat.database.AppDatabase; 12 | import io.github.veeshostak.aichat.database.dao.ChatPostDao; 13 | import io.github.veeshostak.aichat.database.entity.ChatPost; 14 | 15 | /** 16 | * Created by vladshostak on 12/26/17. 17 | */ 18 | 19 | /* 20 | 21 | ViewModels do not contain code related to the UI. This helps in the decoupling of our app components. 22 | In Room, the database instance should ideally be contained in a ViewModel rather than on the Activity/Fragment. 23 | 24 | ViewModels are entities that are free of the Activity/Fragment lifecycle. 25 | For example, they can retain their state/data even during an orientation change. 26 | */ 27 | 28 | // If your ViewModel needs the application context, it must extend AndroidViewModel, else extend ViewModel 29 | public class ListAllChatPostsViewModel extends AndroidViewModel { 30 | 31 | private LiveData> chatPostList; 32 | 33 | private AppDatabase appDatabase; 34 | 35 | public ListAllChatPostsViewModel(Application application) { 36 | super(application); 37 | // get instance of our db 38 | appDatabase = AppDatabase.getDatabase(this.getApplication()); 39 | // Set ChatPosts. Live Data runs the query asynchronously on a background thread 40 | chatPostList = appDatabase.chatPostDao().getAllChatPosts(); 41 | } 42 | 43 | // Get chatPosts. Live Data runs the query asynchronously on a background thread 44 | public LiveData> getData() { 45 | if (chatPostList == null) { 46 | chatPostList = appDatabase.chatPostDao().getAllChatPosts(); 47 | } 48 | return chatPostList; 49 | } 50 | 51 | 52 | // delete doesnt use liveData, execute in another thread. (Live Data runs the query asynchronously on a background thread when needed) 53 | public void deleteItem(ChatPost toDeleteChatPost) { 54 | new DeleteAsyncTask(appDatabase).execute(toDeleteChatPost); 55 | } 56 | private static class DeleteAsyncTask extends AsyncTask { 57 | private AppDatabase appDb; 58 | DeleteAsyncTask(AppDatabase appDatabase) { 59 | appDb = appDatabase; 60 | } 61 | @Override 62 | protected Void doInBackground(final ChatPost... params) { 63 | appDb.chatPostDao().deleteChatPosts(params[0]); 64 | return null; 65 | } 66 | } 67 | 68 | 69 | } 70 | 71 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/veeshostak/aichat/viewmodels/ListNotInRemoteChatPostsViewModel.java: -------------------------------------------------------------------------------- 1 | package io.github.veeshostak.aichat.viewmodels; 2 | 3 | import android.app.Application; 4 | import android.arch.lifecycle.AndroidViewModel; 5 | import android.arch.lifecycle.LiveData; 6 | import android.os.AsyncTask; 7 | 8 | import java.util.List; 9 | 10 | import io.github.veeshostak.aichat.database.AppDatabase; 11 | import io.github.veeshostak.aichat.database.entity.ChatPost; 12 | 13 | /** 14 | * Created by vladshostak on 12/26/17. 15 | */ 16 | 17 | public class ListNotInRemoteChatPostsViewModel extends AndroidViewModel { 18 | 19 | private AppDatabase appDatabase; 20 | 21 | private LiveData> notInRemoteChatPostsList; 22 | 23 | public ListNotInRemoteChatPostsViewModel(Application application) { 24 | super(application); 25 | // get instance of our db 26 | appDatabase = AppDatabase.getDatabase(this.getApplication()); 27 | // Set ChatPosts. Live Data runs the query asynchronously on a background thread 28 | notInRemoteChatPostsList = appDatabase.chatPostDao().getAllChatPostsNotInRemoteDb(); 29 | } 30 | // Live Data runs the query asynchronously on a background thread 31 | public LiveData>getAllChatPostsNotInRemoteDb() { 32 | return appDatabase.chatPostDao().getAllChatPostsNotInRemoteDb(); 33 | } 34 | 35 | // Get chatPosts. Live Data runs the query asynchronously on a background thread 36 | public LiveData> getData() { 37 | if (notInRemoteChatPostsList == null) { 38 | notInRemoteChatPostsList = appDatabase.chatPostDao().getAllChatPostsNotInRemoteDb(); 39 | } 40 | return notInRemoteChatPostsList; 41 | } 42 | 43 | 44 | // delete doesnt use liveData, execute in another thread. (Live Data runs the query asynchronously on a background thread when needed) 45 | public void deleteItem(ChatPost toDeleteChatPost) { 46 | new ListNotInRemoteChatPostsViewModel.DeleteAsyncTask(appDatabase).execute(toDeleteChatPost); 47 | } 48 | private static class DeleteAsyncTask extends AsyncTask { 49 | private AppDatabase appDb; 50 | DeleteAsyncTask(AppDatabase appDatabase) { 51 | appDb = appDatabase; 52 | } 53 | @Override 54 | protected Void doInBackground(final ChatPost... params) { 55 | appDb.chatPostDao().deleteChatPosts(params[0]); 56 | return null; 57 | } 58 | } 59 | 60 | 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/veeshostak/aichat/viewmodels/UpdateChatPostsViewModel.java: -------------------------------------------------------------------------------- 1 | package io.github.veeshostak.aichat.viewmodels; 2 | 3 | import android.app.Application; 4 | import android.arch.lifecycle.AndroidViewModel; 5 | import android.os.AsyncTask; 6 | 7 | import io.github.veeshostak.aichat.database.AppDatabase; 8 | import io.github.veeshostak.aichat.database.entity.ChatPost; 9 | 10 | /** 11 | * Created by vladshostak on 12/26/17. 12 | */ 13 | 14 | public class UpdateChatPostsViewModel extends AndroidViewModel{ 15 | 16 | private AppDatabase appDatabase; 17 | 18 | public UpdateChatPostsViewModel(Application application) { 19 | super(application); 20 | // get instance of our db 21 | appDatabase = AppDatabase.getDatabase(this.getApplication()); 22 | } 23 | 24 | // create asyncTask sicne doesnt use liveData 25 | public void updateChatPosts(ChatPost... chatPost) { 26 | new UpdateChatPostsAsyncTask(appDatabase).execute(chatPost); 27 | } 28 | private static class UpdateChatPostsAsyncTask extends AsyncTask { 29 | private AppDatabase appDb; 30 | UpdateChatPostsAsyncTask(AppDatabase appDatabase) { 31 | appDb = appDatabase; 32 | } 33 | 34 | @Override 35 | protected Void doInBackground(ChatPost... params) { 36 | appDb.chatPostDao().updateChatPosts(params); 37 | return null; 38 | } 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/circle.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/fiona_chat_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VeeShostak/android-aws-architecture-components-dialogflow-chatbot/29c095c4bd476b757e456640bf1160fd599b8e01/app/src/main/res/drawable/fiona_chat_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/in_message_bg.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VeeShostak/android-aws-architecture-components-dialogflow-chatbot/29c095c4bd476b757e456640bf1160fd599b8e01/app/src/main/res/drawable/in_message_bg.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/out_message_bg.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VeeShostak/android-aws-architecture-components-dialogflow-chatbot/29c095c4bd476b757e456640bf1160fd599b8e01/app/src/main/res/drawable/out_message_bg.9.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_chat_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | 23 | 24 | 25 | 26 | 27 | 28 | 42 | 43 | 56 | 57 | 58 |