├── .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 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
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 |
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 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_sign_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
19 |
20 |
33 |
34 |
46 |
47 |
48 |
56 |
57 |
65 |
66 |
75 |
76 |
86 |
87 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_terms_of_service.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
14 |
15 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_message_recieved.xml:
--------------------------------------------------------------------------------
1 |
2 |
15 |
23 |
24 |
34 |
35 |
48 |
49 |
58 |
59 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_message_sent.xml:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
27 |
28 |
37 |
38 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/list_item_chat_message.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
11 |
12 |
19 |
20 |
29 |
30 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/app/src/main/res/log4j2.component.properties:
--------------------------------------------------------------------------------
1 | log4j2.loggerContextFactory=org.apache.logging.log4j.simple.SimpleLoggerContextFactory
2 | org.apache.logging.log4j.simplelog.level=DEBUG
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_chat.xml:
--------------------------------------------------------------------------------
1 |
5 |
7 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VeeShostak/android-aws-architecture-components-dialogflow-chatbot/29c095c4bd476b757e456640bf1160fd599b8e01/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_fiona.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VeeShostak/android-aws-architecture-components-dialogflow-chatbot/29c095c4bd476b757e456640bf1160fd599b8e01/app/src/main/res/mipmap-hdpi/ic_launcher_fiona.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VeeShostak/android-aws-architecture-components-dialogflow-chatbot/29c095c4bd476b757e456640bf1160fd599b8e01/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_fiona.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VeeShostak/android-aws-architecture-components-dialogflow-chatbot/29c095c4bd476b757e456640bf1160fd599b8e01/app/src/main/res/mipmap-mdpi/ic_launcher_fiona.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VeeShostak/android-aws-architecture-components-dialogflow-chatbot/29c095c4bd476b757e456640bf1160fd599b8e01/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_fiona.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VeeShostak/android-aws-architecture-components-dialogflow-chatbot/29c095c4bd476b757e456640bf1160fd599b8e01/app/src/main/res/mipmap-xhdpi/ic_launcher_fiona.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VeeShostak/android-aws-architecture-components-dialogflow-chatbot/29c095c4bd476b757e456640bf1160fd599b8e01/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_fiona.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VeeShostak/android-aws-architecture-components-dialogflow-chatbot/29c095c4bd476b757e456640bf1160fd599b8e01/app/src/main/res/mipmap-xxhdpi/ic_launcher_fiona.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VeeShostak/android-aws-architecture-components-dialogflow-chatbot/29c095c4bd476b757e456640bf1160fd599b8e01/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_fiona.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VeeShostak/android-aws-architecture-components-dialogflow-chatbot/29c095c4bd476b757e456640bf1160fd599b8e01/app/src/main/res/mipmap-xxxhdpi/ic_launcher_fiona.png
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #ed5739
4 | #ed4839
5 | #39cfed
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Fiona
3 | Settings
4 | ChatActivity
5 |
6 |
7 |
8 | \n \n
9 | Terms of Service \n
10 | EFFECTIVE DATE: October 24, 2017 \n \n
11 | These Fiona Terms of Service (the \"Terms\") are a legal contract between you and Fiona, Inc. (\"Fiona\", \"we\" or \"us\"). These Terms explain how you are permitted to use the Fiona mobile application and services, and any content therein (collectively, the \"Services\"). Unless otherwise specified, all references to the Services include the services available through the Fiona mobile application (the \"App\") and the Site, as well as any software that Fiona provides to you that allows you to access the Services from a mobile device and any other Materials (as defined below). By using the Services, you are agreeing to all of the Terms; if you do not agree with any of these Terms, do not access or otherwise use the Services.
12 | \n \n
13 | Changes \n
14 | Fiona may make changes to the Services at any time. Fiona can change, update, or add or remove provisions of these Terms at any time by posting the updated Terms on the App or the Site, by posting a notice on the Services or by notifying you via the App. By using the Services after Fiona has updated the Terms, you are agreeing to all the updated Terms; if you do not agree with any of the updated Terms, you must stop using Services.
15 | \n \n
16 | General Use \n
17 | The Services may not be available in all locations, and we may block access to the Services from certain locations based on your device’s geolocation information. We may add to or remove the areas in which the Services are not available at any time, without notice to you.
18 | By using, you represent, acknowledge and agree that you are at least 18 years of age. If you are not at least 18 years old, you may not use the Services at any time or in any manner or submit any information to the App, the Site or any part of the Services.
19 | We may, in our sole discretion, refuse to offer the Services to any person or entity. We may, without notice and in our sole discretion, terminate your right to use the Services, or any portion thereof, and block or prevent your future access to and use of the Services or any portion thereof.
20 | \n \n
21 | Services \n
22 | Fiona provides content through the Services that is copyrighted and/or trademarked work of Fiona or Fiona’s third-party licensors and suppliers or other users of the Services (collectively, the \"Materials\"). Materials may include logos, text graphics, video, images, photos, software and other content.
23 | Subject to these Terms, Fiona hereby grants you a limited, non-exclusive, non-sublicensable, non-transferable and revocable license to use and to display the Materials and to use the Services solely for your personal, non-commercial use. Except for the foregoing license, you have no other rights in the Services or any Materials and you may not modify, edit, copy, reproduce, create derivative works of, reverse engineer, alter, enhance or in any way exploit any of the Services or Materials in any manner.
24 | You may use the App for one registered account on one mobile device owned or leased solely by you, for your personal, non-commercial use. You may not: (i) modify, disassemble, decompile or reverse engineer the App, except to the extent that such restriction is expressly prohibited by law; (ii) rent, lease, loan, resell, sublicense, distribute or otherwise transfer the App to any third-party or use the App to provide time sharing or similar services for any third-party; (iii) make any copies of the App; (iv) remove, circumvent, disable, damage or otherwise interfere with security-related features of the App, features that prevent or restrict use or copying of any content accessible through the App, or features that enforce limitations on use of the App; or (v) delete the copyright and other proprietary rights notices on the App. You acknowledge that Fiona may from time to time issue upgraded versions of the App, and may automatically electronically upgrade the version of the App that you are using on your mobile device. You consent to such automatic upgrading on your mobile device, and agree that these Terms will apply to all such upgrades. Standard carrier data charges may apply to your use of the App.
25 | Except for the limited rights expressly licensed in these Terms, Fiona and its third-party licensors or suppliers retain all right, title, and interest in and to the Services.
26 | \n \n
27 | The following additional terms and conditions apply with respect to any App that Fiona provides to you designed for use on an Apple iOS-powered mobile device (an \"iOS App\"):
28 | You acknowledge that these Terms are between you and Fiona only, and not with Apple, Inc. (\"Apple\").
29 | Your use of Fiona’s iOS App must comply with Apple\'s then-current App Store Terms of Service.
30 | Fiona, and not Apple, are solely responsible for our iOS App and the Services and Content available thereon. You acknowledge that Apple has no obligation to provide maintenance and support services with respect to our iOS App. To the maximum extent permitted by applicable law, Apple will have no warranty obligation whatsoever with respect to our iOS App.
31 | You agree that Fiona, and not Apple, are responsible for addressing any claims by you or any third-party relating to our iOS App or your possession and/or use of our iOS App.
32 | You agree that Fiona, and not Apple, shall be responsible, to the extent required by these Terms, for the investigation, defense, settlement and discharge of any third-party intellectual property infringement claim related to our iOS App or your possession and use of our iOS App.
33 | You represent and warrant that (i) you are not located in a country that is subject to a U.S. Government embargo, or that has been designated by the U.S. Government as a \"terrorist supporting\" country; and (ii) You are not listed on any U.S. Government list of prohibited or restricted parties.
34 | You agree to comply with all applicable third-party terms of agreement when using our iOS App (e.g., you must not be in violation of your wireless data service terms of agreement when using the iOS App).
35 | The parties agree that Apple and Apple\'s subsidiaries are third-party beneficiaries to these Terms as they relate to your license of Fiona\'s iOS App. Upon your acceptance of these Terms, Apple will have the right (and will be deemed to have accepted the right) to enforce these Terms against you as they relate to your license of the iOS App as a third-party beneficiary thereof.
36 | If you breach any of these Terms, the above license will terminate automatically and you must immediately stop using the Services and destroy any downloaded or printed Materials.
37 | \n \n
38 | Account Security \n
39 | We may ask for your mobile number in order to verify your account when you try to post content or if we suspect improper activity on your account. Please note that your carrier\'s text messaging and data fees apply for mobile number verification.
40 | \n \n
41 | Privacy \n
42 | Please review the Fiona Privacy Policy (the \"Privacy Policy\"). By using our Services, you agree that we may use and disclose the information we collect from and about you as stated in the Privacy Policy (which is incorporated into these Terms). Without limiting the foregoing, you expressly acknowledge that any Submissions (defined below) or other information you submit to the Services may be viewable by all other users of the Services and any third party.
43 | Submissions
44 | You are responsible for any information, handles, profiles, statuses, opinions, messages, comments, photos, videos, graphics, sounds and other content or material that you submit, upload, post or otherwise make available on, through or in connection with the Services (each a \"Submission\") . When you provide Submissions you agree that those Submissions shall not be in violation of the \"Unauthorized Activities\" section below. Although Fiona may, in its sole discretion, monitor, screen, modify, refuse, remove or edit Submissions for any reason, Fiona is not obligated to do so. You have full responsibility for each Submission you make, including its legality, reliability and appropriateness and will be solely liable for any damage or harm resulting from your Submissions.
45 | We may retain your Submissions, even after they have expired from view within the Services or even after you have deleted them. However, you agree that we have no obligation to retain any Submission for any period of time, nor shall we be responsible or liable to you or any third party for any lost content or losses related to the expiration or deletion of a Submission.
46 | Unless otherwise explicitly stated herein or in the Privacy Policy, you agree that any Submission provided by you in connection with the Services is provided on a non-proprietary and non-confidential basis. You hereby grant to Fiona a non-exclusive, perpetual, irrevocable, royalty-free, fully paid-up, worldwide license (including the right to sublicense through multiple tiers) to use, reproduce, process, adapt, publicly perform, publicly display, modify, prepare derivative works, publish, transmit and distribute each of your Submissions, or any portion thereof, in any form, medium or distribution method now known or hereafter existing, known or developed, and authorize others to do the same (\"Submission License\"). Without limiting the foregoing, you agree that the Submission License gives Fiona the right to sublicense Submissions to third parties in connection with the syndication, broadcast, distribution, promotion, or publication of Submissions in any and all media or distribution methods, now known or later developed. No use of Submissions in accordance with the Submission License shall entitle you to any compensation from Fiona, or any third party. You agree to pay for all royalties, fees, damages and any other monies owing any person by reason of any Submissions posted by you to or through the Services.
47 | \n \n
48 | Unauthorized Activities \n
49 | When using the Services, you agree not to:
50 | Create a handle for the purpose of preventing others from using that handle.
51 | Sell or buy handles.
52 | Impersonate another person in a manner that is intended to or does mislead, confuse or deceive others.
53 | Post or share another individual\'s private information without their express authorization and permission.
54 | Defame, abuse, bully, harass, stalk, threaten, or otherwise violate the legal rights of others.
55 | Use racially or ethnically offensive language.
56 | Discuss or incite illegal activity.
57 | Post or share Submissions that contain pornography or graphic violence.
58 | Post or share anything that exploits children or minors or that depicts cruelty to animals.
59 | Post or share Submissions that violate any third party right, including any copyright, trademark, patent, trade secret, moral right, privacy right, right of publicity or any other intellectual property or proprietary right.
60 | Disseminate any unsolicited or unauthorized advertising, promotional materials, \'junk mail\', \'spam\', \'chain letters\', \'pyramid schemes\', or any other form of such solicitation.
61 | Use any robot, spider, crawler, scraper or other automated means to access the Services.
62 | Take any action that imposes an unreasonable or disproportionately large load on our infrastructure.
63 | Use or develop any third party applications that interact with the Services or Submissions without our prior written consent.
64 | Alter the opinions or comments posted by others on the Services.
65 | Post or share any image or language that is obscene, vulgar or offensive or that threatens, disparages or demeans any individual or group.
66 | Attempt to circumvent any of our content-filtering techniques.
67 | Post or share anything inappropriate or disruptive to the Services.
68 | Disrupt, negatively affect or inhibit users from having a positive experience with the Services.
69 | Use the Services in violation of these Terms and/or for any unlawful purposes.
70 | Post or share anything contrary to our public image, goodwill or reputation.
71 | This list of prohibitions provides examples and is not exhaustive or exclusive. Fiona reserves the right to (a) suspend or terminate access to your account and your ability to post to the Services (or otherwise use the Services), (b) delete, remove or refuse to distribute any Submissions and/or © reclaim handles, all with or without cause and with or without notice, for any reason or no reason without liability to you. If Fiona believes a Submission violates these Terms, it may make that Submission invisible to other users without notifying you. Your Submission will be visible to you, but will not appear for any other user. Fiona may report to law enforcement authorities any actions that may be illegal, and any reports it receives of such conduct. When legally required or at Fiona\'s discretion, Fiona will cooperate with law enforcement agencies in any investigation of alleged illegal activity on the Services or on the Internet.
72 | Unauthorized use of any Materials or Third-Party Content contained in the Services may violate certain laws and regulations. You agree to indemnify and hold Fiona and its officers, directors, employees, consultants, affiliates, agents, licensors, and business partners (collectively, the \"Indemnified Entities\") harmless from and against any and all costs, damages, liabilities, and expenses (including attorneys\' fees and costs of defense) Fiona or any other Indemnified Entity suffers in relation to, arising from, or for the purpose of avoiding, any claim or demand from a third party that your use of the Services or the use of the Services by any person using your user name and/or password violates any applicable law or regulation, or the copyrights, trademark rights or other rights of any third party.
73 | \n \n
74 | Disclaimer of Warranties \n
75 | Your use of the Services is at your own risk. The Materials may not have been verified or authenticated in whole or in part by Fiona, and they may include inaccuracies or typographical or other errors. Fiona does not warrant the accuracy of timeliness of the Materials contained on the Services. Fiona has no liability for any loss of, or errors or omissions in Submissions, or for any errors or omissions in the Materials or any other portion of the Services, whether provided by Fiona, our licensors or suppliers or other users.
76 | Fiona, FOR ITSELF AND ITS LICENSORS, MAKES NO EXPRESS, IMPLIED OR STATUTORY REPRESENTATIONS, WARRANTIES, OR GUARANTEES IN CONNECTION WITH THE SERVICES OR ANY MATERIALS RELATING TO THE QUALITY, SUITABILITY, TRUTH, ACCURACY OR COMPLETENESS OF ANY INFORMATION OR MATERIAL CONTAINED OR PRESENTED ON THE SERVICES. UNLESS OTHERWISE EXPLICITLY STATED, TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THE SERVICES, MATERIALS AND ANY OTHER PORTION OF THE SERVICES OR ANY INFORMATION OR MATERIAL CONTAINED OR PRESENTED ON THE SERVICES IS PROVIDED TO YOU ON AN \"AS IS,\" \"AS AVAILABLE\" BASIS WITH NO WARRANTY OF IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT OF THIRD PARTY RIGHTS. WITHOUT LIMITING THE FOREGOING, Fiona DOES NOT PROVIDE ANY WARRANTIES AGAINST VIRUSES, SPYWARE OR MALWARE THAT MAY BE INSTALLED ON YOUR COMPUTER.
77 | \n \n
78 | Limitation of Liability \n
79 | IN NO EVENT WILL Fiona BE LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE, INCIDENTAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES EVEN IF Fiona HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. WITHOUT LIMITING THE FOREGOING, Fiona SHALL NOT BE LIABLE TO YOU FOR ANY DAMAGES RESULTING FROM YOUR DISPLAYING, COPYING, OR DOWNLOADING ANY MATERIALS TO OR FROM THE SERVICES.
80 | YOU ASSUME TOTAL RESPONSIBILITY FOR YOUR USE OF THE SERVICES. YOUR ONLY REMEDY AGAINST Fiona FOR DISSATISFACTION WITH THE SERVICES IS TO STOP USING THE SERVICES. IF, NOTWITHSTANDING THESE TERMS, Fiona IS FOUND LIABLE TO YOU FOR ANY DAMAGE OR LOSS THAT ARISES OUT OF OR IS IN ANY WAY CONNECTED WITH YOUR USE OF THE SERVICES, Fiona\'S LIABILITY SHALL IN NO EVENT EXCEED ONE U.S. DOLLAR ($1.00) IN THE AGGREGATE.
81 | THE FOREGOING EXCLUSIONS AND LIMITATIONS SHALL APPLY TO THE EXTENT PERMITTED BY LAW. SOME JURISDICTIONS DO NOT ALLOW LIMITATIONS OF LIABILITY, SO THE FOREGOING EXCLUSIONS AND LIMITATIONS MAY NOT APPLY TO YOU.
82 | \n \n
83 | Local Laws; Export Control. \n
84 | Fiona controls and operates the Services from its headquarters in the United States and the Services may not be appropriate or available for use in other locations. You are responsible for following applicable laws and regulations in your jurisdiction, including but not limited to laws and regulations regarding the transmission of technical data exported from the United States or the country in which you reside.
85 | \n \n
86 | Feedback \n
87 | If you send or transmit any communications, comments, questions, suggestions, or related materials regarding Fiona or the Services, whether by email otherwise (collectively, \"Feedback\"), such Feedback is, and will be treated as, non-confidential and non-proprietary. You hereby assign all right, title, and interest in, and Fiona is free to use, without any attribution or compensation to you, any and all Feedback for any purpose whatsoever. You understand and agree that Fiona is not obligated to use, display, reproduce, or distribute any such ideas, know-how, concepts, or techniques contained in the Feedback, and you have no right to compel such use, display, reproduction, or distribution.
88 | \n \n
89 | General \n
90 | The applicable U.S. federal law and State law, without regard to the choice or conflicts of law provisions, will govern these Terms. Foreign laws do not apply. The United Nations on Contracts for the International Sale of Goods and any laws based on the Uniform Computer Information Transactions Act (UCITA) shall not apply to this Agreement. If any of these Terms is found to be inconsistent with applicable law, then such term shall be interpreted to reflect the intentions of the parties, and no other terms will be modified. Fiona\'s failure to enforce any of these Terms is not a waiver of such term. These Terms are the entire agreement between you and Fiona and supersede all prior or contemporaneous negotiations, discussions or agreements between you and Fiona about the Services. The proprietary rights, disclaimer of warranties, representations made by you, indemnities, limitations of liability and general provisions shall survive any termination of these Terms.
91 | \n \n
92 | Copyright Policy \n
93 | Fiona respects the intellectual property rights of others. If you believe that your intellectual property appears on the Services in violation of your copyright, please provide Fiona\'s designated agent the following information:
94 | A physical or electronic signature of a person authorized to act on behalf of the owner of an exclusive right that is allegedly infringed.
95 | Identification of the work claimed to have been infringed, or, if multiple works at a single online site are covered by a single notification, a representative list of such works at that site.
96 | Identification of the material that is claimed to be infringing or to be the subject of infringing activity and that is to be removed or access to which is to be disabled at the Services, and information reasonably sufficient to permit Fiona to locate the material.
97 | Information reasonably sufficient to permit Fiona to contact you as the complaining party, such as an address, telephone number, and, if available, an electronic mail address at which you may be contacted.
98 | A statement that you have a good faith belief that use of the material in the manner complained of is not authorized by the intellectual property owner, its agent, or the law.
99 | A statement that the information in the notification is accurate, and under penalty of perjury, that you are authorized to act on behalf of the owner of an exclusive right that is allegedly infringed.
100 | Fiona\'s agent for notice of claims of intellectual property infringement on the Services can be reached as follows:
101 |
102 | We will notify you that we have removed or disabled access to copyright-protected material that you provided, if such removal is pursuant to a valid DMCA take-down notice that we have received. If you receive such notice from us, you may provide us with a counter-notification in writing to Fiona that includes all of the following information:
103 | Your physical or electronic signature;
104 | Identification of the material that has been removed or to which access has been disabled, and the location at which the material appeared before it was removed or access to it was disabled;
105 | A statement from you under the penalty of perjury, that you have a good faith belief that the material was removed or disabled as a result of a mistake or misidentification; and
106 | Your name, physical address and telephone number, and a statement that you consent to the jurisdiction of a court for the judicial district in which your physical address is located, or if your physical address is outside of the United States, for any judicial district in which Fiona may be located, and that you will accept service of process from the person who provided notification of allegedly infringing material or an agent of such person.
107 |
108 |
109 |
110 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/io/github/veeshostak/aichat/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package io.github.veeshostak.aichat;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | maven {
7 | url 'https://maven.google.com/'
8 | name 'Google'
9 | }
10 | }
11 | dependencies {
12 | classpath 'com.android.tools.build:gradle:3.0.1'
13 |
14 | // NOTE: Do not place your application dependencies here; they belong
15 | // in the individual module build.gradle files
16 | }
17 | }
18 |
19 | allprojects {
20 | repositories {
21 | jcenter()
22 | maven {
23 | url 'https://maven.google.com/'
24 | name 'Google'
25 | }
26 | }
27 | }
28 |
29 | task clean(type: Delete) {
30 | delete rootProject.buildDir
31 | }
32 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VeeShostak/android-aws-architecture-components-dialogflow-chatbot/29c095c4bd476b757e456640bf1160fd599b8e01/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Dec 27 15:18:53 EST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/screenshots/Nexus-6P-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VeeShostak/android-aws-architecture-components-dialogflow-chatbot/29c095c4bd476b757e456640bf1160fd599b8e01/screenshots/Nexus-6P-1.png
--------------------------------------------------------------------------------
/screenshots/Nexus-6P-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VeeShostak/android-aws-architecture-components-dialogflow-chatbot/29c095c4bd476b757e456640bf1160fd599b8e01/screenshots/Nexus-6P-2.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------