diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..45ecce5 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,14 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "maven" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "daily" + target-branch: "develop" + reviewers: "aditmodhvadia" + assignees: "aditmodhvadia" diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..53671a8 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +ChatBot MET CS622 \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..ae78c11 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,113 @@ + + + + + +
+ + + + xmlns:android + + ^$ + + + +
+
+ + + + xmlns:.* + + ^$ + + + BY_NAME + +
+
+ + + + .*:id + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + .*:name + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + name + + ^$ + + + +
+
+ + + + style + + ^$ + + + +
+
+ + + + .* + + ^$ + + + BY_NAME + +
+
+ + + + .* + + http://schemas.android.com/apk/res/android + + + ANDROID_ATTRIBUTE_ORDER + +
+
+ + + + .* + + .* + + + BY_NAME + +
+
+
+
+
+
\ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..21e588e --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..b165b06 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 95da3c7..17e4a5f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,67 @@ -# MET-CS622-ChatBot-Project -An Android chat bot for MET CS622 final Project +# MET-CS622-ChatBot-Project (Chat Bot) +An Android chat bot for MET CS622 final Project which queries smartwatch database to give user insights on their activity. + +## Getting Started + +These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. See deployment for notes on how to deploy the project on a live system. + +### Prerequisites + +* Please fork the repository and work from it so that I can incoorporate the changes you make or if you add any new features. +* You need to have the googleservices.json file to link to firebase, you can set it up using [Set up Firebase](https://firebase.google.com/docs/android/setup) or email me on (adit.modhvadia@gmail.com) for further instructions. +* You need to clone the backend for this project as well which you can find here [MET-CS622-ChatBot-Backend](https://github.com/aditmodhvadia/MET-CS622-ChatBot-Project-Backend). Instructions on how to clone it are given below. + +### Installing + +* Fork and clone both the front end Android repo and the back end Java server. + * [MET-CS622-ChatBot-Android-App](https://github.com/aditmodhvadia/MET-CS622-ChatBot-Project.git) + * [MET-CS622-ChatBot-Backend](https://github.com/aditmodhvadia/MET-CS622-ChatBot-Project-Backend) +* Simply run the following command from your terminal to get the source code on your system in the desired directory. + +``` +git clone https://github.com//MET-CS622-ChatBot-Project.git +``` +``` +git clone https://github.com//MET-CS622-ChatBot-Project-Backend.git +``` + +* Or you can fork this repository and then create a pull request for implementing the changes + +* If you want to install the release apk on your android device then go to the release tab of the repo, and download the apk from the latest release. + +## Deployment + +* All releases would be ready to be isntalled on supported android devices +* This app is for education/research purpose only and hence won't be released on the playstore as of yet, prior announcement will be made. + +## Features +* Run queries to database via chat messages. +* Messages are stored both locally and on cloud. +* User authentication allows any user to log in and retrieve previously typed messages and responses. +* Built with MVVM hence easy to maintain, test and easy to pickup. + +## Built With + +* [Firebase](https://firebase.google.com/) - A comprehensive mobile development platform, go serverless with firebase +* [Maven](https://maven.apache.org/) - Dependency Management +* [Git](https://git-scm.com/downloads) - Used for version control +* [FastAndroidNetworking](https://github.com/amitshekhariitbhu/Fast-Android-Networking) - Used FAN API to call REST APIs +* [Timber](https://github.com/JakeWharton/timber) - Used for logging + +## Contributing + +Will update the requirements and code of conduct soon. + +## Versioning + +We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/aditmodhvadia/MET-CS622-ChatBot-Project/tags). + +## Authors + +* **Adit Modhvadia** - *Initial work* - [aditmodhvadia](https://github.com/aditmodhvadia/) + +See also the list of [contributors](https://github.com/aditmodhvadia/MET-CS622-ChatBot-Project/contributors) who participated in this project. + +## License + +This project is licensed under the GPL License - see the [LICENSE](https://github.com/aditmodhvadia/Canteen_App/blob/master/LICENSE) file for details diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..e4abdd2 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,63 @@ +apply plugin: 'com.android.application' +apply plugin: 'com.google.gms.google-services' +apply plugin: 'io.fabric' + +android { + compileSdkVersion 29 + buildToolsVersion "29.0.2" + defaultConfig { + applicationId "com.fazemeright.chatbotmetcs622" + minSdkVersion 22 + targetSdkVersion 29 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'com.google.android.material:material:1.0.0' + implementation 'com.jakewharton.timber:timber:4.7.1' // logging library + +// testing libraries + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + androidTestImplementation 'androidx.test:core:1.2.0' + androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation 'androidx.test:rules:1.2.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + // Testing-only dependencies +/* testImplementation 'androidx.test:core:1.2.0' + testImplementation 'androidx.test.ext:junit:1.1.1' + testImplementation 'androidx.test.espresso:espresso-core:3.2.0' + testImplementation 'androidx.test.espresso:espresso-intents:3.2.0'*/ + + // Room and Lifecycle dependencies + implementation "androidx.room:room-runtime:2.2.1" + annotationProcessor "androidx.room:room-compiler:2.2.1" + implementation "androidx.lifecycle:lifecycle-extensions:2.1.0" + +// WorkManager Library + implementation ("androidx.work:work-runtime:2.2.0") { + exclude group: 'com.google.guava', module: 'listenablefuture' + } + implementation 'com.google.guava:guava:27.0.1-android' + + +// Networking libraries + implementation 'com.google.code.gson:gson:2.8.6' + implementation 'com.amitshekhar.android:android-networking:1.0.2' + + + implementation project(':firebase-api-library') +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/androidTest/java/com/fazemeright/chatbotmetcs622/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/fazemeright/chatbotmetcs622/ExampleInstrumentedTest.java new file mode 100644 index 0000000..fcb058d --- /dev/null +++ b/app/src/androidTest/java/com/fazemeright/chatbotmetcs622/ExampleInstrumentedTest.java @@ -0,0 +1,27 @@ +package com.fazemeright.chatbotmetcs622; + +import android.content.Context; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertEquals; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + + assertEquals("com.fazemeright.chatbotmetcs622", appContext.getPackageName()); + } +} diff --git a/app/src/androidTest/java/com/fazemeright/chatbotmetcs622/ui/login/LoginActivityTest.java b/app/src/androidTest/java/com/fazemeright/chatbotmetcs622/ui/login/LoginActivityTest.java new file mode 100644 index 0000000..db3d688 --- /dev/null +++ b/app/src/androidTest/java/com/fazemeright/chatbotmetcs622/ui/login/LoginActivityTest.java @@ -0,0 +1,41 @@ +package com.fazemeright.chatbotmetcs622.ui.login; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.rule.ActivityTestRule; + +import com.fazemeright.chatbotmetcs622.R; +import com.fazemeright.chatbotmetcs622.ui.registration.RegistrationActivityTest; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard; +import static androidx.test.espresso.action.ViewActions.typeText; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static org.hamcrest.core.IsNot.not; + +@RunWith(AndroidJUnit4.class) +public class LoginActivityTest { + + @Rule + public ActivityTestRule mActivityRule = new ActivityTestRule<>(LoginActivity.class); + + @Test + public void login_with_email_password_isCorrect() { +// onView(withId(R.id.tvHaveAccount)).check(matches(isDisplayed())); +// onView(withId(R.id.tvHaveAccount)).perform(click()); + onView(withId(R.id.btnLogin)).check(matches(isDisplayed())); + onView(withId(R.id.userLoginEmailEditText)).perform(typeText(RegistrationActivityTest.CORRECT_EMAIL_ADDRESS)); + onView(withId(R.id.userPasswordEditText)).perform(typeText(RegistrationActivityTest.CORRECT_PASSWORD), closeSoftKeyboard()); + onView(withId(R.id.btnLogin)).perform(click()); +// TODO: Add IdlingResources to successfully run these tests + onView(withId(R.id.btnLogin)).check(matches(not(isDisplayed()))); +// openActionBarOverflowOrOptionsMenu(ApplicationProvider.getApplicationContext()); +// onView(withId(R.id.action_logout)).check(matches(withText("Logout"))); + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/fazemeright/chatbotmetcs622/ui/registration/RegistrationActivityTest.java b/app/src/androidTest/java/com/fazemeright/chatbotmetcs622/ui/registration/RegistrationActivityTest.java new file mode 100644 index 0000000..1a9b4b6 --- /dev/null +++ b/app/src/androidTest/java/com/fazemeright/chatbotmetcs622/ui/registration/RegistrationActivityTest.java @@ -0,0 +1,54 @@ +package com.fazemeright.chatbotmetcs622.ui.registration; + +import android.widget.EditText; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.rule.ActivityTestRule; + +import com.fazemeright.chatbotmetcs622.R; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard; +import static androidx.test.espresso.action.ViewActions.typeText; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static org.hamcrest.core.IsNot.not; +import static org.junit.Assert.assertEquals; + + +@RunWith(AndroidJUnit4.class) +public class RegistrationActivityTest { + + public static final String INCORRECT_EMAIL_ADDRESS = "abcd@gmail"; + public static final String CORRECT_EMAIL_ADDRESS = "unittesting@gmail.com"; + public static final String CORRECT_PASSWORD = "12345678"; + + @Rule + public ActivityTestRule mActivityRule = new ActivityTestRule<>(RegistrationActivity.class); + + @Test + public void incorrect_email_address_error() throws Exception { + String expected = mActivityRule.getActivity().getString(R.string.incorrect_email_err_msg); + onView(withId(R.id.userLoginEmailEditText)).perform(typeText(INCORRECT_EMAIL_ADDRESS)); + onView(withId(R.id.btnRegister)).perform(click()); + EditText etEmail = mActivityRule.getActivity().findViewById(R.id.userLoginEmailEditText); + assertEquals(etEmail.getError().toString(), expected); + } + + @Test + public void registration_flow_isCorrect() throws Exception { +// onView(withId(R.id.btnRegister)).check(matches(isDisplayed())); + onView(withId(R.id.userLoginEmailEditText)).perform(typeText(CORRECT_EMAIL_ADDRESS)); + onView(withId(R.id.userPasswordEditText)).perform(typeText(CORRECT_PASSWORD)); + onView(withId(R.id.userConPasswordEditText)).perform(typeText(CORRECT_PASSWORD), closeSoftKeyboard()); + onView(withId(R.id.btnRegister)).perform(click()); + onView(withId(R.id.btnRegister)).check(matches(not(isDisplayed()))); + } + +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/fazemeright/chatbotmetcs622/ui/splash/SplashActivityTest.java b/app/src/androidTest/java/com/fazemeright/chatbotmetcs622/ui/splash/SplashActivityTest.java new file mode 100644 index 0000000..f788332 --- /dev/null +++ b/app/src/androidTest/java/com/fazemeright/chatbotmetcs622/ui/splash/SplashActivityTest.java @@ -0,0 +1,31 @@ +package com.fazemeright.chatbotmetcs622.ui.splash; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.rule.ActivityTestRule; + +import com.fazemeright.chatbotmetcs622.BuildConfig; +import com.fazemeright.chatbotmetcs622.R; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static androidx.test.espresso.matcher.ViewMatchers.withText; + +@RunWith(AndroidJUnit4.class) +public class SplashActivityTest { + + @Rule + public ActivityTestRule mActivityRule = new ActivityTestRule<>(SplashActivity.class); + + @Test + public void display_app_version() throws Exception { + String expected = BuildConfig.VERSION_NAME; + onView(withId(R.id.tvAppVersion)).check(matches(isDisplayed())); + onView(withId(R.id.tvAppVersion)).check(matches(withText(expected))); + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..d40a3c1 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/fazemeright/chatbotmetcs622/ChatBotApp.java b/app/src/main/java/com/fazemeright/chatbotmetcs622/ChatBotApp.java new file mode 100644 index 0000000..236c612 --- /dev/null +++ b/app/src/main/java/com/fazemeright/chatbotmetcs622/ChatBotApp.java @@ -0,0 +1,46 @@ +package com.fazemeright.chatbotmetcs622; + +import android.app.Application; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.os.Build; + +import com.fazemeright.chatbotmetcs622.network.ApiManager; +import com.fazemeright.chatbotmetcs622.network.NetworkManager; + +import timber.log.Timber; + +public class ChatBotApp extends Application { + + public static final String CHANNEL_ID = "FireBaseSyncChannel"; + + @Override + public void onCreate() { + super.onCreate(); + if (BuildConfig.DEBUG) { + Timber.plant(new Timber.DebugTree()); + } + NetworkManager.getInstance().init(getApplicationContext(), 300); + + ApiManager.BaseUrl.setLocalIP("http://192.168.43.28:8080"); + + createNotificationChannel(); + } + + /** + * Call to create a notification channel for OS greater than OREO + */ + private void createNotificationChannel() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationChannel serviceChannel = new NotificationChannel( + CHANNEL_ID, + "FireBase Sync channel", + NotificationManager.IMPORTANCE_DEFAULT + ); + NotificationManager manager = getSystemService(NotificationManager.class); + if (manager != null) { + manager.createNotificationChannel(serviceChannel); + } + } + } +} diff --git a/app/src/main/java/com/fazemeright/chatbotmetcs622/database/BaseDao.java b/app/src/main/java/com/fazemeright/chatbotmetcs622/database/BaseDao.java new file mode 100644 index 0000000..79b8ba2 --- /dev/null +++ b/app/src/main/java/com/fazemeright/chatbotmetcs622/database/BaseDao.java @@ -0,0 +1,32 @@ +package com.fazemeright.chatbotmetcs622.database; + +import androidx.room.Delete; +import androidx.room.Insert; +import androidx.room.Update; + +public interface BaseDao { + + /** + * Insert an object in the database. + * + * @param element the object to be inserted. + */ + @Insert + void insert(T element); + + /** + * Update an object from the database. + * + * @param element the object to be updated + */ + @Update + void update(T element); + + /** + * Delete an object from the database + * + * @param element the object to be deleted + */ + @Delete + void deleteItem(T element); +} diff --git a/app/src/main/java/com/fazemeright/chatbotmetcs622/database/ChatBotDatabase.java b/app/src/main/java/com/fazemeright/chatbotmetcs622/database/ChatBotDatabase.java new file mode 100644 index 0000000..ce199ad --- /dev/null +++ b/app/src/main/java/com/fazemeright/chatbotmetcs622/database/ChatBotDatabase.java @@ -0,0 +1,32 @@ +package com.fazemeright.chatbotmetcs622.database; + +import android.content.Context; + +import androidx.room.Database; +import androidx.room.Room; +import androidx.room.RoomDatabase; + +import com.fazemeright.chatbotmetcs622.database.messages.Message; +import com.fazemeright.chatbotmetcs622.database.messages.MessageDao; + + +@Database(entities = {Message.class}, version = 1, exportSchema = false) +public abstract class ChatBotDatabase extends RoomDatabase { + + // singleton instance of Database + private static ChatBotDatabase INSTANCE; + + public static synchronized ChatBotDatabase getInstance(Context context) { + if (INSTANCE == null) { + INSTANCE = Room.databaseBuilder(context, ChatBotDatabase.class, "chat_bot_database") + // Wipes and rebuilds instead of migrating if no Migration object. + .fallbackToDestructiveMigration() + .build(); + } + + return INSTANCE; + } + + // List all daos here + public abstract MessageDao messageDao(); +} diff --git a/app/src/main/java/com/fazemeright/chatbotmetcs622/database/messages/Message.java b/app/src/main/java/com/fazemeright/chatbotmetcs622/database/messages/Message.java new file mode 100644 index 0000000..7f6ac31 --- /dev/null +++ b/app/src/main/java/com/fazemeright/chatbotmetcs622/database/messages/Message.java @@ -0,0 +1,100 @@ +package com.fazemeright.chatbotmetcs622.database.messages; + +import androidx.room.Entity; +import androidx.room.PrimaryKey; + +import java.io.Serializable; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +/** + * POJO for a message + */ +@Entity(tableName = "my_messages_table") +public class Message implements Serializable { + public static final String SENDER_USER = "User"; + /** + * mid of message + */ + @PrimaryKey(autoGenerate = true) + private long mid; + /** + * text of message + */ + private String msg; + /** + * sender of the message + */ + private String sender; + /** + * receiver of the message + */ + private String receiver; + /** + * mid of the chat room where message was sent + */ + private long chatRoomId; + /** + * timestamp of the message + */ + private long timestamp; + + public Message(long mid, String msg, String sender, String receiver, long chatRoomId, long timestamp) { + this.mid = mid; + this.msg = msg; + this.sender = sender; + this.receiver = receiver; + this.chatRoomId = chatRoomId; + this.timestamp = timestamp; + } + + public static Message newMessage(String msg, String sender, String receiver, long chatRoomId) { + return new Message(0, msg, sender, receiver, chatRoomId, System.currentTimeMillis()); + } + + public static Map getHashMap(Message message) { + Map messageHashMap = new HashMap<>(); + messageHashMap.put("mid", message.getMid()); + messageHashMap.put("msg", message.getMsg()); + messageHashMap.put("sender", message.getSender()); + messageHashMap.put("receiver", message.getReceiver()); + messageHashMap.put("chatRoomId", message.getChatRoomId()); + messageHashMap.put("timestamp", message.getTimestamp()); + return messageHashMap; + } + + public long getMid() { + return mid; + } + + public String getMsg() { + return msg; + } + + public String getSender() { + return sender; + } + + public String getReceiver() { + return receiver; + } + + public long getChatRoomId() { + return chatRoomId; + } + + public long getTimestamp() { + return timestamp; + } + + public String getFormattedTime() { + String pattern = "MM/dd HH:mm a"; + SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern, Locale.US); + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(timestamp); + return simpleDateFormat.format(cal.getTime()); + } +} diff --git a/app/src/main/java/com/fazemeright/chatbotmetcs622/database/messages/MessageDao.java b/app/src/main/java/com/fazemeright/chatbotmetcs622/database/messages/MessageDao.java new file mode 100644 index 0000000..6d7e319 --- /dev/null +++ b/app/src/main/java/com/fazemeright/chatbotmetcs622/database/messages/MessageDao.java @@ -0,0 +1,36 @@ +package com.fazemeright.chatbotmetcs622.database.messages; + +import androidx.room.Dao; +import androidx.room.FtsOptions; +import androidx.room.Insert; +import androidx.room.OnConflictStrategy; +import androidx.room.Query; + +import com.fazemeright.chatbotmetcs622.database.BaseDao; + +import java.util.List; + +@Dao +public interface MessageDao extends BaseDao { + + @Query("SELECT * from my_messages_table WHERE mid = :key") + Message get(long key); + + @Query("DELETE FROM my_messages_table") + void clear(); + + @Query("SELECT * FROM my_messages_table ORDER BY timestamp DESC") + List getAllMessages(); + + @Query("SELECT * FROM my_messages_table WHERE chatRoomId = :chatRoomId ORDER BY timestamp DESC") + List getAllMessagesFromChatRoom(long chatRoomId); + + @Query("DELETE from my_messages_table WHERE chatRoomId = :chatRoomId") + void clearChatRoomMessages(long chatRoomId); + + @Query("SELECT * FROM my_messages_table WHERE chatRoomId = :chatRoomId ORDER BY timestamp DESC LIMIT 1") + Message getLatestMessage(long chatRoomId); + + @Insert(onConflict = OnConflictStrategy.REPLACE) + void insertAllMessages(List order); +} diff --git a/app/src/main/java/com/fazemeright/chatbotmetcs622/intentservice/FireBaseIntentService.java b/app/src/main/java/com/fazemeright/chatbotmetcs622/intentservice/FireBaseIntentService.java new file mode 100644 index 0000000..269e23d --- /dev/null +++ b/app/src/main/java/com/fazemeright/chatbotmetcs622/intentservice/FireBaseIntentService.java @@ -0,0 +1,144 @@ +package com.fazemeright.chatbotmetcs622.intentservice; + +import android.app.IntentService; +import android.app.Notification; +import android.app.NotificationManager; +import android.content.Intent; +import android.os.Build; +import android.os.PowerManager; + +import androidx.annotation.Nullable; +import androidx.core.app.NotificationCompat; + +import com.fazemeright.chatbotmetcs622.ChatBotApp; +import com.fazemeright.chatbotmetcs622.R; +import com.fazemeright.chatbotmetcs622.database.ChatBotDatabase; +import com.fazemeright.chatbotmetcs622.database.messages.Message; +import com.fazemeright.chatbotmetcs622.repositories.MessageRepository; +import com.fazemeright.firebase_api_library.api.FireBaseApiManager; +import com.fazemeright.firebase_api_library.listeners.DBValueListener; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import timber.log.Timber; + +public class FireBaseIntentService extends IntentService { + + public static final String ACTION_ADD_MESSAGE = "AddMessage"; + public static final String ACTION_SYNC_MESSAGES = "SyncMessages"; + /** + * TAG for logs + */ + private static final String TAG = "FireBaseIntentService"; + /** + * Use to send data with intent + */ + public static final String ACTION = "IntentAction"; + public static final String MESSAGE = "Message"; + public static final String RESULT_RECEIVER = "ResultReceiver"; + + protected ChatBotDatabase database; + private PowerManager.WakeLock wakeLock; + private FireBaseApiManager fireBaseApiManager; + private MessageRepository messageRepository; + + /** + * Creates an IntentService. Invoked by your subclass's constructor. + *

+ * TAG Used to name the worker thread, important only for debugging. + */ + public FireBaseIntentService() { + super(TAG); + } + + @Override + public void onCreate() { + super.onCreate(); + Timber.i("onCreate"); + database = ChatBotDatabase.getInstance(getApplicationContext()); + PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE); + if (powerManager != null) { + wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ChatBot:WakeLockTag"); + wakeLock.acquire(60 * 1000); // acquire CPU + Timber.i("onCreate: Wake Lock acquired"); + } + showForegroundServiceNotification("Chat Bot", "Syncing Messages..."); + } + + @Override + public void onDestroy() { + super.onDestroy(); + Timber.i("onDestroy"); + wakeLock.release(); + Timber.i("onDestroy: Wake Lock released"); + + } + + @Override + protected void onHandleIntent(@Nullable Intent intent) { + Timber.d("onHandleIntent"); + if (intent != null) { + switch (intent.getStringExtra(ACTION)) { + case ACTION_ADD_MESSAGE: + addMessageToFireStore((Message) intent.getSerializableExtra(MESSAGE)); + break; + case ACTION_SYNC_MESSAGES: + syncMessages(); + break; + } + } + + } + + /** + * Call to sync messages from FireStore to Room for the logged in user + */ + private void syncMessages() { + /*try { + Thread.sleep(5000); // intentionally kept delay to show in presentation TODO: Remove afterwards + } catch (InterruptedException e) { + e.printStackTrace(); + }*/ + messageRepository.syncMessagesFromFireStoreToRoom(); + } + + /** + * Call to add given message to FireStore + * + * @param message given message + */ + private void addMessageToFireStore(Message message) { + messageRepository.addMessageToFireBase(Message.getHashMap(message)); + } + + /** + * Call to display foreground running notification to notify user of a background operation running + * + * @param title title to display in notification + * @param text text to display in notification + */ + private void showForegroundServiceNotification(String title, String text) { + Timber.i("Show notification called"); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + + Notification notification = new NotificationCompat.Builder(this, ChatBotApp.CHANNEL_ID) + .setContentTitle(title) + .setContentText(text) + .setSmallIcon(R.drawable.ic_launcher_foreground) + .setPriority(NotificationManager.IMPORTANCE_DEFAULT) + .build(); + + startForeground(1, notification); + } + } + + @Override + public void onStart(@Nullable Intent intent, int startId) { + super.onStart(intent, startId); + fireBaseApiManager = FireBaseApiManager.getInstance(); + messageRepository = MessageRepository.getInstance(getApplicationContext()); + } +} diff --git a/app/src/main/java/com/fazemeright/chatbotmetcs622/models/ChatRoom.java b/app/src/main/java/com/fazemeright/chatbotmetcs622/models/ChatRoom.java new file mode 100644 index 0000000..45e002d --- /dev/null +++ b/app/src/main/java/com/fazemeright/chatbotmetcs622/models/ChatRoom.java @@ -0,0 +1,48 @@ +package com.fazemeright.chatbotmetcs622.models; + +import java.io.Serializable; + +/** + * POJO to hold Chat room + */ +public class ChatRoom implements Serializable { + public static final int BRUTE_FORCE_ID = 0; + public static final int LUCENE_ID = 1; + public static final int MONGO_DB_ID = 2; + public static final int MY_SQL_ID = 3; + + public static final String MONGO_DB = "MongoDB"; + public static final String MY_SQL = "MySQL"; + public static final String LUCENE = "Lucene"; + public static final String BRUTE_FORCE = "Brute Force"; + /** + * is of the chat room + */ + private long id; + /** + * Name of the chat room + */ + private String name; + /** + * Id of resource file associated with the ChatRoom + */ + private int logoId; + + public ChatRoom(int id, String name, int logoId) { + this.id = id; + this.name = name; + this.logoId = logoId; + } + + public long getId() { + return id; + } + + public String getName() { + return name; + } + + public int getLogoId() { + return logoId; + } +} diff --git a/app/src/main/java/com/fazemeright/chatbotmetcs622/network/ApiManager.java b/app/src/main/java/com/fazemeright/chatbotmetcs622/network/ApiManager.java new file mode 100644 index 0000000..e18117e --- /dev/null +++ b/app/src/main/java/com/fazemeright/chatbotmetcs622/network/ApiManager.java @@ -0,0 +1,112 @@ +package com.fazemeright.chatbotmetcs622.network; + +import android.content.Context; + +import com.fazemeright.chatbotmetcs622.database.messages.Message; +import com.fazemeright.chatbotmetcs622.models.ChatRoom; +import com.fazemeright.chatbotmetcs622.network.handlers.NetworkCallback; +import com.fazemeright.chatbotmetcs622.network.handlers.NetworkWrapper; +import com.fazemeright.chatbotmetcs622.network.models.NetError; +import com.fazemeright.chatbotmetcs622.network.models.NetResponse; +import com.fazemeright.chatbotmetcs622.network.models.request.MessageQueryRequestModel; +import com.fazemeright.chatbotmetcs622.network.models.response.QueryResponseMessage; +import com.google.gson.reflect.TypeToken; + +public class ApiManager { + + private static ApiManager apiManager = null; + private NetworkManager networkManager; + + public static ApiManager getInstance() { + if (apiManager == null) { + apiManager = new ApiManager(); + } + return apiManager; + } + + /** + * Initialize base url,alias key and {@link NetworkWrapper} from application side, + * so that no need to pass base url in every user related network call. + * + * @param networkManager + */ + public void init(NetworkManager networkManager) { + this.networkManager = networkManager; + + } + + + /** + * Call Query API to backend to fetch results for the given Message query + * + * @param context context + * @param newMessage given message query + * @param networkCallback callback to listen to response + */ + public void queryDatabase(Context context, Message newMessage, + final NetworkCallback networkCallback) { + String serverEndPoint; + switch ((int) newMessage.getChatRoomId()) { + case ChatRoom.BRUTE_FORCE_ID: + serverEndPoint = DatabaseUrl.BRUTE_FORCE; + break; + case ChatRoom.LUCENE_ID: + serverEndPoint = DatabaseUrl.LUCENE; + break; + case ChatRoom.MONGO_DB_ID: + serverEndPoint = DatabaseUrl.MONGO_DB; + break; + default: + serverEndPoint = DatabaseUrl.MY_SQL; + break; + } + String url = BaseUrl.BASE_URL.concat(BaseUrl.BASE_APP_NAME).concat(serverEndPoint); + + MessageQueryRequestModel messageQuery = new MessageQueryRequestModel(newMessage.getMsg()); + + TypeToken typeToken = new TypeToken() { + }; + networkManager.makePostRequest(context, url, messageQuery, typeToken, "", new NetworkCallback() { + @Override + public void onSuccess(NetResponse response) { + networkCallback.onSuccess(response); + } + + @Override + public void onError(NetError error) { + networkCallback.onError(error); + } + }); + + } + + /** + * DatabaseUrl module Api sub url + */ + static class DatabaseUrl { + final static String MONGO_DB = "/mongodb"; + final static String LUCENE = "/lucene"; + final static String MY_SQL = "/mysql"; + final static String BRUTE_FORCE = "/bruteforce"; + + } + + /** + * baseURL model for all the BASE URL addresses + */ + public static class BaseUrl { + static String BASE_URL = "http://192.168.43.28:8080"; // Update the url as per your local ip address + final static String BASE_APP_NAME = "/MET_CS622_ChatBot_Backend_war_exploded"; + + /** + * Set the local IP address or the base url address for the server where the database back end is hosted. + * Also include the port. + * + * @param hostAddress ip address + */ + public static void setLocalIP(String hostAddress) { + BASE_URL = hostAddress; + } + + } +} diff --git a/app/src/main/java/com/fazemeright/chatbotmetcs622/network/NetworkManager.java b/app/src/main/java/com/fazemeright/chatbotmetcs622/network/NetworkManager.java new file mode 100644 index 0000000..6921e00 --- /dev/null +++ b/app/src/main/java/com/fazemeright/chatbotmetcs622/network/NetworkManager.java @@ -0,0 +1,498 @@ +package com.fazemeright.chatbotmetcs622.network; + + +import android.content.Context; + +import androidx.annotation.Nullable; + +import com.androidnetworking.AndroidNetworking; +import com.androidnetworking.common.Priority; +import com.androidnetworking.error.ANError; +import com.androidnetworking.interceptors.HttpLoggingInterceptor; +import com.androidnetworking.interfaces.ParsedRequestListener; +import com.fazemeright.chatbotmetcs622.R; +import com.fazemeright.chatbotmetcs622.network.handlers.NetworkCallback; +import com.fazemeright.chatbotmetcs622.network.handlers.NetworkWrapper; +import com.fazemeright.chatbotmetcs622.network.models.ChatBotError; +import com.fazemeright.chatbotmetcs622.network.models.NetCompoundRes; +import com.fazemeright.chatbotmetcs622.network.models.NetError; +import com.fazemeright.chatbotmetcs622.network.models.NetResponse; +import com.fazemeright.chatbotmetcs622.network.utils.CoreUtils; +import com.google.gson.reflect.TypeToken; + +import java.util.HashMap; +import java.util.concurrent.TimeUnit; + +import okhttp3.OkHttpClient; +import timber.log.Timber; + +public class NetworkManager implements NetworkWrapper { + + private final static String TAG = NetworkManager.class.getSimpleName(); + private static final String CONTENT_TYPE = "application/json; charset=utf-8"; + private static NetworkManager networkManager = null; + + public static NetworkManager getInstance() { + if (networkManager == null) { + networkManager = new NetworkManager(); + } + return networkManager; + } + + /** + * To get Http client + * + * @param requestTimeOut Network Request timeout in millisecond it's configurable from backend + * @return OkHttpClient + */ + public static OkHttpClient getHttpClient(int requestTimeOut) { + //if set < 2 second then we put our default timeout + + OkHttpClient.Builder builder = new OkHttpClient.Builder(); + builder.connectTimeout(requestTimeOut, TimeUnit.SECONDS); + builder.readTimeout(requestTimeOut, TimeUnit.SECONDS); + builder.writeTimeout(requestTimeOut, TimeUnit.SECONDS); + + return builder.build(); + } + + /** + * Initializing at the very first time + * Set Request Timeout + * Enabling network logging + * + * @param requestTimeOut Network Request timeout in millisecond it's configurable from backend + * @param context + */ + public void init(Context context, int requestTimeOut) { + initSecureClient(context, requestTimeOut); + } + + /** + * To initialize network manager + * + * @param context App context + * @param requestTimeOut Network Request timeout in millisecond it's configurable from backend + */ + private void initSecureClient(Context context, int requestTimeOut) { + AndroidNetworking.initialize(context, getHttpClient(requestTimeOut)); + } + + @Override + public void makeGetRequest(Context context, String url, TypeToken typeToken, String tag, + final NetworkCallback networkCallback) { + if (CoreUtils.isValidUrl(url)) { + if (CoreUtils.isNetworkAvailable(context)) { + printURLAndRequestParameters(url, null); + AndroidNetworking.get(url) + .setTag(tag) + .setPriority(Priority.MEDIUM) + .build() + .getAsParsed(typeToken, new ParsedRequestListener() { + @Override + public void onResponse(T response) { + Timber.i("onResponse :: %s", CoreUtils.getStringFromObject(response)); + NetResponse netResponse = new NetResponse(); + netResponse.setResponse(response); + networkCallback.onSuccess(netResponse); + } + + @Override + public void onError(ANError anError) { + Timber.e("onError :: %s", CoreUtils.getStringFromObject(anError)); + networkCallback.onError(getNetError(anError, null)); + } + }); + } else { + networkCallback.onError(getNetErrorConnectivityError(getConnectivityError(context), null)); + } + } else { + networkCallback.onError(getInvalidUrlError(url)); + } + } + + @Override + public void makePostRequest(Context context, String url, TypeToken typeToken, + HashMap hashMapHeader, String tag, + final NetworkCallback networkCallback) { + if (CoreUtils.isValidUrl(url)) { + if (CoreUtils.isNetworkAvailable(context)) { + AndroidNetworking.post(url) + .setContentType("application/x-www-form-urlencoded") // custom ContentType + .setTag(tag) + .addHeaders(hashMapHeader) + .setPriority(Priority.MEDIUM) + .build() + .getAsParsed(typeToken, new ParsedRequestListener() { + @Override + public void onResponse(T response) { + NetResponse netResponse = new NetResponse(); + netResponse.setResponse(response); + networkCallback.onSuccess(netResponse); + } + + @Override + public void onError(ANError anError) { + networkCallback.onError(getNetError(anError, null)); + } + }); + } else { + networkCallback.onError(getNetErrorConnectivityError(getConnectivityError(context), + null)); + } + } else { + networkCallback.onError(getInvalidUrlError(url)); + } + } + + + @Override + public void makeGetRequestHeader(Context context, String url, TypeToken typeToken, + HashMap hashMapHeader, String tag, + final NetworkCallback networkCallback) { + if (CoreUtils.isValidUrl(url)) { + if (CoreUtils.isNetworkAvailable(context)) { + printURLAndRequestParameters(url, null); + AndroidNetworking.get(url) + .setTag(tag) + .addHeaders(hashMapHeader) + .setPriority(Priority.MEDIUM) + .build() + .getAsParsed(typeToken, new ParsedRequestListener() { + @Override + public void onResponse(T response) { + Timber.i("onResponse :: %s", CoreUtils.getStringFromObject(response)); + NetResponse netResponse = new NetResponse(); + netResponse.setResponse(response); + networkCallback.onSuccess(netResponse); + } + + @Override + public void onError(ANError anError) { + Timber.e("onError :: %s", CoreUtils.getStringFromObject(anError)); + networkCallback.onError(getNetError(anError, null)); + } + }); + } else { + networkCallback.onError(getNetErrorConnectivityError(getConnectivityError(context), null)); + } + } else { + networkCallback.onError(getInvalidUrlError(url)); + } + } + + @Override + public void makePutRequestHeader(Context context, String url, final Object dataObject, + TypeToken typeToken, HashMap hashMapHeader, + String tag, final NetworkCallback networkCallback) { + if (CoreUtils.isValidUrl(url)) { + if (CoreUtils.isNetworkAvailable(context)) { + printURLAndRequestParameters(url, dataObject); + //.addBodyParameter(dataObject) + //.addStringBody(NetworkUtility.getStringFromObject(dataObject)) + AndroidNetworking.put(url) + .addApplicationJsonBody(dataObject) + .addHeaders(hashMapHeader) + .setContentType(CONTENT_TYPE) // custom ContentType + .setTag(tag) + .setPriority(Priority.MEDIUM) + .build() + .getAsParsed(typeToken, new ParsedRequestListener() { + @Override + public void onResponse(T response) { + // LogUtils.getInstance().printLog(TAG, "onResponse :: " + // + CoreUtils.getStringFromObject(response)); + NetResponse netResponse = new NetResponse(); + netResponse.setResponse(response); + networkCallback.onSuccess(netResponse); + } + + @Override + public void onError(ANError anError) { + // LogUtils.getInstance().printLog(TAG, "onError :: " + // + CoreUtils.getStringFromObject(anError)); + networkCallback.onError(getNetError(anError, dataObject)); + } + }); + } else { + networkCallback.onError(getNetErrorConnectivityError(getConnectivityError(context), dataObject)); + } + } else { + networkCallback.onError(getInvalidUrlError(url)); + } + } + + + @Override + public void makePostRequest(Context context, String url, final Object dataObject, TypeToken typeToken, + String tag, final NetworkCallback networkCallback) { + if (CoreUtils.isValidUrl(url)) { + if (CoreUtils.isNetworkAvailable(context)) { + printURLAndRequestParameters(url, dataObject); +//.addBodyParameter(dataObject) +//.addStringBody(NetworkUtility.getStringFromObject(dataObject)) + AndroidNetworking.post(url) + .addApplicationJsonBody(dataObject) +// .addHeaders(hashMapHeader) + .setContentType(CONTENT_TYPE) // custom ContentType + .setTag(tag) + .setPriority(Priority.MEDIUM) + .build() + .getAsParsed(typeToken, new ParsedRequestListener() { + @Override + public void onResponse(T response) { + // LogUtils.getInstance().printLog(TAG, "onResponse :: " + // + CoreUtils.getStringFromObject(response)); + NetResponse netResponse = new NetResponse(); + netResponse.setResponse(response); + networkCallback.onSuccess(netResponse); + } + + @Override + public void onError(ANError anError) { + // LogUtils.getInstance().printLog(TAG, "onError :: " + // + CoreUtils.getStringFromObject(anError)); + networkCallback.onError(getNetError(anError, dataObject)); + } + }); + } else { + networkCallback.onError(getNetErrorConnectivityError(getConnectivityError(context), dataObject)); + } + } else { + networkCallback.onError(getInvalidUrlError(url)); + } + } + + @Override + public void makePostStringRequest(Context context, String url, String data, TypeToken typeToken, + String tag, NetworkCallback networkCallback) { + + } + + @Override + public NetCompoundRes makePostRequestSync(Context context, String url, Object dataObject, + TypeToken typeToken, String tag) { + return null; + } + + @Override + public void makePostRequestHeader(Context context, String url, final Object dataObject, + TypeToken typeToken, HashMap hashMapHeader, + String tag, final NetworkCallback networkCallback) { + + if (CoreUtils.isValidUrl(url)) { + if (CoreUtils.isNetworkAvailable(context)) { + printURLAndRequestParameters(url, dataObject); +//.addBodyParameter(dataObject) +//.addStringBody(NetworkUtility.getStringFromObject(dataObject)) + AndroidNetworking.post(url) + .addApplicationJsonBody(dataObject) + .addHeaders(hashMapHeader) + .setContentType(CONTENT_TYPE) // custom ContentType + .setTag(tag) + .setPriority(Priority.MEDIUM) + .build() + .getAsParsed(typeToken, new ParsedRequestListener() { + @Override + public void onResponse(T response) { + // LogUtils.getInstance().printLog(TAG, "onResponse :: " + // + CoreUtils.getStringFromObject(response)); + NetResponse netResponse = new NetResponse(); + netResponse.setResponse(response); + networkCallback.onSuccess(netResponse); + } + + @Override + public void onError(ANError anError) { + // LogUtils.getInstance().printLog(TAG, "onError :: " + // + CoreUtils.getStringFromObject(anError)); + networkCallback.onError(getNetError(anError, dataObject)); + } + }); + } else { + networkCallback.onError(getNetErrorConnectivityError(getConnectivityError(context), dataObject)); + } + } else { + networkCallback.onError(getInvalidUrlError(url)); + } + } + + @Override + public void makeDeleteRequestHeader(Context context, String url, /*final Object dataObject,*/ + TypeToken typeToken, HashMap hashMapHeader, + String tag, final NetworkCallback networkCallback) { + + if (CoreUtils.isValidUrl(url)) { + if (CoreUtils.isNetworkAvailable(context)) { +// printURLAndRequestParameters(url, dataObject); +//.addBodyParameter(dataObject) +//.addStringBody(NetworkUtility.getStringFromObject(dataObject)) + AndroidNetworking.delete(url) +// .addApplicationJsonBody(dataObject) + .addHeaders(hashMapHeader) + .setContentType(CONTENT_TYPE) // custom ContentType + .setTag(tag) + .setPriority(Priority.MEDIUM) + .build() + .getAsParsed(typeToken, new ParsedRequestListener() { + @Override + public void onResponse(T response) { + // LogUtils.getInstance().printLog(TAG, "onResponse :: " + // + CoreUtils.getStringFromObject(response)); + NetResponse netResponse = new NetResponse(); + netResponse.setResponse(response); + networkCallback.onSuccess(netResponse); + } + + @Override + public void onError(ANError anError) { + // LogUtils.getInstance().printLog(TAG, "onError :: " + // + CoreUtils.getStringFromObject(anError)); + networkCallback.onError(getNetError(anError, null)); + } + }); + } else { + networkCallback.onError(getNetErrorConnectivityError(getConnectivityError(context), null)); + } + } else { + networkCallback.onError(getInvalidUrlError(url)); + } + } + + @Override + public void cancelRequest(String tag) { + + } + + /** + * To use only for get id_token,access_token and refresh_token when login with Social + * + * @param context + * @param url + * @param dataObject + * @param typeToken + * @param hashMapHeader + * @param tag + * @param networkCallback + * @param + */ + @Override + public void makeCustomPostForSocialRequest(Context context, String url, final String dataObject, + TypeToken typeToken, + HashMap hashMapHeader, String tag, + final NetworkCallback networkCallback) { + + String stringURL = url.concat("?grant_type=authorization_code&redirect_uri=rosedaleapp://login&client_id=2idjeho7u7717nur0uhb6kmuhj&") + .concat("code=").concat(dataObject).concat("&scope=email openid profile"); + + if (CoreUtils.isValidUrl(url)) { + if (CoreUtils.isNetworkAvailable(context)) { + printURLAndRequestParameters(stringURL, dataObject); + AndroidNetworking.post(stringURL) + .setContentType("application/x-www-form-urlencoded") // custom ContentType + .setTag(tag) + .setPriority(Priority.MEDIUM) + .build() + .getAsParsed(typeToken, new ParsedRequestListener() { + @Override + public void onResponse(T response) { + NetResponse netResponse = new NetResponse(); + netResponse.setResponse(response); + networkCallback.onSuccess(netResponse); + } + + @Override + public void onError(ANError anError) { + networkCallback.onError(getNetError(anError, dataObject)); + } + }); + } else { + networkCallback.onError(getNetErrorConnectivityError(getConnectivityError(context), dataObject)); + } + } else { + networkCallback.onError(getInvalidUrlError(url)); + } + } + + /** + * To create class for error from network/api + * + * @param anError + * @param requestObject + */ + private NetError getNetError(ANError anError, @Nullable Object requestObject) { + NetError netError = new NetError(anError.getMessage()); + netError.setErrorBody(anError.getErrorBody()); + netError.setErrorCode(anError.getErrorCode()); + netError.setErrorDetail(anError.getErrorDetail()); + netError.setErrorLocalizeMessage(anError.getLocalizedMessage()); + netError.setApiRequest(requestObject); + netError.setResponseErrorMessage(anError.getErrorBody()); + return netError; + } + + /** + * To create class for error from network/api + * + * @param anError + * @param requestObject + */ + private NetError getNetErrorConnectivityError(ANError anError, @Nullable Object requestObject) { + NetError netError = new NetError(anError.getMessage()); + netError.setErrorBody(anError.getErrorBody()); + netError.setErrorCode(anError.getErrorCode()); + netError.setErrorDetail(anError.getErrorDetail()); + netError.setErrorLocalizeMessage(anError.getLocalizedMessage()); + netError.setApiRequest(requestObject); + netError.setResponseErrorMessage(anError.getErrorBody()); + return netError; + } + + /** + * To get Connectivity Error + * + * @param context + * @return + */ + private ANError getConnectivityError(Context context) { + ANError anError = new ANError(context.getString(R.string.no_internet_connection_available)); + anError.setErrorCode(ChatBotError.ChatBotErrorCodes.INTERNET_NOT_AVAILABLE); + anError.setErrorBody(context.getString(R.string.no_internet_connection_available)); + return anError; + } + + /** + * To Print Request + * + * @param url + * @param data + */ + private void printURLAndRequestParameters(String url, Object data) { + Timber.i("url :: %s", url); + Timber.i("requestParameters :: %s", + CoreUtils.getStringFromObject(data)); + } + + + /** + * To get Invalid Url Error + * + * @param url + * @return + */ + private NetError getInvalidUrlError(String url) { + NetError anError = new NetError("Invalid url: " + url); + anError.setErrorCode(ChatBotError.ChatBotErrorCodes.INVALID_URL); + return anError; + } + + + private void enableAndroidNetworkingLogging(boolean enable) { + if (enable) { + AndroidNetworking.enableLogging(HttpLoggingInterceptor.Level.BODY); + } else { + AndroidNetworking.enableLogging(HttpLoggingInterceptor.Level.NONE); + } + } + +} diff --git a/app/src/main/java/com/fazemeright/chatbotmetcs622/network/handlers/NetworkCallback.java b/app/src/main/java/com/fazemeright/chatbotmetcs622/network/handlers/NetworkCallback.java new file mode 100644 index 0000000..dac9c4e --- /dev/null +++ b/app/src/main/java/com/fazemeright/chatbotmetcs622/network/handlers/NetworkCallback.java @@ -0,0 +1,25 @@ +package com.fazemeright.chatbotmetcs622.network.handlers; + + +import com.fazemeright.chatbotmetcs622.network.models.NetError; +import com.fazemeright.chatbotmetcs622.network.models.NetResponse; + +/* + * Interface for handling API response + * */ +public interface NetworkCallback { + + /** + * Interface method called on success of api call + * + * @param response {@link NetResponse} + */ + void onSuccess(NetResponse response); + + /** + * Interface method called on api/network failure + * + * @param error Error obj with error detail + */ + void onError(NetError error); +} \ No newline at end of file diff --git a/app/src/main/java/com/fazemeright/chatbotmetcs622/network/handlers/NetworkWrapper.java b/app/src/main/java/com/fazemeright/chatbotmetcs622/network/handlers/NetworkWrapper.java new file mode 100644 index 0000000..bb2ea71 --- /dev/null +++ b/app/src/main/java/com/fazemeright/chatbotmetcs622/network/handlers/NetworkWrapper.java @@ -0,0 +1,152 @@ +package com.fazemeright.chatbotmetcs622.network.handlers; + + +import android.content.Context; + +import com.fazemeright.chatbotmetcs622.network.models.NetCompoundRes; +import com.google.gson.reflect.TypeToken; + +import java.util.HashMap; + +/* + * Interface for calling API + * */ +public interface NetworkWrapper { + + /** + * API GET method Request + * + * @param context App context + * @param url Request URL + * @param typeToken {@link TypeToken} of the expected parsed object + * @param tag request tag + * @param networkCallback Network callback + */ + void makeGetRequest(Context context, String url, TypeToken typeToken, String tag, + NetworkCallback networkCallback); + + /** + * API POST method Request + * + * @param context App context + * @param url Request URL + * @param typeToken {@link TypeToken} of the expected parsed object + * @param tag request tag + * @param networkCallback Network callback + */ + void makePostRequest(Context context, String url, TypeToken typeToken, + HashMap hashMapHeader, String tag, + NetworkCallback networkCallback); + + /** + * API POST method Request + * + * @param context App context + * @param url Request URL + * @param dataObject Request body + * @param typeToken {@link TypeToken} of the expected parsed object + * @param tag request tag + * @param networkCallback Network callback + */ + void makePostRequest(Context context, String url, Object dataObject, TypeToken typeToken, + String tag, NetworkCallback networkCallback); + + /** + * API POST method Request + * + * @param context App context + * @param url Request URL + * @param data Request body + * @param typeToken {@link TypeToken} of the expected parsed object + * @param tag request tag + * @param networkCallback Network callback + */ + void makePostStringRequest(Context context, String url, String data, TypeToken typeToken, + String tag, NetworkCallback networkCallback); + + /** + * API sync POST method Request. Expect a response/error in {@link NetCompoundRes} + * + * @param context App context + * @param url Request URL + * @param dataObject Request body + * @param typeToken {@link TypeToken} of the expected parsed object + * @param tag request tag + * @return Compound response ( It contains success / error ). First check it using method + * isSuccess() to find whether response contains error or not + */ + NetCompoundRes makePostRequestSync(Context context, String url, Object dataObject, + TypeToken typeToken, String tag); + + /** + * API POST method Request with header + * + * @param context App context + * @param url Request URL + * @param dataObject Request body + * @param typeToken {@link TypeToken} of the expected parsed object + * @param hashMapHeader HashMap of request header + * @param tag request tag + * @param networkCallback Network callback + */ + void makePostRequestHeader(Context context, String url, Object dataObject, TypeToken typeToken, + HashMap hashMapHeader, String tag, + NetworkCallback networkCallback); + + /** + * API POST method Request with header + * + * @param context App context + * @param url Request URL + * @param typeToken {@link TypeToken} of the expected parsed object + * @param hashMapHeader HashMap of request header + * @param tag request tag + * @param networkCallback Network callback + */ + void makeDeleteRequestHeader(Context context, String url/*, Object dataObject*/, TypeToken typeToken, + HashMap hashMapHeader, String tag, + NetworkCallback networkCallback); + + + /** + * API Get method Request with header + * + * @param context App context + * @param url Request URL + * @param typeToken {@link TypeToken} of the expected parsed object + * @param hashMapHeader HashMap of request header + * @param tag request tag + * @param networkCallback Network callback + */ + void makeGetRequestHeader(Context context, String url, TypeToken typeToken, + HashMap hashMapHeader, String tag, + NetworkCallback networkCallback); + + /** + * API PUT Method Request with header + * + * @param context + * @param url + * @param dataObject + * @param typeToken + * @param hashMapHeader + * @param tag + * @param networkCallback + * @param + */ + void makePutRequestHeader(Context context, String url, Object dataObject, TypeToken typeToken, + HashMap hashMapHeader, String tag, + NetworkCallback networkCallback); + + /** + * Cancel api request by tag + * + * @param tag request tag + */ + void cancelRequest(String tag); + + void makeCustomPostForSocialRequest(Context context, String url, + String dataObject, TypeToken typeToken, + HashMap hashMapAuthenticate, String s, + NetworkCallback networkCallback); +} diff --git a/app/src/main/java/com/fazemeright/chatbotmetcs622/network/models/ChatBotError.java b/app/src/main/java/com/fazemeright/chatbotmetcs622/network/models/ChatBotError.java new file mode 100644 index 0000000..4e525c6 --- /dev/null +++ b/app/src/main/java/com/fazemeright/chatbotmetcs622/network/models/ChatBotError.java @@ -0,0 +1,60 @@ +package com.fazemeright.chatbotmetcs622.network.models; + + +import androidx.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + + + +public class ChatBotError { + + @Retention(RetentionPolicy.SOURCE) + @IntDef({ChatBotErrorCodes.INTERNET_NOT_AVAILABLE, /*SOMETHING_WENT_WRONG,*/ ChatBotErrorCodes.INVALID_URL, ChatBotErrorCodes.DATA_NOT_FOUND, ChatBotErrorCodes.BAD_REQUEST, ChatBotErrorCodes.UN_AUTHORIZED, ChatBotErrorCodes.UN_EXPECTED_SERVER_ERROR}) + public @interface ChatBotErrorCodes { + + /** + * Internet is not available + */ + public static final int INTERNET_NOT_AVAILABLE = 1001; + + + /** + * Invalid URL + */ + public static final int INVALID_URL = 1002; + + + /** + * Used for unknown error + */ + + public static final int SOMETHING_WENT_WRONG = -123456; + + /** + * Used for data not found error + */ + public static final int DATA_NOT_FOUND = 204; + + + /** + * Used for Bad request (Malformed Parameters or parameters missing) + */ + public static final int BAD_REQUEST = 400; + + + /** + * Used for Unauthorized (Authorization header is incorrec, log-in user again) + */ + public static final int UN_AUTHORIZED = 401; + + + /** + * Used for Unexpected server error (Error) + */ + public static final int UN_EXPECTED_SERVER_ERROR = 500; + + } + +} diff --git a/app/src/main/java/com/fazemeright/chatbotmetcs622/network/models/NetCompoundRes.java b/app/src/main/java/com/fazemeright/chatbotmetcs622/network/models/NetCompoundRes.java new file mode 100644 index 0000000..33867a1 --- /dev/null +++ b/app/src/main/java/com/fazemeright/chatbotmetcs622/network/models/NetCompoundRes.java @@ -0,0 +1,37 @@ +package com.fazemeright.chatbotmetcs622.network.models; + +/** + * To use when we want to have sync response from network manager + * + * @param + */ +public class NetCompoundRes { + + private boolean isSuccess; + private NetResponse netResponse; + private NetError netError; + + public void setSuccess(boolean success) { + isSuccess = success; + } + + public void setNetResponse(NetResponse netResponse) { + this.netResponse = netResponse; + } + + public void setNetError(NetError netError) { + this.netError = netError; + } + + public boolean isSuccess() { + return isSuccess; + } + + public NetResponse getNetResponse() { + return netResponse; + } + + public NetError getNetError() { + return netError; + } +} diff --git a/app/src/main/java/com/fazemeright/chatbotmetcs622/network/models/NetError.java b/app/src/main/java/com/fazemeright/chatbotmetcs622/network/models/NetError.java new file mode 100644 index 0000000..7e00820 --- /dev/null +++ b/app/src/main/java/com/fazemeright/chatbotmetcs622/network/models/NetError.java @@ -0,0 +1,114 @@ +package com.fazemeright.chatbotmetcs622.network.models; + + +import android.text.TextUtils; + +import org.json.JSONException; +import org.json.JSONObject; + +/* + * Class for handling Network/API related errors + * */ +public class NetError extends Exception { + + private String errorBody = ""; + private int errorCode = ChatBotError.ChatBotErrorCodes.SOMETHING_WENT_WRONG; + private String errorDetail = ""; + private String errorLocalizeMessage = ""; + private Object apiRequest; + private String requestName = ""; + private String responseErrorMessage; + + public String getResponseErrorMessage() { + return responseErrorMessage; + } + + public void setResponseErrorMessage(String responseErrorMessage) { + this.responseErrorMessage = parseJson(responseErrorMessage); + } + + private String parseJson(String responseErrorMessage) { + String errorMessage = "Something went wrong!"; + if (!TextUtils.isEmpty(responseErrorMessage)) { + try { + JSONObject jsonObject = new JSONObject(responseErrorMessage); + if (jsonObject.has("responseMessage")) { + errorMessage = jsonObject.getString("responseMessage"); + } else { + return responseErrorMessage; + } + } catch (JSONException e) { + e.printStackTrace(); + return responseErrorMessage; + } + } + return errorMessage; + + } + + public String getRequestName() { + return requestName; + } + + public void setRequestName(String requestName) { + if (requestName != null) { + this.requestName = requestName; + } + } + + public Object getApiRequest() { + return apiRequest; + } + + public void setApiRequest(Object apiRequest) { + this.apiRequest = apiRequest; + } + + public String getErrorLocalizeMessage() { + return errorLocalizeMessage; + } + + public void setErrorLocalizeMessage(String errorLocalizeMessage) { + if (errorLocalizeMessage != null) { + this.errorLocalizeMessage = errorLocalizeMessage; + } + } + + public NetError(String message) { + super(message); + if (message == null) { + message = "Getting null error message."; + } + setErrorLocalizeMessage(message); + setErrorBody(message); + setErrorDetail(message); + } + + public void setErrorDetail(String errorDetail) { + if (errorDetail != null) { + this.errorDetail = errorDetail; + } + } + + public String getErrorDetail() { + return this.errorDetail; + } + + public void setErrorCode(int errorCode) { + this.errorCode = errorCode; + } + + public int getErrorCode() { + return this.errorCode; + } + + public String getErrorBody() { + return errorBody; + } + + public void setErrorBody(String errorBody) { + if (errorBody != null) { + this.errorBody = errorBody; + } + } +} diff --git a/app/src/main/java/com/fazemeright/chatbotmetcs622/network/models/NetResponse.java b/app/src/main/java/com/fazemeright/chatbotmetcs622/network/models/NetResponse.java new file mode 100644 index 0000000..3ad6b07 --- /dev/null +++ b/app/src/main/java/com/fazemeright/chatbotmetcs622/network/models/NetResponse.java @@ -0,0 +1,20 @@ +package com.fazemeright.chatbotmetcs622.network.models; + +import com.google.gson.annotations.SerializedName; + +/** + * Class for handling generic response + */ +public class NetResponse { + + @SerializedName("response") + private T response; + + public T getResponse() { + return response; + } + + public void setResponse(T response) { + this.response = response; + } +} diff --git a/app/src/main/java/com/fazemeright/chatbotmetcs622/network/models/request/MessageQueryRequestModel.java b/app/src/main/java/com/fazemeright/chatbotmetcs622/network/models/request/MessageQueryRequestModel.java new file mode 100644 index 0000000..85d8bb9 --- /dev/null +++ b/app/src/main/java/com/fazemeright/chatbotmetcs622/network/models/request/MessageQueryRequestModel.java @@ -0,0 +1,22 @@ +package com.fazemeright.chatbotmetcs622.network.models.request; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class MessageQueryRequestModel { + @SerializedName("query") + @Expose + private String query; + + public MessageQueryRequestModel(String query) { + this.query = query; + } + + public String getQuery() { + return query; + } + + public void setQuery(String query) { + this.query = query; + } +} diff --git a/app/src/main/java/com/fazemeright/chatbotmetcs622/network/models/response/QueryResponseMessage.java b/app/src/main/java/com/fazemeright/chatbotmetcs622/network/models/response/QueryResponseMessage.java new file mode 100644 index 0000000..93077ad --- /dev/null +++ b/app/src/main/java/com/fazemeright/chatbotmetcs622/network/models/response/QueryResponseMessage.java @@ -0,0 +1,36 @@ +package com.fazemeright.chatbotmetcs622.network.models.response; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class QueryResponseMessage { + + @SerializedName("data") + @Expose + private Data data; + + public Data getData() { + return data; + } + + public void setData(Data data) { + this.data = data; + } + + public class Data { + + + @SerializedName("responseMsg") + @Expose + private String responseMsg; + + public String getResponseMsg() { + return responseMsg; + } + + public void setResponseMsg(String responseMsg) { + this.responseMsg = responseMsg; + } + } +} + diff --git a/app/src/main/java/com/fazemeright/chatbotmetcs622/network/utils/CoreUtils.java b/app/src/main/java/com/fazemeright/chatbotmetcs622/network/utils/CoreUtils.java new file mode 100644 index 0000000..c34cf18 --- /dev/null +++ b/app/src/main/java/com/fazemeright/chatbotmetcs622/network/utils/CoreUtils.java @@ -0,0 +1,43 @@ +package com.fazemeright.chatbotmetcs622.network.utils; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.webkit.URLUtil; + +import com.google.gson.Gson; + + +public class CoreUtils { + + /** + * To check internet connection + * + * @param mContext App context + * @return true if available else false + */ + public static boolean isNetworkAvailable(Context mContext) { + ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo networkInfo = null; + if (cm != null) { + networkInfo = cm.getActiveNetworkInfo(); + } + return networkInfo != null && networkInfo.isConnected(); + } + + public static String getStringFromObject(Object data) { + Gson gson = new Gson(); + return gson.toJson(data); + } + + /** + * Check whether URL is valid or not + * + * @param url url + * @return true if valid else false + */ + public static boolean isValidUrl(String url) { + return URLUtil.isValidUrl(url); + } + +} diff --git a/app/src/main/java/com/fazemeright/chatbotmetcs622/repositories/MessageRepository.java b/app/src/main/java/com/fazemeright/chatbotmetcs622/repositories/MessageRepository.java new file mode 100644 index 0000000..4961091 --- /dev/null +++ b/app/src/main/java/com/fazemeright/chatbotmetcs622/repositories/MessageRepository.java @@ -0,0 +1,427 @@ +package com.fazemeright.chatbotmetcs622.repositories; + +import android.content.Context; +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Build; + +import androidx.core.content.ContextCompat; + +import com.fazemeright.chatbotmetcs622.database.ChatBotDatabase; +import com.fazemeright.chatbotmetcs622.database.messages.Message; +import com.fazemeright.chatbotmetcs622.database.messages.MessageDao; +import com.fazemeright.chatbotmetcs622.intentservice.FireBaseIntentService; +import com.fazemeright.chatbotmetcs622.models.ChatRoom; +import com.fazemeright.chatbotmetcs622.network.ApiManager; +import com.fazemeright.chatbotmetcs622.network.NetworkManager; +import com.fazemeright.chatbotmetcs622.network.handlers.NetworkCallback; +import com.fazemeright.chatbotmetcs622.network.models.NetError; +import com.fazemeright.chatbotmetcs622.network.models.NetResponse; +import com.fazemeright.chatbotmetcs622.network.models.response.QueryResponseMessage; +import com.fazemeright.firebase_api_library.api.FireBaseApiManager; +import com.fazemeright.firebase_api_library.listeners.DBValueListener; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; + +import timber.log.Timber; + + +public class MessageRepository { + + private static MessageRepository repository; + private ChatBotDatabase database; + private ApiManager apiManager; + private FireBaseApiManager fireBaseApiManager; + + private MessageRepository(ChatBotDatabase database, ApiManager apiManager, FireBaseApiManager fireBaseApiManager) { + this.database = database; + this.apiManager = apiManager; + this.fireBaseApiManager = fireBaseApiManager; +// messageList = this.database.messageDao().getAllMessages(); + } + + /** + * Call to get instance of MessageRepository with the given context + * + * @param context given context + * @return synchronized call to get Instance of MessageRepository class + */ + public static MessageRepository getInstance(Context context) { + if (repository == null) { + synchronized (MessageRepository.class) { +// get instance of database + ChatBotDatabase database = ChatBotDatabase.getInstance(context); + ApiManager apiManager = ApiManager.getInstance(); + FireBaseApiManager fireBaseApiManager = FireBaseApiManager.getInstance(); + apiManager.init(NetworkManager.getInstance()); + repository = new MessageRepository(database, apiManager, fireBaseApiManager); + } + } + return repository; + } + + /** + * Call to insert given project into database with thread safety + * + * @param newMessage given project + * @return + */ + private Message insertMessageInRoom(Message newMessage) { +// insert into Room using AsyncTask + Timber.i("Insert message in Room called%s", newMessage.getMsg()); + try { + return new InsertAsyncTask(database.messageDao()).execute(newMessage).get(); + } catch (ExecutionException | InterruptedException e) { + e.printStackTrace(); + } + return newMessage; + } + + /** + * Call to update given project into database with thread safety + * + * @param oldMessage given project + */ + private void updateMessage(Message oldMessage) { + // insert into Room using AsyncTask + new UpdateAsyncTask(database.messageDao()).execute(oldMessage); + } + + /** + * Call to get Message with given Message ID + * + * @param mid given Message ID + * @return Message with given ID + */ + public Message getMessage(long mid) { + return fetchMessage(mid); + } + + /** + * Call to get Message with given Message ID with thread safety + * + * @param pid given Message ID + * @return Message with given ID + */ + private Message fetchMessage(long pid) { + try { + return new FetchMessageAsyncTask(database.messageDao()).execute(pid).get(); + } catch (ExecutionException | InterruptedException e) { + e.printStackTrace(); + return null; + } + } + + /** + * Call to delete project with given project + * + * @param project given Message + * @return Deleted Message + */ + public void deleteMessage(Message project) { + deleteMessageFromRoom(project); + } + + /** + * Delete given Message from Room with Thread Safety + * + * @param project given Message + */ + private void deleteMessageFromRoom(Message project) { + try { + new DeleteMessageAsyncTask(database.messageDao()).execute(project).get(); + } catch (ExecutionException | InterruptedException e) { + e.printStackTrace(); + } + } + + public ArrayList getMessagesForChatRoom(ChatRoom chatRoom) { + return getChatRoomMessagesFromDatabase(chatRoom); + } + + private ArrayList getChatRoomMessagesFromDatabase(ChatRoom chatRoom) { + try { + return (ArrayList) new FetchChatRoomMessagesAsyncTask(database.messageDao()).execute(chatRoom).get(); + } catch (ExecutionException | InterruptedException e) { + e.printStackTrace(); + return new ArrayList<>(); + } + } + + /** + * Called when user sends a given new message in the ChatRoom + * - Add new Message to Room + * - Call API to fetch answer for new message + * - Sync message with FireStore + * + * @param newMessage given new message + */ + public void newMessageSent(final Context context, final Message newMessage, final OnMessageResponseReceivedListener listener) { + final Message roomLastMessage = insertMessageInRoom(newMessage); + insertMessageInFireBase(context, roomLastMessage); + apiManager.queryDatabase(context, newMessage, new NetworkCallback() { + @Override + public void onSuccess(NetResponse response) { + Message queryResponseMessage = Message.newMessage(response.getResponse().getData().getResponseMsg(), + newMessage.getReceiver(), newMessage.getSender(), newMessage.getChatRoomId()); + + Message roomLastInsertedMessage = insertMessageInRoom(queryResponseMessage); + insertMessageInFireBase(context, roomLastInsertedMessage); + listener.onMessageResponseReceived(queryResponseMessage); + } + + @Override + public void onError(NetError error) { + listener.onNoResponseReceived(new Error(error.getErrorLocalizeMessage())); + } + }); + +// TODO: Finish the remaining cart + } + + /** + * Call to insert the given new message to FireStore database + * + * @param context context + * @param newMessage given new message + */ + private void insertMessageInFireBase(Context context, Message newMessage) { + Intent intent = new Intent(context, FireBaseIntentService.class); + intent.putExtra(FireBaseIntentService.ACTION, FireBaseIntentService.ACTION_ADD_MESSAGE); + intent.putExtra(FireBaseIntentService.MESSAGE, newMessage); + context.startService(intent); + } + + public ArrayList getAllMessages() { + return (ArrayList) database.messageDao().getAllMessages(); + } + + /** + * Clear all given chat room messages + * - From Room + * - From FireStore + * + * @param chatRoom given chat room + */ + public void clearAllChatRoomMessages(ChatRoom chatRoom) { + clearAllChatRoomMessagesFromRoom(chatRoom); + } + + private void clearAllChatRoomMessagesFromRoom(ChatRoom chatRoom) { + new ClearAllMessagesInChatRoomAsyncTask(database.messageDao()).execute(chatRoom); + } + + /** + * Call to logout user and clear all messages from Room + */ + public void logOutUser() { + fireBaseApiManager.logOutUser(); + clearAllMessages(); + } + + /** + * Call to clear all messages from Room + */ + private void clearAllMessages() { + new ClearAllMessagesAsyncTask(database.messageDao()).execute(); + } + + /** + * Add given list of messages to Room + * + * @param messages + */ + public void addMessages(List messages) { + new AddAllMessagesAsyncTask(database.messageDao()).execute(messages); + } + + /** + * Call to add the given message to FireStore + * + * @param messageHashMap given message converted into HashMap + */ + public void addMessageToFireBase(Map messageHashMap) { + fireBaseApiManager.addMessageToUserDatabase(messageHashMap); + } + + /** + * Call to sync messages from FireStore to Room for the logged in user + */ + public void syncMessagesFromFireStoreToRoom() { + fireBaseApiManager.syncMessages(new DBValueListener>>() { + @Override + public void onDataReceived(List> data) { +// This code runs on the UI thread + List messages = new ArrayList<>(); + for (Map object : + data) { + Timber.i(String.valueOf(object.get("mid"))); + Message newMessage = new Message((long) object.get("mid"), String.valueOf(object.get("msg")), + String.valueOf(object.get("sender")), String.valueOf(object.get("receiver")), + (long) object.get("chatRoomId"), (long) object.get("timestamp")); + Timber.i(newMessage.toString()); + messages.add(newMessage); + } + addMessages(messages); + } + + @Override + public void onCancelled(Error error) { + + } + }); + } + + /** + * Fetch all chat room messages for the given ChatRoom through AsyncTask from Room + */ + private static class FetchChatRoomMessagesAsyncTask extends AsyncTask> { + + private MessageDao mAsyncTaskDao; + + FetchChatRoomMessagesAsyncTask(MessageDao dao) { + mAsyncTaskDao = dao; + } + + @Override + protected List doInBackground(ChatRoom... params) { + return mAsyncTaskDao.getAllMessagesFromChatRoom(params[0].getId()); + } + } + + /** + * Fetch all chat room messages for the given ChatRoom through AsyncTask from Room + */ + private static class ClearAllMessagesInChatRoomAsyncTask extends AsyncTask { + + private MessageDao mAsyncTaskDao; + + ClearAllMessagesInChatRoomAsyncTask(MessageDao dao) { + mAsyncTaskDao = dao; + } + + @Override + protected Void doInBackground(ChatRoom... params) { + mAsyncTaskDao.clearChatRoomMessages(params[0].getId()); + return null; + } + } + + /** + * Fetch all messages through AsyncTask from Room + */ + private static class AddAllMessagesAsyncTask extends AsyncTask, Void, Void> { + + private MessageDao mAsyncTaskDao; + + AddAllMessagesAsyncTask(MessageDao dao) { + mAsyncTaskDao = dao; + } + + @Override + protected Void doInBackground(List... lists) { + mAsyncTaskDao.insertAllMessages(lists[0]); + return null; + } + } + + /** + * Fetch all messages through AsyncTask from Room + */ + private static class ClearAllMessagesAsyncTask extends AsyncTask { + + private MessageDao mAsyncTaskDao; + + ClearAllMessagesAsyncTask(MessageDao dao) { + mAsyncTaskDao = dao; + } + + @Override + protected Void doInBackground(Void... params) { + mAsyncTaskDao.clear(); + return null; + } + } + + /** + * Call to get favorite projects from Room through AsyncTask + */ + private static class DeleteMessageAsyncTask extends AsyncTask { + + private MessageDao dao; + + DeleteMessageAsyncTask(MessageDao mDao) { + dao = mDao; + } + + + @Override + protected Message doInBackground(Message... params) { + dao.deleteItem(params[0]); + return params[0]; + } + } + + /** + * Fetch a specific project for the given Message ID through AsyncTask + */ + private static class FetchMessageAsyncTask extends AsyncTask { + + private MessageDao mAsyncTaskDao; + + FetchMessageAsyncTask(MessageDao dao) { + mAsyncTaskDao = dao; + } + + @Override + protected Message doInBackground(Long... params) { + return mAsyncTaskDao.get(params[0]); + } + } + + /** + * AsyncTask which makes insert operation thread safe and does not block the main thread for a long time + */ + private static class InsertAsyncTask extends AsyncTask { + + private MessageDao mAsyncTaskDao; + + InsertAsyncTask(MessageDao dao) { + mAsyncTaskDao = dao; + } + + @Override + protected Message doInBackground(final Message... params) { + mAsyncTaskDao.insert(params[0]); + Timber.i("Inside AsyncTask to insert message in Room %s", params[0].getMsg()); + return mAsyncTaskDao.getLatestMessage(params[0].getChatRoomId()); + } + } + + /** + * AsyncTask which makes insert operation thread safe and does not block the main thread for a long time + */ + private static class UpdateAsyncTask extends AsyncTask { + + private MessageDao mAsyncTaskDao; + + UpdateAsyncTask(MessageDao dao) { + mAsyncTaskDao = dao; + } + + @Override + protected Void doInBackground(final Message... params) { + mAsyncTaskDao.update(params[0]); + return null; + } + } + + public interface OnMessageResponseReceivedListener { + void onMessageResponseReceived(Message response); + + void onNoResponseReceived(Error error); + } +} diff --git a/app/src/main/java/com/fazemeright/chatbotmetcs622/ui/base/BaseActivity.java b/app/src/main/java/com/fazemeright/chatbotmetcs622/ui/base/BaseActivity.java new file mode 100644 index 0000000..86edcfc --- /dev/null +++ b/app/src/main/java/com/fazemeright/chatbotmetcs622/ui/base/BaseActivity.java @@ -0,0 +1,127 @@ +package com.fazemeright.chatbotmetcs622.ui.base; + +import android.app.Activity; +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.os.Bundle; +import android.view.View; +import android.view.inputmethod.InputMethodManager; +import android.widget.Button; +import android.widget.EditText; + +import androidx.annotation.LayoutRes; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +import com.fazemeright.chatbotmetcs622.network.ApiManager; +import com.fazemeright.chatbotmetcs622.network.NetworkManager; +import com.fazemeright.chatbotmetcs622.repositories.MessageRepository; +import com.fazemeright.firebase_api_library.api.FireBaseApiManager; + +public abstract class BaseActivity extends AppCompatActivity { + + public Context mContext; + protected FireBaseApiManager fireBaseApiManager; + protected MessageRepository messageRepository; + protected ApiManager apiManager; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mContext = this; + fireBaseApiManager = FireBaseApiManager.getInstance(); + messageRepository = MessageRepository.getInstance(mContext); + apiManager = ApiManager.getInstance(); + apiManager.init(NetworkManager.getInstance()); + setContentView(getLayoutResId()); + } + + @Override + public void setContentView(@LayoutRes int layoutResID) { + super.setContentView(layoutResID); + initViews(); + setListeners(); + } + + /** + * Call to hide soft keyboard + * + * @param activity + */ + public void hideKeyboard(Activity activity) { + InputMethodManager imm = (InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE); + //Find the currently focused view, so we can grab the correct window token from it. + View view = activity.getCurrentFocus(); + //If no view currently has focus, create a new one, just so we can grab a window token from it + if (view == null) { + view = new View(activity); + } + if (imm != null) { + imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + } + + public void showKeyBoard(EditText yourEditText) { + InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null) { + imm.showSoftInput(yourEditText, InputMethodManager.SHOW_IMPLICIT); + } + } + + /** + * Call to disable the given button + * + * @param button given button + */ + protected void disableButton(Button button) { + button.setEnabled(false); + } + + /** + * Call to enable the given button + * + * @param button given button + */ + protected void enableButton(Button button) { + button.setEnabled(true); + } + + /** + * To initialize views of activity + */ + public abstract void initViews(); + + /** + * To set listeners of view or callback + */ + public abstract void setListeners(); + + /** + * To get layout resource id + */ + public abstract int getLayoutResId(); + + public boolean isNetworkConnected() { + ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo networkInfo = null; + if (cm != null) { + networkInfo = cm.getActiveNetworkInfo(); + } + return networkInfo != null && networkInfo.isConnected(); + } + + public Context getContext() { + return mContext; + } + + @Override + protected void onResume() { + super.onResume(); + } + + @Override + protected void onPause() { + super.onPause(); + } +} diff --git a/app/src/main/java/com/fazemeright/chatbotmetcs622/ui/base/BaseFragment.java b/app/src/main/java/com/fazemeright/chatbotmetcs622/ui/base/BaseFragment.java new file mode 100644 index 0000000..90812a4 --- /dev/null +++ b/app/src/main/java/com/fazemeright/chatbotmetcs622/ui/base/BaseFragment.java @@ -0,0 +1,79 @@ +package com.fazemeright.chatbotmetcs622.ui.base; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.LayoutRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.fazemeright.chatbotmetcs622.network.ApiManager; +import com.fazemeright.chatbotmetcs622.network.NetworkManager; +import com.fazemeright.chatbotmetcs622.repositories.MessageRepository; +import com.fazemeright.firebase_api_library.api.FireBaseApiManager; + +public abstract class BaseFragment extends Fragment { + + public Context mContext; + public FireBaseApiManager fireBaseApiManager; + protected MessageRepository messageRepository; + protected ApiManager apiManager; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mContext = getActivity(); + fireBaseApiManager = FireBaseApiManager.getInstance(); + messageRepository = MessageRepository.getInstance(mContext); + apiManager = ApiManager.getInstance(); + apiManager.init(NetworkManager.getInstance()); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(getLayoutResId(), container, false); + initViews(view); + setListeners(view); + return view; + } + + /** + * To get layout resource id + */ + public abstract @LayoutRes + int getLayoutResId(); + + /** + * To initialize views of activity + */ + public abstract void initViews(View view); + + /** + * To set listeners of view or callback + * + * @param view + */ + public abstract void setListeners(View view); + + public boolean isNetworkConnected() { + ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo networkInfo = null; + if (cm != null) { + networkInfo = cm.getActiveNetworkInfo(); + } + return networkInfo != null && networkInfo.isConnected(); + } + + @Nullable + @Override + public Context getContext() { + return mContext; + } +} diff --git a/app/src/main/java/com/fazemeright/chatbotmetcs622/ui/chat/ChatActivity.java b/app/src/main/java/com/fazemeright/chatbotmetcs622/ui/chat/ChatActivity.java new file mode 100644 index 0000000..b04986a --- /dev/null +++ b/app/src/main/java/com/fazemeright/chatbotmetcs622/ui/chat/ChatActivity.java @@ -0,0 +1,193 @@ +package com.fazemeright.chatbotmetcs622.ui.chat; + +import android.text.TextUtils; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.CompoundButton; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.fazemeright.chatbotmetcs622.R; +import com.fazemeright.chatbotmetcs622.database.messages.Message; +import com.fazemeright.chatbotmetcs622.models.ChatRoom; +import com.fazemeright.chatbotmetcs622.repositories.MessageRepository; +import com.fazemeright.chatbotmetcs622.ui.base.BaseActivity; +import com.fazemeright.chatbotmetcs622.ui.landing.LandingActivity; +import com.google.android.material.chip.Chip; +import com.google.android.material.chip.ChipGroup; + +import java.util.ArrayList; +import java.util.Objects; + +public class ChatActivity extends BaseActivity implements View.OnClickListener { + + private RecyclerView rvChatList; + private ChatListAdapter adapter; + private ArrayList messages; + private EditText etMsg; + private ImageView ivSendMsg; + private ChatRoom chatRoom; + private ChipGroup dataFilterChipGroup; + + @Override + public void initViews() { + + etMsg = findViewById(R.id.etMsg); + ivSendMsg = findViewById(R.id.ivSendMsg); + rvChatList = findViewById(R.id.rvChatList); + dataFilterChipGroup = findViewById(R.id.dataFilterChipGroup); + + if (getIntent() != null) { + chatRoom = (ChatRoom) getIntent().getSerializableExtra(LandingActivity.SELECTED_CHAT_ROOM); + if (getSupportActionBar() != null) { + getSupportActionBar().setTitle(chatRoom.getName()); + getSupportActionBar().setHomeButtonEnabled(true); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + } + } + + String[] dataFilters = getResources().getStringArray(R.array.query_sample_selection); + setupFilterKeywords(dataFilters); + + rvChatList.setHasFixedSize(true); + LinearLayoutManager linearLayoutManager = new LinearLayoutManager(mContext); + linearLayoutManager.setReverseLayout(true); + linearLayoutManager.setStackFromEnd(true); + rvChatList.setLayoutManager(linearLayoutManager); + + ArrayList messages = messageRepository.getMessagesForChatRoom(chatRoom); + adapter = new ChatListAdapter(messages, mContext); + + rvChatList.setAdapter(adapter); +// Show user the most recent messages, hence scroll to the top + rvChatList.scrollToPosition(ChatListAdapter.MOST_RECENT_MSG_POSITION); + } + + /** + * Use to setup chips for the given list of data filter for device usage + * + * @param dataFilters given array of data filter + */ + private void setupFilterKeywords(String[] dataFilters) { +// remove all views from ChipGroup if any + dataFilterChipGroup.removeAllViews(); + if (dataFilters != null) { + for (final String dataFilter : + dataFilters) { +// create new chip and apply attributes + Chip chip = new Chip(Objects.requireNonNull(mContext)) {{ + setText(dataFilter); // set text + setClickable(true); + setCloseIconVisible(false); // no need for close icon in our scenario + setCheckable(true); // set checkable to be true, hence allow check changes + }}; + dataFilterChipGroup.addView(chip); // add chip to ChipGroup + chip.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isChecked) { + etMsg.requestFocus(); + etMsg.setText(buttonView.getText().toString()); + etMsg.setSelection(buttonView.getText().toString().length()); + showKeyBoard(etMsg); + } + } + }); + + } +// show ChipGroup if list is not empty + dataFilterChipGroup.setVisibility(View.VISIBLE); + } else { +// hide ChipGroup if list is empty + dataFilterChipGroup.setVisibility(View.INVISIBLE); + } + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + onBackPressed(); + break; + case R.id.action_clear: + clearChatRoomMessagesClicked(chatRoom); + break; + } + return super.onOptionsItemSelected(item); + } + + /** + * Call to clear all message for the given ChatRoom + * + * @param chatRoom given ChatRoom + */ + private void clearChatRoomMessagesClicked(ChatRoom chatRoom) { + messageRepository.clearAllChatRoomMessages(chatRoom); + adapter.clearAllMessages(); + } + + @Override + public void setListeners() { + ivSendMsg.setOnClickListener(this); + } + + @Override + public int getLayoutResId() { + return R.layout.activity_chat; + } + + @Override + public void onClick(View v) { + if (v.getId() == R.id.ivSendMsg) { + sendMessageClicked(); + } + } + + /** + * User clicked send message. Show new message to user and pass it to repository + */ + private void sendMessageClicked() { + String msg = etMsg.getText().toString().trim(); + if (TextUtils.isEmpty(msg)) { + return; + } + etMsg.setText(""); + Message newMessage = Message.newMessage(msg, Message.SENDER_USER, chatRoom.getName(), chatRoom.getId()); + addMessageToAdapter(newMessage); +// send new message to repository + messageRepository.newMessageSent(mContext, newMessage, new MessageRepository.OnMessageResponseReceivedListener() { + @Override + public void onMessageResponseReceived(Message response) { + addMessageToAdapter(response); + } + + @Override + public void onNoResponseReceived(Error error) { + Toast.makeText(mContext, error.getLocalizedMessage(), Toast.LENGTH_SHORT).show(); +// TODO: Show error to the user + } + }); + } + + /** + * Call to add given new message to the Adapter and display it to the user and scroll to it + * + * @param newMessage given new message to be displayed + */ + private void addMessageToAdapter(Message newMessage) { + adapter.addMessage(newMessage); + rvChatList.scrollToPosition(ChatListAdapter.MOST_RECENT_MSG_POSITION); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_chat, menu); + return super.onCreateOptionsMenu(menu); + } +} diff --git a/app/src/main/java/com/fazemeright/chatbotmetcs622/ui/chat/ChatListAdapter.java b/app/src/main/java/com/fazemeright/chatbotmetcs622/ui/chat/ChatListAdapter.java new file mode 100644 index 0000000..992968b --- /dev/null +++ b/app/src/main/java/com/fazemeright/chatbotmetcs622/ui/chat/ChatListAdapter.java @@ -0,0 +1,128 @@ +package com.fazemeright.chatbotmetcs622.ui.chat; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fazemeright.chatbotmetcs622.R; +import com.fazemeright.chatbotmetcs622.database.messages.Message; + +import java.util.ArrayList; + +/** + * RecyclerView Adapter to display Chat + * + * @see ChatActivity for use + */ +public class ChatListAdapter extends RecyclerView.Adapter { + + static final int MOST_RECENT_MSG_POSITION = 0; + private static final int TYPE_SENT = 0; + private static final int TYPE_RECEIVED = 1; + private ArrayList messages; + private Context context; + + + ChatListAdapter(ArrayList messages, Context context) { + this.messages = messages; + this.context = context; + } + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) { + View view; + if (viewType == TYPE_SENT) { // for sent message layout + view = LayoutInflater.from(context).inflate(R.layout.sender_message_display_view_item, viewGroup, false); + return new SentViewHolder(view); + + } else { // for received message layout + view = LayoutInflater.from(context).inflate(R.layout.receiver_message_display_view_item, viewGroup, false); + return new ReceivedViewHolder(view); + } + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { + if (getItemViewType(position) == TYPE_SENT) { + ((SentViewHolder) holder).bind(messages.get(position)); + } else { + ((ReceivedViewHolder) holder).bind(messages.get(position)); + } + } + + @Override + public int getItemViewType(int position) { + if (messages.get(position).getSender().equals(Message.SENDER_USER)) { + return TYPE_SENT; + } else { + return TYPE_RECEIVED; + } + } + + @Override + public int getItemCount() { + return messages == null ? 0 : messages.size(); + } + + /** + * Call to add given new message to ArrayList at the bottom of the list and notify it was inserted + * + * @param newMessage given new message + */ + void addMessage(Message newMessage) { + if (messages == null) { + messages = new ArrayList<>(); + } + messages.add(MOST_RECENT_MSG_POSITION, newMessage); + notifyItemInserted(MOST_RECENT_MSG_POSITION); + } + + /** + * Call to remove all messages from the Data List and notify data set changed + */ + void clearAllMessages() { + messages.clear(); + notifyDataSetChanged(); + } + + public interface ChatMessageInteractionListener { + } + + public class SentViewHolder extends RecyclerView.ViewHolder { + + TextView tvMsg, tvTimestamp; + + SentViewHolder(@NonNull View itemView) { + super(itemView); + tvMsg = itemView.findViewById(R.id.tvMsg); + tvTimestamp = itemView.findViewById(R.id.tvTimestamp); + } + + void bind(Message item) { + tvMsg.setText(item.getMsg()); + tvTimestamp.setText(item.getFormattedTime()); + } + } + + public class ReceivedViewHolder extends RecyclerView.ViewHolder { + + TextView tvMsg, tvTimestamp; + + ReceivedViewHolder(@NonNull View itemView) { + super(itemView); + tvMsg = itemView.findViewById(R.id.tvMsg); + tvTimestamp = itemView.findViewById(R.id.tvTimestamp); + } + + void bind(Message item) { + tvMsg.setText(item.getMsg()); + tvTimestamp.setText(item.getFormattedTime()); + } + } +} diff --git a/app/src/main/java/com/fazemeright/chatbotmetcs622/ui/landing/ChatSelectionListAdapter.java b/app/src/main/java/com/fazemeright/chatbotmetcs622/ui/landing/ChatSelectionListAdapter.java new file mode 100644 index 0000000..e56df88 --- /dev/null +++ b/app/src/main/java/com/fazemeright/chatbotmetcs622/ui/landing/ChatSelectionListAdapter.java @@ -0,0 +1,92 @@ +package com.fazemeright.chatbotmetcs622.ui.landing; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.ListAdapter; +import androidx.recyclerview.widget.RecyclerView; + +import com.fazemeright.chatbotmetcs622.R; +import com.fazemeright.chatbotmetcs622.models.ChatRoom; + +import java.util.ArrayList; + +/** + * RecyclerView Adapter to show Chat Rooms + * + * @see LandingActivity for use + */ +public class ChatSelectionListAdapter extends ListAdapter { + + private ChatListInteractionListener listener; + + protected ChatSelectionListAdapter(ChatListInteractionListener listener) { + super(new ChatRoomDiffCallBack()); + this.listener = listener; + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View v = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.chat_room_display_view_item, parent, false); + + return new ViewHolder(v); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + holder.bind(getItem(position)); + } + + public void submitDataList(ArrayList dataList) { + submitList(dataList); + } + + public interface ChatListInteractionListener { + void onChatRoomClicked(ChatRoom chatRoom); + } + + static class ChatRoomDiffCallBack extends DiffUtil.ItemCallback { + + @Override + public boolean areItemsTheSame(@NonNull ChatRoom oldItem, @NonNull ChatRoom newItem) { + return oldItem.getId() == newItem.getId(); + } + + @Override + public boolean areContentsTheSame(@NonNull ChatRoom oldItem, @NonNull ChatRoom newItem) { + return oldItem.equals(newItem); + } + } + + public class ViewHolder extends RecyclerView.ViewHolder { + + TextView tvChatRoomName; + ImageView ivChatRoom; + + public ViewHolder(@NonNull View itemView) { + super(itemView); + tvChatRoomName = itemView.findViewById(R.id.tvChatRoomName); + ivChatRoom = itemView.findViewById(R.id.ivChatRoom); + } + + public void bind(ChatRoom item) { + tvChatRoomName.setText(item.getName()); + + ivChatRoom.setBackgroundResource(item.getLogoId()); + + itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + listener.onChatRoomClicked(getItem(getAdapterPosition())); + } + }); + } + } +} diff --git a/app/src/main/java/com/fazemeright/chatbotmetcs622/ui/landing/LandingActivity.java b/app/src/main/java/com/fazemeright/chatbotmetcs622/ui/landing/LandingActivity.java new file mode 100644 index 0000000..159a450 --- /dev/null +++ b/app/src/main/java/com/fazemeright/chatbotmetcs622/ui/landing/LandingActivity.java @@ -0,0 +1,99 @@ +package com.fazemeright.chatbotmetcs622.ui.landing; + +import android.content.Intent; +import android.view.Menu; +import android.view.MenuItem; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.fazemeright.chatbotmetcs622.R; +import com.fazemeright.chatbotmetcs622.models.ChatRoom; +import com.fazemeright.chatbotmetcs622.ui.base.BaseActivity; +import com.fazemeright.chatbotmetcs622.ui.chat.ChatActivity; +import com.fazemeright.chatbotmetcs622.ui.login.LoginActivity; +import com.fazemeright.chatbotmetcs622.ui.registration.RegistrationActivity; + +import java.util.ArrayList; + +public class LandingActivity extends BaseActivity implements ChatSelectionListAdapter.ChatListInteractionListener { + + public static final String SELECTED_CHAT_ROOM = "chatRoomSelected"; + private RecyclerView rvChatRoomList; + private ChatSelectionListAdapter adapter; + + @Override + public void initViews() { + if (getSupportActionBar() != null) { + String firstName = fireBaseApiManager.getCurrentUserFirstName(); + if (firstName == null) { + firstName = "Adit"; + } + getSupportActionBar().setTitle(getString(R.string.welcome_title) + " " + firstName); + } + + rvChatRoomList = findViewById(R.id.rvChatRoomList); + rvChatRoomList.setHasFixedSize(true); + rvChatRoomList.setLayoutManager(new LinearLayoutManager(mContext)); + rvChatRoomList.addItemDecoration(new DividerItemDecoration(rvChatRoomList.getContext(), LinearLayoutManager.VERTICAL)); + adapter = new ChatSelectionListAdapter(this); + rvChatRoomList.setAdapter(adapter); + + adapter.submitDataList(getChatRoomList()); + } + + private ArrayList getChatRoomList() { + ArrayList chatRooms = new ArrayList<>(); + chatRooms.add(new ChatRoom(ChatRoom.BRUTE_FORCE_ID, ChatRoom.BRUTE_FORCE, R.drawable.brute_force_logo)); + chatRooms.add(new ChatRoom(ChatRoom.LUCENE_ID, ChatRoom.LUCENE, R.drawable.lucene_logo)); + chatRooms.add(new ChatRoom(ChatRoom.MONGO_DB_ID, ChatRoom.MONGO_DB, R.drawable.mongodb_logo)); + chatRooms.add(new ChatRoom(ChatRoom.MY_SQL_ID, ChatRoom.MY_SQL, R.drawable.mysql_logo)); + return chatRooms; + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_landing, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + switch (item.getItemId()) { + case R.id.action_logout: + logoutUser(); + openRegistrationActivity(); + return true; + } + + return super.onOptionsItemSelected(item); + } + + private void openRegistrationActivity() { + startActivity(new Intent(LandingActivity.this, RegistrationActivity.class)); + finish(); + } + + private void logoutUser() { + messageRepository.logOutUser(); + } + + @Override + public void setListeners() { + + } + + @Override + public int getLayoutResId() { + return R.layout.activity_landing; + } + + @Override + public void onChatRoomClicked(ChatRoom chatRoom) { + Intent intent = new Intent(LandingActivity.this, ChatActivity.class); + intent.putExtra(SELECTED_CHAT_ROOM, chatRoom); + startActivity(intent); + } +} diff --git a/app/src/main/java/com/fazemeright/chatbotmetcs622/ui/login/LoginActivity.java b/app/src/main/java/com/fazemeright/chatbotmetcs622/ui/login/LoginActivity.java new file mode 100644 index 0000000..f803fbc --- /dev/null +++ b/app/src/main/java/com/fazemeright/chatbotmetcs622/ui/login/LoginActivity.java @@ -0,0 +1,143 @@ +package com.fazemeright.chatbotmetcs622.ui.login; + +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.view.MenuItem; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; + +import com.fazemeright.chatbotmetcs622.R; +import com.fazemeright.chatbotmetcs622.intentservice.FireBaseIntentService; +import com.fazemeright.chatbotmetcs622.ui.base.BaseActivity; +import com.fazemeright.chatbotmetcs622.ui.landing.LandingActivity; +import com.fazemeright.chatbotmetcs622.utils.AppUtils; +import com.fazemeright.firebase_api_library.listeners.OnTaskCompleteListener; + +import timber.log.Timber; + +public class LoginActivity extends BaseActivity implements View.OnClickListener { + + private EditText userEmailEditText, userPasswordEditText; + private TextView tvDontHaveAccount; + private Button btnLogin; + + @Override + public void initViews() { +// set title for activity + if (getSupportActionBar() != null) { + getSupportActionBar().setTitle(getString(R.string.login_title)); + getSupportActionBar().setHomeButtonEnabled(true); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + } + + + userEmailEditText = findViewById(R.id.userLoginEmailEditText); + userPasswordEditText = findViewById(R.id.userPasswordEditText); + tvDontHaveAccount = findViewById(R.id.tvDontHaveAccount); + btnLogin = findViewById(R.id.btnLogin); + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + } + return super.onOptionsItemSelected(item); + } + + @Override + public void setListeners() { + btnLogin.setOnClickListener(this); + tvDontHaveAccount.setOnClickListener(this); + } + + @Override + public int getLayoutResId() { + return R.layout.activity_login; + } + + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.tvDontHaveAccount: + openRegistrationActivity(); + break; + case R.id.btnLogin: +// TODO: Disable and re-enable the button after performing registration and validation + disableButton(btnLogin); + String email = userEmailEditText.getText().toString(); + String password = userPasswordEditText.getText().toString(); + performLogin(email, password); + enableButton(btnLogin); + break; + } + } + + /** + * Perform login with the given credentials + * + * @param email user email address + * @param password user password + */ + private void performLogin(String email, String password) { + if (!AppUtils.isValidEmail(email)) { + userEmailEditText.setError(mContext.getString(R.string.incorrect_email_err_msg)); + userEmailEditText.requestFocus(); + return; + } + + if (!AppUtils.isValidPassword(password)) { + userPasswordEditText.setError(mContext.getString(R.string.incorrect_pass_err_msg)); + userPasswordEditText.requestFocus(); + return; + } + + Timber.i("Login clicked"); + fireBaseApiManager.logInWithEmailAndPassword(email, password, new OnTaskCompleteListener() { + @Override + public void onTaskSuccessful() { + Timber.i("User logged in successfully %s", fireBaseApiManager.getCurrentLoggedInUserEmail()); + btnLogin.setText(getString(R.string.login_success_msg)); + Intent intent = new Intent(mContext, FireBaseIntentService.class); + intent.putExtra(FireBaseIntentService.ACTION, FireBaseIntentService.ACTION_SYNC_MESSAGES); +// ContextCompat.startForegroundService(LoginActivity.this, intent); +// ContextCompat.startForegroundService(mContext, intent); + ContextCompat.startForegroundService(mContext, intent); + openLandingActivity(); + } + + @Override + public void onTaskCompleteButFailed(String errMsg) { + Timber.e(errMsg); +// TODO: Show error to user + } + + @Override + public void onTaskFailed(Exception e) { + Timber.e(e); +// TODO: Show error to user + } + }); + } + + /** + * Open LandingActivity and finish this one + */ + private void openLandingActivity() { + startActivity(new Intent(LoginActivity.this, LandingActivity.class)); + finishAffinity(); + } + + /** + * Open Registration Activity + */ + private void openRegistrationActivity() { + finish(); + } +} diff --git a/app/src/main/java/com/fazemeright/chatbotmetcs622/ui/registration/RegistrationActivity.java b/app/src/main/java/com/fazemeright/chatbotmetcs622/ui/registration/RegistrationActivity.java new file mode 100644 index 0000000..0541698 --- /dev/null +++ b/app/src/main/java/com/fazemeright/chatbotmetcs622/ui/registration/RegistrationActivity.java @@ -0,0 +1,150 @@ +package com.fazemeright.chatbotmetcs622.ui.registration; + +import android.content.Intent; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; + +import com.fazemeright.chatbotmetcs622.ui.landing.LandingActivity; +import com.fazemeright.chatbotmetcs622.R; +import com.fazemeright.chatbotmetcs622.ui.base.BaseActivity; +import com.fazemeright.chatbotmetcs622.ui.login.LoginActivity; +import com.fazemeright.chatbotmetcs622.utils.AppUtils; +import com.fazemeright.firebase_api_library.listeners.OnTaskCompleteListener; + +import timber.log.Timber; + +public class RegistrationActivity extends BaseActivity implements View.OnClickListener { + + private EditText userEmailEditText, userPasswordEditText, userConPasswordEditText, etFirstName, etLastName; + private TextView tvHaveAccount; + private Button btnRegister; + + @Override + public void initViews() { +// set title for activity + if (getSupportActionBar() != null) { + getSupportActionBar().setTitle(getString(R.string.registration)); + } + + userEmailEditText = findViewById(R.id.userLoginEmailEditText); + etFirstName = findViewById(R.id.etFirstName); + etLastName = findViewById(R.id.etLastName); + userPasswordEditText = findViewById(R.id.userPasswordEditText); + userConPasswordEditText = findViewById(R.id.userConPasswordEditText); + tvHaveAccount = findViewById(R.id.tvHaveAccount); + btnRegister = findViewById(R.id.btnRegister); + + } + + @Override + public void setListeners() { + btnRegister.setOnClickListener(this); + tvHaveAccount.setOnClickListener(this); + } + + @Override + public int getLayoutResId() { + return R.layout.activity_registration; + } + + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.tvHaveAccount: + openLoginActivity(); + break; + case R.id.btnRegister: + disableButton(btnRegister); + String email = userEmailEditText.getText().toString(); + String firstName = etFirstName.getText().toString(); + String lastName = etLastName.getText().toString(); + String password = userPasswordEditText.getText().toString(); + String conPassword = userConPasswordEditText.getText().toString(); + performRegistration(email, firstName, lastName, password, conPassword); + enableButton(btnRegister); + break; + } + } + + /** + * Open Login Activity + */ + private void openLoginActivity() { + startActivity(new Intent(RegistrationActivity.this, LoginActivity.class)); + } + + /** + * Call to perform validation on the input parameters and then perform registration + * + * @param email user email address + * @param firstName first name of user + * @param lastName last name of user + * @param password user selected password + * @param conPassword user selected confirmation password + */ + private void performRegistration(final String email, String firstName, String lastName, final String password, String conPassword) { + if (!AppUtils.isValidEmail(email)) { + userEmailEditText.setError(mContext.getString(R.string.incorrect_email_err_msg)); + userEmailEditText.requestFocus(); + return; + } + + if (!AppUtils.isValidName(firstName)) { + etFirstName.setError(mContext.getString(R.string.incorrect_first_name)); + etFirstName.requestFocus(); + return; + } + + if (!AppUtils.isValidName(lastName)) { + etLastName.setError(mContext.getString(R.string.incorrect_last_name)); + etLastName.requestFocus(); + return; + } + + if (!AppUtils.isValidPassword(password)) { + userPasswordEditText.setError(mContext.getString(R.string.incorrect_pass_err_msg)); + userPasswordEditText.requestFocus(); + return; + } + + if (!AppUtils.arePasswordsValid(password, conPassword)) { + userPasswordEditText.setError(mContext.getString(R.string.passwords_dont_match_err_msg)); + userPasswordEditText.requestFocus(); + userPasswordEditText.setText(""); + userConPasswordEditText.setText(""); + return; + } + + fireBaseApiManager.registerNewUserWithEmailPassword(email, password, firstName, lastName, new OnTaskCompleteListener() { + @Override + public void onTaskSuccessful() { + Timber.i("New user registered successfully %s", fireBaseApiManager.getCurrentLoggedInUserEmail()); + btnRegister.setText(getString(R.string.registration_success_msg)); + openLandingActivity(); + } + + @Override + public void onTaskCompleteButFailed(String errMsg) { + Timber.e(errMsg); +// TODO: Show error to user + } + + @Override + public void onTaskFailed(Exception e) { + Timber.e(e); +// TODO: Show error to user + } + }); + + } + + /** + * Open LandingActivity and finish this one + */ + private void openLandingActivity() { + startActivity(new Intent(RegistrationActivity.this, LandingActivity.class)); + finish(); + } +} diff --git a/app/src/main/java/com/fazemeright/chatbotmetcs622/ui/splash/SplashActivity.java b/app/src/main/java/com/fazemeright/chatbotmetcs622/ui/splash/SplashActivity.java new file mode 100644 index 0000000..2619afc --- /dev/null +++ b/app/src/main/java/com/fazemeright/chatbotmetcs622/ui/splash/SplashActivity.java @@ -0,0 +1,149 @@ +package com.fazemeright.chatbotmetcs622.ui.splash; + +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Handler; +import android.view.WindowManager; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.widget.TextView; + +import androidx.work.Constraints; +import androidx.work.PeriodicWorkRequest; +import androidx.work.WorkManager; + +import com.fazemeright.chatbotmetcs622.ui.landing.LandingActivity; +import com.fazemeright.chatbotmetcs622.R; +import com.fazemeright.chatbotmetcs622.ui.base.BaseActivity; +import com.fazemeright.chatbotmetcs622.ui.registration.RegistrationActivity; +import com.fazemeright.chatbotmetcs622.workers.FireBaseSyncWorker; +import com.fazemeright.firebase_api_library.listeners.OnTaskCompleteListener; + +import java.util.concurrent.TimeUnit; + +import timber.log.Timber; + +public class SplashActivity extends BaseActivity { + + private TextView tvAppVersion, tvAppTitle; + + @Override + public void initViews() { + hideSystemUI(); + tvAppVersion = findViewById(R.id.tvAppVersion); + tvAppTitle = findViewById(R.id.tvAppTitle); + + tvAppVersion.setText(getAppVersion()); + + fadeInViews(); + + final Handler handler = new Handler(); + handler.postDelayed(new Runnable() { + @Override + public void run() { + determineIfUserIsLoggedIn(); + } + }, 800); + } + + private void fadeInViews() { + Animation aniFade = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.fade_in); + tvAppTitle.startAnimation(aniFade); + tvAppVersion.startAnimation(aniFade); + } + + private void determineIfUserIsLoggedIn() { + fireBaseApiManager.reloadUserAuthState(new OnTaskCompleteListener() { + @Override + public void onTaskSuccessful() { +// user is logged in, open landing activity + Constraints constraints = new Constraints.Builder() + .setRequiresCharging(true) + .build(); + + PeriodicWorkRequest saveRequest = + new PeriodicWorkRequest.Builder(FireBaseSyncWorker.class, 1, TimeUnit.DAYS) + .setConstraints(constraints) + .build(); + + WorkManager.getInstance(mContext) + .enqueue(saveRequest); + + Timber.i("Open Landing Activity"); + openLandingActivity(); + } + + @Override + public void onTaskCompleteButFailed(String errMsg) { + // user not logged in, open registration activity + Timber.i("Open Registration Activity"); + openRegistrationActivity(); + } + + @Override + public void onTaskFailed(Exception e) { + // user not logged in or could not perform check, open registration activity + Timber.i("Open Registration Activity"); + openRegistrationActivity(); + } + }); + } + + /** + * Call to open RegistrationActivity from the current activity + */ + private void openRegistrationActivity() { + Animation animFadeOut = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.fade_out); + tvAppVersion.startAnimation(animFadeOut); + tvAppTitle.startAnimation(animFadeOut); + + startActivity(new Intent(SplashActivity.this, RegistrationActivity.class)); + finish(); + } + + /** + * Open LandingActivity and finish this one + */ + private void openLandingActivity() { + Animation animFadeOut = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.fade_out); + tvAppVersion.startAnimation(animFadeOut); + tvAppTitle.startAnimation(animFadeOut); + + startActivity(new Intent(SplashActivity.this, LandingActivity.class)); + finish(); + } + + /** + * Call to get the version of the Application + * + * @return version name of the application + */ + private String getAppVersion() { + PackageInfo pInfo; + try { + pInfo = getPackageManager().getPackageInfo(getPackageName(), 0); + return pInfo.versionName; + } catch (PackageManager.NameNotFoundException e) { + return "beta-testing"; + } + } + + /** + * Makes the screen layout to cover the full display of the device + */ + private void hideSystemUI() { + getWindow().setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, + WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); + } + + @Override + public void setListeners() { + + } + + @Override + public int getLayoutResId() { + return R.layout.activity_splash; + } +} diff --git a/app/src/main/java/com/fazemeright/chatbotmetcs622/utils/AppUtils.java b/app/src/main/java/com/fazemeright/chatbotmetcs622/utils/AppUtils.java new file mode 100644 index 0000000..76eb38a --- /dev/null +++ b/app/src/main/java/com/fazemeright/chatbotmetcs622/utils/AppUtils.java @@ -0,0 +1,48 @@ +package com.fazemeright.chatbotmetcs622.utils; + +import android.text.TextUtils; +import android.util.Patterns; + +public class AppUtils { + + /** + * Call to check validity of given email address + * + * @param email given email address + * @return true if given email is valid, else false + */ + public static boolean isValidEmail(String email) { + return !TextUtils.isEmpty(email) && Patterns.EMAIL_ADDRESS.matcher(email).matches(); + } + + /** + * Call to check if given password is valid i.e. it is longer than 7 characters + * + * @param password given password + * @return true if given password is valid, else false + */ + public static boolean isValidPassword(String password) { + return !TextUtils.isEmpty(password) && password.length() > 7; + } + + /** + * Call to check if given password match or not + * + * @param password given password + * @param conPassword given confirm password + * @return true if both passwords match, else false + */ + public static boolean arePasswordsValid(String password, String conPassword) { + return password.equals(conPassword); + } + + /** + * Call to check if given name is valid or not. It should not be empty + * + * @param name given name + * @return true if given name is valid, else false + */ + public static boolean isValidName(String name) { + return !TextUtils.isEmpty(name); + } +} diff --git a/app/src/main/java/com/fazemeright/chatbotmetcs622/workers/FireBaseSyncWorker.java b/app/src/main/java/com/fazemeright/chatbotmetcs622/workers/FireBaseSyncWorker.java new file mode 100644 index 0000000..0febbf8 --- /dev/null +++ b/app/src/main/java/com/fazemeright/chatbotmetcs622/workers/FireBaseSyncWorker.java @@ -0,0 +1,30 @@ +package com.fazemeright.chatbotmetcs622.workers; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.work.Worker; +import androidx.work.WorkerParameters; + +import com.fazemeright.chatbotmetcs622.database.messages.Message; +import com.fazemeright.chatbotmetcs622.repositories.MessageRepository; + +public class FireBaseSyncWorker extends Worker { + public FireBaseSyncWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { + super(context, workerParams); + } + + @NonNull + @Override + public Result doWork() { +// add all messages from Room to FireBase + MessageRepository messageRepository = MessageRepository.getInstance(getApplicationContext()); + for (Message message : messageRepository.getAllMessages()) { + messageRepository.addMessageToFireBase(Message.getHashMap(message)); + } +// get all messages from FireBase to Room + messageRepository.syncMessagesFromFireStoreToRoom(); + + return Result.success(); + } +} diff --git a/app/src/main/res/anim/fade_in.xml b/app/src/main/res/anim/fade_in.xml new file mode 100644 index 0000000..306b605 --- /dev/null +++ b/app/src/main/res/anim/fade_in.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/fade_out.xml b/app/src/main/res/anim/fade_out.xml new file mode 100644 index 0000000..d7841b9 --- /dev/null +++ b/app/src/main/res/anim/fade_out.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-anydpi/ic_send.xml b/app/src/main/res/drawable-anydpi/ic_send.xml new file mode 100644 index 0000000..8b51306 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_send.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-hdpi/ic_send.png b/app/src/main/res/drawable-hdpi/ic_send.png new file mode 100644 index 0000000..fb9e7ed Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_send.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_send.png b/app/src/main/res/drawable-mdpi/ic_send.png new file mode 100644 index 0000000..f3d26b0 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_send.png differ diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..1f6bb29 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable-xhdpi/ic_send.png b/app/src/main/res/drawable-xhdpi/ic_send.png new file mode 100644 index 0000000..205c7a3 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_send.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_send.png b/app/src/main/res/drawable-xxhdpi/ic_send.png new file mode 100644 index 0000000..4cf5259 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_send.png differ diff --git a/app/src/main/res/drawable/brute_force_logo.jpg b/app/src/main/res/drawable/brute_force_logo.jpg new file mode 100644 index 0000000..6bf050f Binary files /dev/null and b/app/src/main/res/drawable/brute_force_logo.jpg differ diff --git a/app/src/main/res/drawable/button_round_primary.xml b/app/src/main/res/drawable/button_round_primary.xml new file mode 100644 index 0000000..910ec26 --- /dev/null +++ b/app/src/main/res/drawable/button_round_primary.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..0d025f9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/lucene_logo.png b/app/src/main/res/drawable/lucene_logo.png new file mode 100644 index 0000000..a714c72 Binary files /dev/null and b/app/src/main/res/drawable/lucene_logo.png differ diff --git a/app/src/main/res/drawable/mongodb_logo.png b/app/src/main/res/drawable/mongodb_logo.png new file mode 100644 index 0000000..47a5aec Binary files /dev/null and b/app/src/main/res/drawable/mongodb_logo.png differ diff --git a/app/src/main/res/drawable/mysql_logo.jpg b/app/src/main/res/drawable/mysql_logo.jpg new file mode 100644 index 0000000..821dbc6 Binary files /dev/null and b/app/src/main/res/drawable/mysql_logo.jpg differ diff --git a/app/src/main/res/drawable/receiver_message_bg.xml b/app/src/main/res/drawable/receiver_message_bg.xml new file mode 100644 index 0000000..55d07be --- /dev/null +++ b/app/src/main/res/drawable/receiver_message_bg.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/sender_message_bg.xml b/app/src/main/res/drawable/sender_message_bg.xml new file mode 100644 index 0000000..44fc8bb --- /dev/null +++ b/app/src/main/res/drawable/sender_message_bg.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/font/aclonica.xml b/app/src/main/res/font/aclonica.xml new file mode 100644 index 0000000..a88a968 --- /dev/null +++ b/app/src/main/res/font/aclonica.xml @@ -0,0 +1,7 @@ + + + diff --git a/app/src/main/res/layout/activity_chat.xml b/app/src/main/res/layout/activity_chat.xml new file mode 100644 index 0000000..65d6b0f --- /dev/null +++ b/app/src/main/res/layout/activity_chat.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_landing.xml b/app/src/main/res/layout/activity_landing.xml new file mode 100644 index 0000000..0615c28 --- /dev/null +++ b/app/src/main/res/layout/activity_landing.xml @@ -0,0 +1,30 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml new file mode 100644 index 0000000..5b9e3e9 --- /dev/null +++ b/app/src/main/res/layout/activity_login.xml @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + +