Showing preview only (790K chars total). Download the full file or copy to clipboard to get everything.
Repository: a914-gowtham/LetsChat
Branch: master
Commit: 8d8c2ffad6c7
Files: 254
Total size: 16.7 MB
Directory structure:
gitextract_cohzfmis/
├── .firebaserc
├── .gitignore
├── .idea/
│ ├── assetWizardSettings.xml
│ ├── caches/
│ │ └── build_file_checksums.ser
│ ├── codeStyles/
│ │ ├── Project.xml
│ │ └── codeStyleConfig.xml
│ ├── compiler.xml
│ ├── inspectionProfiles/
│ │ └── Project_Default.xml
│ ├── jarRepositories.xml
│ ├── misc.xml
│ ├── modules.xml
│ ├── navEditor.xml
│ ├── runConfigurations.xml
│ └── vcs.xml
├── LICENSE
├── README.md
├── app/
│ ├── .gitignore
│ ├── app-debug.apk
│ ├── build.gradle
│ ├── google-services.json
│ ├── proguard-rules.pro
│ └── src/
│ ├── androidTest/
│ │ └── java/
│ │ └── com/
│ │ └── gowtham/
│ │ └── letschat/
│ │ ├── FlowUtilAndroidTest.kt
│ │ ├── HiltTestRunner.kt
│ │ ├── LiveDataUtilAndroidTest.kt
│ │ ├── db/
│ │ │ └── daos/
│ │ │ ├── ChatUserDaoTest.kt
│ │ │ ├── GroupDaoTest.kt
│ │ │ ├── GroupMessageDaoTest.kt
│ │ │ └── MessageDaoTest.kt
│ │ └── di/
│ │ └── TestAppModule.kt
│ ├── main/
│ │ ├── AndroidManifest.xml
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── gowtham/
│ │ │ └── letschat/
│ │ │ ├── FirebasePush.kt
│ │ │ ├── MApplication.kt
│ │ │ ├── core/
│ │ │ │ ├── ChatHandler.kt
│ │ │ │ ├── ChatUserProfileListener.kt
│ │ │ │ ├── ChatUserUtil.kt
│ │ │ │ ├── ContactsQuery.kt
│ │ │ │ ├── GroupChatHandler.kt
│ │ │ │ ├── GroupMsgSender.kt
│ │ │ │ ├── GroupMsgStatusUpdater.kt
│ │ │ │ ├── GroupQuery.kt
│ │ │ │ ├── MessageSender.kt
│ │ │ │ └── MessageStatusUpdater.kt
│ │ │ ├── db/
│ │ │ │ ├── ChatUserDatabase.kt
│ │ │ │ ├── DbRepository.kt
│ │ │ │ ├── DefaultDbRepo.kt
│ │ │ │ ├── TypeConverter.kt
│ │ │ │ ├── daos/
│ │ │ │ │ ├── ChatUserDao.kt
│ │ │ │ │ ├── GroupDao.kt
│ │ │ │ │ ├── GroupMessageDao.kt
│ │ │ │ │ └── MessageDao.kt
│ │ │ │ └── data/
│ │ │ │ ├── ChatUser.kt
│ │ │ │ ├── ChatUserWithMessages.kt
│ │ │ │ ├── Group.kt
│ │ │ │ ├── GroupMessage.kt
│ │ │ │ ├── GroupWithMessages.kt
│ │ │ │ └── Message.kt
│ │ │ ├── di/
│ │ │ │ ├── AppModule.kt
│ │ │ │ └── DbModule.kt
│ │ │ ├── fragments/
│ │ │ │ ├── FAttachment.kt
│ │ │ │ ├── FImageSrcSheet.kt
│ │ │ │ ├── MainFragmentFactory.kt
│ │ │ │ ├── add_group_members/
│ │ │ │ │ ├── AdAddMembers.kt
│ │ │ │ │ ├── AdChip.kt
│ │ │ │ │ ├── AddGroupViewModel.kt
│ │ │ │ │ └── FAddGroupMembers.kt
│ │ │ │ ├── contacts/
│ │ │ │ │ ├── AdContact.kt
│ │ │ │ │ ├── ContactsViewModel.kt
│ │ │ │ │ └── FContacts.kt
│ │ │ │ ├── countries/
│ │ │ │ │ ├── AdCountries.kt
│ │ │ │ │ └── FCountries.kt
│ │ │ │ ├── create_group/
│ │ │ │ │ ├── CreateGroupViewModel.kt
│ │ │ │ │ └── FCreateGroup.kt
│ │ │ │ ├── group_chat/
│ │ │ │ │ ├── AdGroupChat.kt
│ │ │ │ │ ├── FGroupChat.kt
│ │ │ │ │ └── GroupChatViewModel.kt
│ │ │ │ ├── group_chat_home/
│ │ │ │ │ ├── AdGroupChatHome.kt
│ │ │ │ │ ├── FGroupChatHome.kt
│ │ │ │ │ └── GroupChatHomeViewModel.kt
│ │ │ │ ├── login/
│ │ │ │ │ ├── FLogin.kt
│ │ │ │ │ ├── FVerify.kt
│ │ │ │ │ ├── LogInViewModel.kt
│ │ │ │ │ └── LoginRepo.kt
│ │ │ │ ├── myprofile/
│ │ │ │ │ ├── FMyProfile.kt
│ │ │ │ │ └── FMyProfileViewModel.kt
│ │ │ │ ├── profile/
│ │ │ │ │ ├── FProfile.kt
│ │ │ │ │ └── ProfileViewModel.kt
│ │ │ │ ├── search/
│ │ │ │ │ ├── FSearch.kt
│ │ │ │ │ ├── FSearchViewModel.kt
│ │ │ │ │ └── SearchRepo.kt
│ │ │ │ ├── single_chat/
│ │ │ │ │ ├── AdChat.kt
│ │ │ │ │ ├── FSingleChat.kt
│ │ │ │ │ └── SingleChatViewModel.kt
│ │ │ │ └── single_chat_home/
│ │ │ │ ├── AdSingleChatHome.kt
│ │ │ │ ├── FSingleChatHome.kt
│ │ │ │ └── SingleChatHomeViewModel.kt
│ │ │ ├── models/
│ │ │ │ ├── Contact.kt
│ │ │ │ ├── Country.kt
│ │ │ │ ├── ModelDeviceDetails.kt
│ │ │ │ ├── ModelMobile.kt
│ │ │ │ ├── MyImage.kt
│ │ │ │ ├── PushMsg.kt
│ │ │ │ ├── UserProfile.kt
│ │ │ │ └── UserStatus.kt
│ │ │ ├── services/
│ │ │ │ ├── GroupUploadWorker.kt
│ │ │ │ └── UploadWorker.kt
│ │ │ ├── ui/
│ │ │ │ └── activities/
│ │ │ │ ├── ActBase.kt
│ │ │ │ ├── ActSplash.kt
│ │ │ │ ├── MainActivity.kt
│ │ │ │ └── SharedViewModel.kt
│ │ │ ├── utils/
│ │ │ │ ├── BindingAdapters.kt
│ │ │ │ ├── BottomSheetEvent.kt
│ │ │ │ ├── ConnectionChangeEvent.kt
│ │ │ │ ├── Constants.kt
│ │ │ │ ├── Countries.kt
│ │ │ │ ├── DataStorePreference.kt
│ │ │ │ ├── DiffCallbackChatUser.kt
│ │ │ │ ├── Event.kt
│ │ │ │ ├── Events/
│ │ │ │ │ ├── EventAudioMsg.kt
│ │ │ │ │ └── EventUpdateRecycleItem.kt
│ │ │ │ ├── GroupMsgActionReceiver.kt
│ │ │ │ ├── ImageUtils.kt
│ │ │ │ ├── ItemClickListener.kt
│ │ │ │ ├── LoadState.kt
│ │ │ │ ├── LogInFailedState.kt
│ │ │ │ ├── LogMessage.kt
│ │ │ │ ├── MPreference.kt
│ │ │ │ ├── NActionReceiver.kt
│ │ │ │ ├── NotificationUtils.kt
│ │ │ │ ├── OnSuccessListener.kt
│ │ │ │ ├── ScreenState.kt
│ │ │ │ ├── UserUtils.kt
│ │ │ │ ├── Utils.kt
│ │ │ │ ├── Validator.kt
│ │ │ │ └── ViewUtils.kt
│ │ │ └── views/
│ │ │ ├── CustomEditText.kt
│ │ │ ├── CustomProgress.kt
│ │ │ ├── CustomProgressView.kt
│ │ │ ├── MainNavHostFragment.kt
│ │ │ ├── PausableProgressBar.kt
│ │ │ ├── PausableScaleAnimation.kt
│ │ │ └── StoriesProgressView.kt
│ │ └── res/
│ │ ├── anim/
│ │ │ ├── slide_in_right.xml
│ │ │ └── slide_out_left.xml
│ │ ├── drawable/
│ │ │ ├── ic_arrow_back.xml
│ │ │ ├── ic_arrow_down.xml
│ │ │ ├── ic_arrow_r8.xml
│ │ │ ├── ic_clear.xml
│ │ │ ├── ic_close_24px.xml
│ │ │ ├── ic_launcher_background.xml
│ │ │ ├── ic_menu_24px.xml
│ │ │ ├── ic_search.xml
│ │ │ ├── selector_menu.xml
│ │ │ ├── shape_audio_bg.xml
│ │ │ ├── shape_border_line.xml
│ │ │ ├── shape_btn_bg.xml
│ │ │ ├── shape_circle.xml
│ │ │ ├── shape_circle_blue.xml
│ │ │ ├── shape_contact_selected.xml
│ │ │ ├── shape_divider.xml
│ │ │ ├── shape_edit_bg.xml
│ │ │ ├── shape_gradient.xml
│ │ │ ├── shape_home_bg.xml
│ │ │ ├── shape_menu_active.xml
│ │ │ ├── shape_menu_non_active.xml
│ │ │ ├── shape_msg_bg.xml
│ │ │ ├── shape_radius.xml
│ │ │ ├── shape_receive_msg.xml
│ │ │ ├── shape_receive_msg_corned.xml
│ │ │ ├── shape_send_msg.xml
│ │ │ ├── shape_send_msg_corned.xml
│ │ │ └── shape_unread_count.xml
│ │ ├── drawable-v24/
│ │ │ └── ic_launcher_foreground.xml
│ │ ├── layout/
│ │ │ ├── act_chat.xml
│ │ │ ├── act_splash.xml
│ │ │ ├── activity_main.xml
│ │ │ ├── activity_main2.xml
│ │ │ ├── alert_dialog.xml
│ │ │ ├── alert_logout.xml
│ │ │ ├── f_add_group_members.xml
│ │ │ ├── f_attachment.xml
│ │ │ ├── f_contacts.xml
│ │ │ ├── f_countries.xml
│ │ │ ├── f_create_group.xml
│ │ │ ├── f_group_chat.xml
│ │ │ ├── f_group_chat_home.xml
│ │ │ ├── f_image_src_sheet.xml
│ │ │ ├── f_login.xml
│ │ │ ├── f_my_profile.xml
│ │ │ ├── f_profile.xml
│ │ │ ├── f_search.xml
│ │ │ ├── f_single_chat.xml
│ │ │ ├── f_single_chat_home.xml
│ │ │ ├── f_verify.xml
│ │ │ ├── load_state_footer.xml
│ │ │ ├── pausable_progress.xml
│ │ │ ├── progress_dialog.xml
│ │ │ ├── row_add_member.xml
│ │ │ ├── row_audio_receive.xml
│ │ │ ├── row_audio_sent.xml
│ │ │ ├── row_chat.xml
│ │ │ ├── row_chip.xml
│ │ │ ├── row_contact.xml
│ │ │ ├── row_country.xml
│ │ │ ├── row_group_audio_receive.xml
│ │ │ ├── row_group_audio_sent.xml
│ │ │ ├── row_group_chat.xml
│ │ │ ├── row_group_image_receive.xml
│ │ │ ├── row_group_image_sent.xml
│ │ │ ├── row_group_sticker_receive.xml
│ │ │ ├── row_group_sticker_sent.xml
│ │ │ ├── row_group_txt_sent.xml
│ │ │ ├── row_grp_txt_receive.xml
│ │ │ ├── row_image_receive.xml
│ │ │ ├── row_image_sent.xml
│ │ │ ├── row_receive_message.xml
│ │ │ ├── row_search_contact.xml
│ │ │ ├── row_sent_message.xml
│ │ │ ├── row_sticker_receive.xml
│ │ │ ├── row_sticker_sent.xml
│ │ │ ├── view_chat_btm.xml
│ │ │ ├── view_chat_toolbar.xml
│ │ │ ├── view_group_chat_btm.xml
│ │ │ └── view_group_chat_toolbar.xml
│ │ ├── menu/
│ │ │ ├── menu_btm_nav.xml
│ │ │ ├── menu_contacts.xml
│ │ │ ├── menu_option.xml
│ │ │ └── menu_search.xml
│ │ ├── navigation/
│ │ │ └── nav_graph.xml
│ │ ├── raw/
│ │ │ ├── empty_state.json
│ │ │ ├── lottie_send.json
│ │ │ ├── lottie_tick.json
│ │ │ └── lottie_voice.json
│ │ ├── values/
│ │ │ ├── attrs.xml
│ │ │ ├── colors.xml
│ │ │ ├── dimen.xml
│ │ │ ├── ids.xml
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ │ └── xml/
│ │ ├── network_security_config.xml
│ │ └── provider_path.xml
│ ├── release/
│ │ └── res/
│ │ └── values/
│ │ └── google_maps_api.xml
│ └── test/
│ └── java/
│ └── com/
│ └── gowtham/
│ └── letschat/
│ ├── LiveDataUtilAndroid.kt
│ ├── db/
│ │ └── DbRepositoryTest.kt
│ ├── fragments/
│ │ └── single_chat_home/
│ │ └── SingleChatHomeViewModelTest.kt
│ └── utils/
│ ├── MainCoroutineRule.kt
│ └── ValidatorTest.kt
├── build.gradle
├── firebase.json
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
================================================
FILE CONTENTS
================================================
================================================
FILE: .firebaserc
================================================
{
"projects": {
"default": "letschat-31c80"
}
}
================================================
FILE: .gitignore
================================================
# Files for the ART/Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
out/
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/
# Intellij
*.iml
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
.idea/dictionaries
.idea/libraries
# Keystore files
*.jks
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
# Freeline
freeline.py
freeline/
freeline_project_description.json
================================================
FILE: .idea/assetWizardSettings.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="WizardSettings">
<option name="children">
<map>
<entry key="imageWizard">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="imageAssetPanel">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="actionbar">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="clipArt">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
<entry key="imagePath" value="/tmp/ic_android_black_24dp.xml" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="text">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="textAsset">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="launcher">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="foregroundClipArt">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="imagePath" value="/tmp/ic_android_black_24dp.xml" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="foregroundImage">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
<entry key="imagePath" value="$USER_HOME$/Downloads/send.png" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="foregroundText">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="foregroundTextAsset">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
<option name="values">
<map>
<entry key="backgroundAssetType" value="COLOR" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="launcherLegacy">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="clipArt">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
<entry key="imagePath" value="/tmp/ic_android_black_24dp.xml" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="text">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="textAsset">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="notification">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="clipArt">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
<entry key="imagePath" value="/tmp/ic_android_black_24dp.xml" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="image">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
<entry key="imagePath" value="$USER_HOME$/Downloads/send.png" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="text">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="textAsset">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
<option name="values">
<map>
<entry key="assetType" value="IMAGE" />
<entry key="imageAsset" value="$USER_HOME$/Downloads/send.png" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="tvBanner">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="foregroundText">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="tvChannel">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="foregroundClipArt">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="imagePath" value="/tmp/ic_android_black_24dp.xml" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="foregroundImage">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="foregroundText">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="foregroundTextAsset">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
<option name="values">
<map>
<entry key="outputIconType" value="NOTIFICATION" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="vectorWizard">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="vectorAssetStep">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="clipartAsset">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="url" value="jar:file:/home/quadkastpc/Documents/android-studio/plugins/android/lib/android.jar!/images/material/icons/materialicons/arrow_back/baseline_arrow_back_24.xml" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
<option name="values">
<map>
<entry key="color" value="ffffff" />
<entry key="outputName" value="ic_arrow_back" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</component>
</project>
================================================
FILE: .idea/codeStyles/Project.xml
================================================
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value>
<package name="java.util" alias="false" withSubpackages="false" />
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
<package name="io.ktor" alias="false" withSubpackages="true" />
</value>
</option>
<option name="PACKAGES_IMPORT_LAYOUT">
<value>
<package name="" alias="false" withSubpackages="true" />
<package name="java" alias="false" withSubpackages="true" />
<package name="javax" alias="false" withSubpackages="true" />
<package name="kotlin" alias="false" withSubpackages="true" />
<package name="" alias="true" withSubpackages="true" />
</value>
</option>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>
================================================
FILE: .idea/codeStyles/codeStyleConfig.xml
================================================
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>
================================================
FILE: .idea/compiler.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" />
</component>
</project>
================================================
FILE: .idea/inspectionProfiles/Project_Default.xml
================================================
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="MoveVariableDeclarationIntoWhen" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
</profile>
</component>
================================================
FILE: .idea/jarRepositories.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="BintrayJCenter" />
<option name="name" value="BintrayJCenter" />
<option name="url" value="https://jcenter.bintray.com/" />
</remote-repository>
<remote-repository>
<option name="id" value="Google" />
<option name="name" value="Google" />
<option name="url" value="https://dl.google.com/dl/android/maven2/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven" />
<option name="name" value="maven" />
<option name="url" value="https://jitpack.io" />
</remote-repository>
<remote-repository>
<option name="id" value="MavenRepo" />
<option name="name" value="MavenRepo" />
<option name="url" value="https://repo.maven.apache.org/maven2/" />
</remote-repository>
</component>
</project>
================================================
FILE: .idea/misc.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="false" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>
================================================
FILE: .idea/modules.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/modules/LetsChat.iml" filepath="$PROJECT_DIR$/.idea/modules/LetsChat.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/LetsChat.app.iml" filepath="$PROJECT_DIR$/.idea/modules/app/LetsChat.app.iml" />
</modules>
</component>
</project>
================================================
FILE: .idea/navEditor.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="navEditor-manualLayoutAlgorithm2">
<option name="myPositions">
<map>
<entry key="nav_graph.xml">
<value>
<LayoutPositions>
<option name="myPositions">
<map>
<entry key="FAddGroupMembers">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="988" />
<option name="y" value="1278" />
</Point>
</option>
<option name="myPositions">
<map>
<entry key="action_FAddGroupMembers_to_FCreateGroup">
<value>
<LayoutPositions />
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="FContacts">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="925" />
<option name="y" value="343" />
</Point>
</option>
<option name="myPositions">
<map>
<entry key="action_FContacts_to_Chat">
<value>
<LayoutPositions />
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="FCountries">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="-50" />
<option name="y" value="660" />
</Point>
</option>
<option name="myPositions">
<map>
<entry key="action_FCountries_to_FLogIn">
<value>
<LayoutPositions />
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="FCreateGroup">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="1254" />
<option name="y" value="1273" />
</Point>
</option>
<option name="myPositions">
<map>
<entry key="action_FCreateGroup_to_FGroupChat">
<value>
<LayoutPositions />
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="FGroupChat">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="1525" />
<option name="y" value="1193" />
</Point>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="FGroupChatHome">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="644" />
<option name="y" value="1022" />
</Point>
</option>
<option name="myPositions">
<map>
<entry key="action_FGroupChatHome_to_FAddGroupMembers">
<value>
<LayoutPositions />
</value>
</entry>
<entry key="action_FGroupChatHome_to_FGroupChat">
<value>
<LayoutPositions />
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="FLogIn">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="-263" />
<option name="y" value="295" />
</Point>
</option>
<option name="myPositions">
<map>
<entry key="action_FLogIn_to_FCountries">
<value>
<LayoutPositions />
</value>
</entry>
<entry key="action_FLogIn_to_FProfile">
<value>
<LayoutPositions />
</value>
</entry>
<entry key="action_FLogIn_to_FSingleChatHome">
<value>
<LayoutPositions />
</value>
</entry>
<entry key="action_FLogIn_to_FVerify">
<value>
<LayoutPositions />
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="FMyProfile">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="686" />
<option name="y" value="-230" />
</Point>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="FProfile">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="435" />
<option name="y" value="278" />
</Point>
</option>
<option name="myPositions">
<map>
<entry key="action_FProfile_to_FSingleChatHome">
<value>
<LayoutPositions />
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="FSearch">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="872" />
<option name="y" value="-39" />
</Point>
</option>
<option name="myPositions">
<map>
<entry key="action_FSearch_to_FSingleChat">
<value>
<LayoutPositions />
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="FSingleChat">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="1423" />
<option name="y" value="383" />
</Point>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="FSingleChatHome">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="736" />
<option name="y" value="218" />
</Point>
</option>
<option name="myPositions">
<map>
<entry key="action_FSingleChatHome_to_FContacts">
<value>
<LayoutPositions />
</value>
</entry>
<entry key="action_FSingleChatHome_to_FGroupChat">
<value>
<LayoutPositions />
</value>
</entry>
<entry key="action_FSingleChat_to_FChat">
<value>
<LayoutPositions />
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="FVerify">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="112" />
<option name="y" value="266" />
</Point>
</option>
<option name="myPositions">
<map>
<entry key="action_FVerify_to_FProfile">
<value>
<LayoutPositions />
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="nav_graph_chat.xml">
<value>
<LayoutPositions>
<option name="myPositions">
<map>
<entry key="FChat">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="12" />
<option name="y" value="12" />
</Point>
</option>
</LayoutPositions>
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
</map>
</option>
</component>
</project>
================================================
FILE: .idea/runConfigurations.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>
================================================
FILE: .idea/vcs.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2021 Gowtham Balamurugan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# LetsChat
LetsChat is a Sample Messaging Android application built to demonstrate the use of Modern Android development tools - (Kotlin, Coroutines, Dagger-Hilt, Architecture Components, MVVM, Room, Coil) and Firebase
- Create a firebase project and replace the google-services.json file which you get from your firebase project console
- Following firebase services need to be enabled in the firebase console
- Phone Auth
- Cloud Firestore
- Realtime Database
- Storage
- Composite indexes should be created for contact query(link for enabling indexes could be found from logcat while using the app)
***You can Install and test latest LetsChat app from below 👇***
[](https://github.com/a914-gowtham/LetsChat/blob/master/app/app-debug.apk)
<p float="center">
<img src="demo_video.gif" />
</p>
## Features ✨
- One on one chat
- Group Chat
- Typing status for one on one and group chat
- Unread messages count
- Message status for failed,sent,delivered and seen
- Supported message types
- Text
- Voice
- Sticker and Gif
- Attachments
- Image
- Video - InProgress
- Notification actions for reply and mark as read
- Search users by username
## Built With 🛠
- [Kotlin](https://kotlinlang.org/) - First class and official programming language for Android development.
- [Coroutines & Flow](https://kotlinlang.org/docs/reference/coroutines-overview.html) - For asynchronous and more..
- [Android Architecture Components](https://developer.android.com/topic/libraries/architecture) - Collection of libraries that help you design quality, robust, testable, and maintainable apps.
- [Navigation Component](https://developer.android.com/guide/navigation/navigation-getting-started) - Handle everything needed for in-app navigation with a single Activity.
- [LiveData](https://developer.android.com/topic/libraries/architecture/livedata) - Data objects that notify views when the underlying database changes.
- [ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel) - Stores UI-related data that isn't destroyed on UI changes.
- [DataBinding](https://github.com/android/databinding-samples) - Generates a binding class for each XML layout file present in that module and allows you to more easily write code that interacts with views.Declaratively bind observable data to UI elements.
- [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager) - WorkManager is an API that makes it easy to schedule deferrable, asynchronous tasks that are expected to run even if the app exits or the device restarts.
- [Room](https://developer.android.com/topic/libraries/architecture/room) - SQLite object mapping library.
- [Dependency Injection](https://developer.android.com/training/dependency-injection) -
- [Dagger-Hilt](https://dagger.dev/hilt/) - Standard library to incorporate Dagger dependency injection into an Android application.
- [Hilt-ViewModel](https://developer.android.com/training/dependency-injection/hilt-jetpack) - DI for injecting `ViewModel`.
- [Firebase](https://firebase.google.com/) -
- [Cloud Messaging](https://firebase.google.com/products/cloud-messaging) - For Sending Notification to client app.
- [Cloud Firestore](https://firebase.google.com/docs/firestore) - Flexible, scalable NoSQL cloud database to store and sync data.
- [Cloud Storage](https://firebase.google.com/docs/storage) - For Store and serve user-generated content.
- [Authentication](https://firebase.google.com/docs/auth) - For Creating account with mobile number.
- [Kotlin Serializer](https://github.com/Kotlin/kotlinx.serialization) - Convert Specific Classes to and from JSON.Runtime library with core serialization API and support libraries with various serialization formats.
- [Coil-kt](https://coil-kt.github.io/coil/) - An image loading library for Android backed by Kotlin Coroutines.
================================================
FILE: app/.gitignore
================================================
/build
================================================
FILE: app/app-debug.apk
================================================
[File too large to display: 16.0 MB]
================================================
FILE: app/build.gradle
================================================
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-parcelize'
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'
apply plugin: 'androidx.navigation.safeargs.kotlin'
apply plugin: 'com.google.firebase.crashlytics'
apply plugin: 'com.google.gms.google-services'
apply plugin: 'kotlinx-serialization'
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
buildFeatures{
dataBinding = true
viewBinding = true
}
defaultConfig {
applicationId "com.gowtham.letschat"
minSdkVersion 19
targetSdkVersion 30
versionCode 1
versionName "1.0"
buildConfigField("String","SERVER_KEY",SERVER_KEY)
multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
sourceSets {
main {
assets.srcDirs = ['src/main/assets', 'src/main/assets/']
res.srcDirs = ['src/main/res', 'src/main/res/drawable']
}
}
dexOptions {
javaMaxHeapSize "4g"
}
}
dependencies {
def work_version = "2.5.0"
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.5.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.11'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
// Activity KTX for viewModels()
implementation "androidx.activity:activity-ktx:1.2.3"
//Databinding compiler
kapt "com.android.databinding:compiler:3.1.4"
//dagger-hilt
implementation "com.google.dagger:hilt-android:2.36" //don't upgrade unless same version of kapt availale
kapt "com.google.dagger:hilt-android-compiler:2.36"
implementation 'androidx.hilt:hilt-work:1.0.0'
kapt "androidx.hilt:hilt-compiler:1.0.0"
//event bus
implementation 'org.greenrobot:eventbus:3.2.0'
implementation "androidx.recyclerview:recyclerview:1.2.1"
//mvvm
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
//Android Navigation Architecture
implementation "androidx.navigation:navigation-fragment-ktx:2.3.5"
implementation "androidx.navigation:navigation-ui-ktx:2.3.5"
// Room
implementation "androidx.room:room-runtime:2.4.0-alpha03"
kapt "androidx.room:room-compiler:2.4.0-alpha03"
// Kotlin Extensions and Coroutines support for Room
implementation "androidx.room:room-ktx:2.4.0-alpha03"
//For device to device notification sending
implementation 'com.github.a914-gowtham:fcm-sender:1.0.2'
//Lottie
implementation 'com.airbnb.android:lottie:3.7.0'
//firebase
//By using the Firebase Android BoM, your app will always use compatible versions of the Firebase Android libraries.
implementation platform('com.google.firebase:firebase-bom:26.0.0')
// When using the BoM, you don't specify versions in Firebase library dependencies
implementation 'com.google.firebase:firebase-analytics-ktx'
implementation 'com.google.firebase:firebase-auth-ktx'
implementation 'com.google.firebase:firebase-firestore-ktx'
implementation 'androidx.browser:browser:1.3.0' //
implementation 'com.google.firebase:firebase-storage-ktx'
implementation 'com.google.firebase:firebase-messaging-ktx'
implementation 'com.google.firebase:firebase-crashlytics-ktx'
implementation 'com.google.firebase:firebase-database-ktx'
implementation 'com.jakewharton.timber:timber:4.7.1'
//Image loader
implementation("io.coil-kt:coil:1.2.1")
implementation("io.coil-kt:coil-gif:1.0.0")
implementation 'com.github.CanHub:Android-Image-Cropper:3.3.5'
//image zoom
implementation 'com.github.stfalcon-studio:StfalconImageViewer:v1.0.1'
//Kotlin seriler
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.1")
// Work Manager
implementation "androidx.work:work-runtime-ktx:$work_version"
implementation "androidx.datastore:datastore-preferences:1.0.0-beta01"
// Local Unit Tests
implementation "androidx.test:core:1.3.0"
testImplementation "junit:junit:4.13.2"
testImplementation "org.hamcrest:hamcrest-all:1.3"
testImplementation "androidx.arch.core:core-testing:2.1.0"
testImplementation "org.robolectric:robolectric:4.3.1"
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.2.1"
testImplementation "com.google.truth:truth:1.0.1"
testImplementation "org.mockito:mockito-core:2.21.0"
// Instrumented Unit Tests
androidTestImplementation "junit:junit:4.13.2"
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.2.1"
androidTestImplementation "androidx.arch.core:core-testing:2.1.0"
androidTestImplementation "com.google.truth:truth:1.0.1"
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
androidTestImplementation "org.mockito:mockito-core:2.28.2"
androidTestImplementation 'com.google.dagger:hilt-android-testing:2.35'
kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.36'
}
================================================
FILE: app/google-services.json
================================================
{
"project_info": {
"project_number": "713876726773",
"firebase_url": "https://letschat-31c80.firebaseio.com",
"project_id": "letschat-31c80",
"storage_bucket": "letschat-31c80.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:713876726773:android:7516967105f42edfd8b6bc",
"android_client_info": {
"package_name": "com.gowtham.letschat"
}
},
"oauth_client": [
{
"client_id": "713876726773-jefdmp8hqi7meehikl7qc0knr5elvgdg.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.gowtham.letschat",
"certificate_hash": "8f067951f97ceb66ed871e1bdf62404f32ce1101"
}
},
{
"client_id": "713876726773-mrqj2mpsm1jcavcursp6vmelglkeb7i6.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.gowtham.letschat",
"certificate_hash": "a7ee622ee9d484ca3a3f44b18bfdb642f21bba6d"
}
},
{
"client_id": "713876726773-n8oqdoievdudn72eulv0t5ebqv8mah61.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.gowtham.letschat",
"certificate_hash": "542c95c560a25358c37923ddd271a737c730c131"
}
},
{
"client_id": "713876726773-0clemuuk99ed2clrqjor4u6s8jkgl98h.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyAZfALlXiAfKOOQCAbcgh9wRphqYhhnYOI"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "713876726773-0clemuuk99ed2clrqjor4u6s8jkgl98h.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
}
],
"configuration_version": "1"
}
================================================
FILE: app/proguard-rules.pro
================================================
# 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
================================================
FILE: app/src/androidTest/java/com/gowtham/letschat/FlowUtilAndroidTest.kt
================================================
package com.gowtham.letschat
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import com.gowtham.letschat.utils.LogMessage
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.cancellable
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.onCompletion
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
/**
* Gets the value of a [LiveData] or waits for it to have one, with a timeout.
*
* Use this extension from host-side (JVM) tests. It's recommended to use it alongside
* `InstantTaskExecutorRule` or a similar mechanism to execute tasks synchronously.
*/
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
fun <T> Flow<T>.getOrAwaitValue(
time: Long = 2,
timeUnit: TimeUnit = TimeUnit.SECONDS,
afterObserve: () -> Unit = {}
): T {
var data: T? = null
val latch = CountDownLatch(1)
val scope= CoroutineScope(Dispatchers.IO).launch {
this@getOrAwaitValue.collect {
data=it
cancel()
latch.countDown()
}
}
try {
afterObserve.invoke()
// Don't wait indefinitely if the LiveData is not set.
if (!latch.await(time, timeUnit)) {
throw TimeoutException("LiveData value was never set.")
}
} finally {
if (scope.isActive)
scope.cancel()
}
@Suppress("UNCHECKED_CAST")
return data as T
}
================================================
FILE: app/src/androidTest/java/com/gowtham/letschat/HiltTestRunner.kt
================================================
package com.gowtham.letschat
import android.app.Application
import android.content.Context
import androidx.test.runner.AndroidJUnitRunner
import dagger.hilt.android.testing.HiltTestApplication
class HiltTestRunner : AndroidJUnitRunner() {
override fun newApplication(
cl: ClassLoader?,
className: String?,
context: Context?
): Application {
return super.newApplication(cl, HiltTestApplication::class.java.name, context)
}
}
================================================
FILE: app/src/androidTest/java/com/gowtham/letschat/LiveDataUtilAndroidTest.kt
================================================
package com.gowtham.letschat
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
/**
* Gets the value of a [LiveData] or waits for it to have one, with a timeout.
*
* Use this extension from host-side (JVM) tests. It's recommended to use it alongside
* `InstantTaskExecutorRule` or a similar mechanism to execute tasks synchronously.
*/
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
fun <T> LiveData<T>.getOrAwaitValue(
time: Long = 2,
timeUnit: TimeUnit = TimeUnit.SECONDS,
afterObserve: () -> Unit = {}
): T {
var data: T? = null
val latch = CountDownLatch(1)
val observer = object : Observer<T> {
override fun onChanged(o: T?) {
data = o
latch.countDown()
this@getOrAwaitValue.removeObserver(this)
}
}
this.observeForever(observer)
try {
afterObserve.invoke()
// Don't wait indefinitely if the LiveData is not set.
if (!latch.await(time, timeUnit)) {
throw TimeoutException("LiveData value was never set.")
}
} finally {
this.removeObserver(observer)
}
@Suppress("UNCHECKED_CAST")
return data as T
}
================================================
FILE: app/src/androidTest/java/com/gowtham/letschat/db/daos/ChatUserDaoTest.kt
================================================
package com.gowtham.letschat.db.daos
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth.assertThat
import com.gowtham.letschat.db.ChatUserDatabase
import com.gowtham.letschat.db.data.ChatUser
import com.gowtham.letschat.models.UserProfile
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runBlockingTest
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import javax.inject.Inject
import javax.inject.Named
@ExperimentalCoroutinesApi
@SmallTest
@HiltAndroidTest
class ChatUserDaoTest {
@get:Rule
var hiltRule=HiltAndroidRule(this)
@get:Rule
var instantTaskExecutorRule=InstantTaskExecutorRule()
@Inject
@Named("test_db")
lateinit var database: ChatUserDatabase
private lateinit var chatUserDao: ChatUserDao
@Before
fun setUp(){
hiltRule.inject()
chatUserDao=database.getChatUserDao()
}
@After
fun tearDown(){
database.close()
}
@Test
fun insert_ChatUser() = runBlockingTest {
val chatUser=ChatUser("testUser1","Gowtham", UserProfile("testUser1",13232113L,123321321L),)
chatUserDao.insertUser(chatUser)
val chatUsers=chatUserDao.getChatUserList()
assertThat(chatUsers).contains(chatUser)
}
@Test
fun get_ChatUser_ById() = runBlockingTest {
val user=ChatUser("testId","Gowtham", UserProfile("testId",13232113L,123321321L),)
chatUserDao.insertUser(user)
val chatUser=chatUserDao.getChatUserById("testId")
assertThat(chatUser).isNotNull()
}
@Test
fun delete_User_ById() = runBlockingTest {
val user=ChatUser("testDeleteUserId","Gowtham", UserProfile("testDeleteUserId",13232113L,123321321L),)
chatUserDao.insertUser(user)
chatUserDao.deleteUserById("testDeleteUserId")
val chatUsers=chatUserDao.getChatUserList()
assertThat(chatUsers).doesNotContain(user)
}
}
================================================
FILE: app/src/androidTest/java/com/gowtham/letschat/db/daos/GroupDaoTest.kt
================================================
package com.gowtham.letschat.db.daos
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
import com.gowtham.letschat.db.ChatUserDatabase
import com.gowtham.letschat.db.data.ChatUser
import com.gowtham.letschat.db.data.Group
import com.gowtham.letschat.getOrAwaitValue
import com.gowtham.letschat.models.UserProfile
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runBlockingTest
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import javax.inject.Inject
import javax.inject.Named
@ExperimentalCoroutinesApi
@SmallTest
@HiltAndroidTest
class GroupDaoTest {
@get:Rule
var hiltRule = HiltAndroidRule(this)
@get:Rule
var instantTaskExecutorRule= InstantTaskExecutorRule()
@Inject
@Named("test_db")
lateinit var database: ChatUserDatabase
private lateinit var groupDao: GroupDao
@Before
fun setUp() {
hiltRule.inject()
groupDao = database.getGroupDao()
}
@After
fun tearDown() {
database.close()
}
@Test
fun insert_Group() = runBlockingTest {
val group=Group("testId",members = ArrayList(),profiles = ArrayList())
groupDao.insertGroup(group)
val groups=groupDao.getAllGroup().getOrAwaitValue()
assertThat(groups).contains(group)
}
@Test
fun insert_Multiple_Group() {
runBlockingTest {
val group1=Group("testId1",members = ArrayList(),profiles = ArrayList())
val group2=Group("testId2",members = ArrayList(),profiles = ArrayList())
groupDao.insertMultipleGroup(listOf(group1,group2))
val groups=groupDao.getAllGroup().getOrAwaitValue()
assertThat(groups).containsAtLeast(group1,group2)
}
}
@Test
fun get_Group_ById() {
runBlockingTest {
val newGroup=Group("testId8",members = ArrayList(),profiles = ArrayList())
groupDao.insertGroup(newGroup)
val group=groupDao.getGroupById(newGroup.id)
assertThat(group).isNotNull()
}
}
@Test
fun delete_Group_ById() {
runBlockingTest {
val group=Group("testId5",members = ArrayList(),profiles = ArrayList())
groupDao.insertGroup(group)
groupDao.deleteGroupById(group.id)
val groups=groupDao.getAllGroup().getOrAwaitValue()
assertThat(groups).doesNotContain(group)
}
}
}
================================================
FILE: app/src/androidTest/java/com/gowtham/letschat/db/daos/GroupMessageDaoTest.kt
================================================
package com.gowtham.letschat.db.daos
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth.assertThat
import com.gowtham.letschat.db.ChatUserDatabase
import com.gowtham.letschat.db.data.*
import com.gowtham.letschat.getOrAwaitValue
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runBlockingTest
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import javax.inject.Inject
import javax.inject.Named
@ExperimentalCoroutinesApi
@SmallTest
@HiltAndroidTest
class GroupMessageDaoTest {
@get:Rule
var hiltRule = HiltAndroidRule(this)
@get:Rule
var instantTaskExecutorRule= InstantTaskExecutorRule()
@Inject
@Named("test_db")
lateinit var database: ChatUserDatabase
private lateinit var groupMessageDao: GroupMessageDao
@Before
fun setUp() {
hiltRule.inject()
groupMessageDao = database.getGroupMessageDao()
}
@After
fun tearDown() {
database.close()
}
@Test
fun insert_Message() = runBlockingTest {
val message=GroupMessage(1,"testGroupId","fromMe", ArrayList(),
"gowtham","",textMessage = TextMessage(),
imageMessage = ImageMessage(),audioMessage = AudioMessage(),videoMessage = VideoMessage(),
fileMessage = FileMessage(),deliveryTime = ArrayList(),seenTime = ArrayList(),status = ArrayList()
)
groupMessageDao.insertMessage(message)
val messages=groupMessageDao.getAllMessages().getOrAwaitValue()
assertThat(messages).contains(message)
}
@Test
fun insert_Multiple_Messages() {
runBlockingTest {
val message1=GroupMessage(2,"testGroupId1","fromMe", ArrayList(),
"gowtham","",textMessage = TextMessage(),
imageMessage = ImageMessage(),audioMessage = AudioMessage(),videoMessage = VideoMessage(),
fileMessage = FileMessage(),deliveryTime = ArrayList(),seenTime = ArrayList(),status = ArrayList()
)
val message2=GroupMessage(3,"testGroupId2","fromMe", ArrayList(),
"gowtham","",textMessage = TextMessage(),
imageMessage = ImageMessage(),audioMessage = AudioMessage(),videoMessage = VideoMessage(),
fileMessage = FileMessage(),deliveryTime = ArrayList(),seenTime = ArrayList(),status = ArrayList()
)
groupMessageDao.insertMultipleMessage(listOf(message1,message2))
val messages=groupMessageDao.getAllMessages().getOrAwaitValue()
assertThat(messages).containsAtLeast(message1,message2)
}
}
}
================================================
FILE: app/src/androidTest/java/com/gowtham/letschat/db/daos/MessageDaoTest.kt
================================================
package com.gowtham.letschat.db.daos
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
import com.gowtham.letschat.db.ChatUserDatabase
import com.gowtham.letschat.db.data.*
import com.gowtham.letschat.getOrAwaitValue
import com.gowtham.letschat.models.UserProfile
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runBlockingTest
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import javax.inject.Inject
import javax.inject.Named
@ExperimentalCoroutinesApi
@SmallTest
@HiltAndroidTest
class MessageDaoTest {
@get:Rule
var hiltRule = HiltAndroidRule(this)
@get:Rule
var instantTaskExecutorRule= InstantTaskExecutorRule()
@Inject
@Named("test_db")
lateinit var database: ChatUserDatabase
private lateinit var messageDao: MessageDao
@Before
fun setUp() {
hiltRule.inject()
messageDao = database.getMessageDao()
}
@After
fun tearDown() {
database.close()
}
@Test
fun insert_Message() = runBlockingTest {
val message=Message(2,
0,0,"fromId","toId","Gowtham","",textMessage = TextMessage(),
imageMessage = ImageMessage(),audioMessage = AudioMessage(),videoMessage = VideoMessage(),
fileMessage = FileMessage(),chatUserId = "",chatUsers = ArrayList(),
)
messageDao.insertMessage(message)
val messages=messageDao.getAllMessages().getOrAwaitValue()
assertThat(messages).contains(message)
}
@Test
fun insert_Multiple_Messages() {
runBlockingTest {
val message1=Message(3,
0,0,"fromId","toId","Gowtham","",textMessage = TextMessage(),
imageMessage = ImageMessage(),audioMessage = AudioMessage(),videoMessage = VideoMessage(),
fileMessage = FileMessage(),chatUserId = "",chatUsers = ArrayList(),
)
val message2=Message(4,
0,0,"fromId","toId","Gowtham","",textMessage = TextMessage(),
imageMessage = ImageMessage(),audioMessage = AudioMessage(),videoMessage = VideoMessage(),
fileMessage = FileMessage(),chatUserId = "",chatUsers = ArrayList(),
)
messageDao.insertMultipleMessage(listOf(message1,message2))
val messages=messageDao.getAllMessages().getOrAwaitValue()
assertThat(messages).containsAtLeast(message1,message2)
}
}
@Test
fun get_Message_ById() {
runBlockingTest {
val messageId=5L
val message=Message(messageId,
0,0,"fromId","toId","Gowtham","",textMessage = TextMessage(),
imageMessage = ImageMessage(),audioMessage = AudioMessage(),videoMessage = VideoMessage(),
fileMessage = FileMessage(),chatUserId = "",chatUsers = ArrayList(),
)
messageDao.insertMessage(message)
val msg=messageDao.getMessageById(messageId)
assertThat(msg).isNotNull()
}
}
@Test
fun delete_Message_ById() {
runBlockingTest {
val messageId=6L
val message=Message(messageId,
0,0,"fromId","toId","Gowtham","",textMessage = TextMessage(),
imageMessage = ImageMessage(),audioMessage = AudioMessage(),videoMessage = VideoMessage(),
fileMessage = FileMessage(),chatUserId = "",chatUsers = ArrayList(),
)
messageDao.insertMessage(message)
messageDao.deleteMessageByCreatedAt(messageId)
val messages=messageDao.getAllMessages().getOrAwaitValue()
assertThat(messages).doesNotContain(message)
}
}
}
================================================
FILE: app/src/androidTest/java/com/gowtham/letschat/di/TestAppModule.kt
================================================
package com.gowtham.letschat.di
import android.content.Context
import androidx.room.Room
import com.gowtham.letschat.db.ChatUserDatabase
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Named
@Module
@InstallIn(SingletonComponent::class)
class TestAppModule {
@Provides
@Named("test_db")
fun provideInMemoryDb(@ApplicationContext context: Context): ChatUserDatabase{
return Room.inMemoryDatabaseBuilder(
context,
ChatUserDatabase::class.java
).allowMainThreadQueries().build()
}
}
================================================
FILE: app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.gowtham.letschat">
<uses-feature
android:name="android.hardware.camera"
android:required="true" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-feature
android:glEsVersion="0x00020000"
android:required="true" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<application
android:name=".MApplication"
android:allowBackup="false"
android:hardwareAccelerated="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:requestLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:targetApi="q">
<receiver
android:name=".utils.GroupMsgActionReceiver"
android:enabled="true"
android:exported="true"/>
<receiver
android:name=".utils.NActionReceiver"
android:enabled="true" />
<activity android:name=".ui.activities.ActSplash">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".ui.activities.MainActivity"
android:windowSoftInputMode="adjustPan"/>
<activity
android:name="com.theartofdev.edmodo.cropper.CropImageActivity"
android:screenOrientation="portrait"
android:theme="@style/Base.Theme.AppCompat" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_path" />
</provider>
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_stat_name" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="@string/default_notification_channel_id" />
<service
android:name=".FirebasePush"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="${applicationId}.workmanager-init"
tools:node="remove"
android:exported="false" />
</application>
</manifest>
================================================
FILE: app/src/main/java/com/gowtham/letschat/FirebasePush.kt
================================================
package com.gowtham.letschat
import android.app.Notification
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.Person
import androidx.core.app.RemoteInput
import coil.ImageLoader
import coil.request.ImageRequest
import coil.request.SuccessResult
import com.google.firebase.firestore.CollectionReference
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import com.gowtham.letschat.core.ChatUserUtil
import com.gowtham.letschat.core.GroupMsgStatusUpdater
import com.gowtham.letschat.core.GroupQuery
import com.gowtham.letschat.core.MessageStatusUpdater
import com.gowtham.letschat.db.DbRepository
import com.gowtham.letschat.db.daos.ChatUserDao
import com.gowtham.letschat.db.daos.GroupDao
import com.gowtham.letschat.db.daos.GroupMessageDao
import com.gowtham.letschat.db.daos.MessageDao
import com.gowtham.letschat.db.data.*
import com.gowtham.letschat.di.GroupCollection
import com.gowtham.letschat.di.MessageCollection
import com.gowtham.letschat.models.PushMsg
import com.gowtham.letschat.ui.activities.MainActivity
import com.gowtham.letschat.utils.*
import com.gowtham.letschat.utils.Constants.ACTION_GROUP_NEW_MESSAGE
import com.gowtham.letschat.utils.Constants.ACTION_LOGGED_IN_ANOTHER_DEVICE
import com.gowtham.letschat.utils.Constants.ACTION_MARK_AS_READ
import com.gowtham.letschat.utils.Constants.ACTION_NEW_MESSAGE
import com.gowtham.letschat.utils.Constants.ACTION_REPLY
import com.gowtham.letschat.utils.Constants.CHAT_USER_DATA
import com.gowtham.letschat.utils.Constants.GROUP_DATA
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import timber.log.Timber
import javax.inject.Inject
const val TYPE_LOGGED_IN = "new_logged_in"
const val TYPE_NEW_MESSAGE = "new_message"
const val TYPE_NEW_GROUP = "new_group"
const val TYPE_NEW_GROUP_MESSAGE = "new_group_message"
const val GROUP_KEY = "com.mygroupkey"
const val SUMMARY_ID = 0
const val KEY_TEXT_REPLY = "key_text_reply"
@AndroidEntryPoint
class FirebasePush : FirebaseMessagingService(), OnSuccessListener {
@Inject
lateinit var preference: MPreference
@Inject
lateinit var dbRepository: DbRepository
@Inject
lateinit var usersCollection: CollectionReference
@Inject
lateinit var messageStatusUpdater: MessageStatusUpdater
@Inject
lateinit var groupMessageStatusUpdater: GroupMsgStatusUpdater
@GroupCollection
@Inject
lateinit var groupCollection: CollectionReference
private var sentTime: Long? = null
private lateinit var pushMsg: PushMsg
private var userId: String? = null
private lateinit var messagesOfChatUser: List<Message>
override fun onCreate() {
super.onCreate()
userId = preference.getUid()
}
override fun onNewToken(token: String) {
preference.updatePushToken(token)
}
override fun onMessageReceived(remoteMessage: RemoteMessage) {
super.onMessageReceived(remoteMessage)
try {
LogMessage.v("Data Payload: ${remoteMessage.data}")
if (preference.isNotLoggedIn() || !preference.isSameDevice())
return
sentTime = remoteMessage.sentTime
val data = remoteMessage.data
pushMsg = Json.decodeFromString(data["data"].toString())
/* pushMsg.to?.let {
if (it!=userId)
return
}*/
handleNotification()
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun handleNotification() {
when (pushMsg.type) {
TYPE_LOGGED_IN -> {
preference.setLastDevice(false)
val intent = Intent(ACTION_LOGGED_IN_ANOTHER_DEVICE)
sendBroadcast(intent)
}
TYPE_NEW_MESSAGE -> {
handleNewMessage()
}
TYPE_NEW_GROUP -> {
handleNewGroup()
}
TYPE_NEW_GROUP_MESSAGE -> {
handleGroupMsg()
}
else -> {
}
}
}
private fun handleGroupMsg() {
//it would be updated by snapshot listeners when app is alive
if (!MApplication.isAppRunning) {
val message = Json.decodeFromString<GroupMessage>(pushMsg.message_body.toString())
CoroutineScope(Dispatchers.IO).launch {
dbRepository.insertMessage(message)
val group = dbRepository.getGroupById(message.groupId)
val messages = dbRepository.getChatsOfGroupList(group?.id.toString())
if (group != null) {
group.unRead = messages.filter {
it.from != userId &&
Utils.myIndexOfStatus(userId!!, it) < 3
}.size
dbRepository.insertGroup(group)
withContext(Dispatchers.Main) {
showGroupNotification(this@FirebasePush, dbRepository)
//update delivery status
groupMessageStatusUpdater.updateToDelivery(userId!!, messages, group.id)
}
} else {
val groupQuery = GroupQuery(message.groupId, dbRepository, preference)
groupQuery.getGroupData(groupCollection)
}
}
}
}
private fun handleNewGroup() {
//it would be updated by snapshot listeners when app is alive
if (!MApplication.isAppRunning) {
val group = Json.decodeFromString<Group>(pushMsg.message_body.toString())
val groupQuery = GroupQuery(group.id, dbRepository, preference)
groupQuery.getGroupData(groupCollection)
}
}
private fun handleNewMessage() {
val message = Json.decodeFromString<Message>(pushMsg.message_body.toString())
if (message.to != userId || MApplication.isAppRunning) {
Timber.v("Push notification ignored")
return
}
val chatUserId = UserUtils.getChatUserId(userId!!, message) //chatUserId from message
message.chatUserId = chatUserId
CoroutineScope(Dispatchers.IO).launch {
dbRepository.insertMessage(message)
val chatUser = dbRepository.getChatUserById(chatUserId)
messagesOfChatUser = dbRepository.getChatsOfFriend(chatUserId)
.filter { it.to == userId && it.status < 3 }
if (chatUser != null) {
chatUser.unRead = messagesOfChatUser.size //set unread msg count
dbRepository.insertUser(chatUser)
withContext(Dispatchers.Main) {
showNotification(this@FirebasePush, dbRepository)
//update delivery status
messageStatusUpdater.updateToDelivery(messagesOfChatUser, chatUser)
}
} else {
withContext(Dispatchers.Main) {
//update delivery status in listener
val util = ChatUserUtil(dbRepository, usersCollection, this@FirebasePush)
util.queryNewUserProfile(
this@FirebasePush,
chatUserId,
null,
showNotification = true
)
}
}
}
}
private suspend fun getBitmap(url: String): Bitmap {
val loader = ImageLoader(this)
val request = ImageRequest.Builder(this)
.data(url)
.build()
val result = (loader.execute(request) as SuccessResult).drawable
return (result as BitmapDrawable).bitmap
}
companion object {
//notification method for common use
var messageCount = 0
var personCount = 0
fun showGroupNotification(context: Context, dbRepository: DbRepository) {
CoroutineScope(Dispatchers.IO).launch {
var groupWithMsgs = dbRepository.getGroupWithMessagesList()
groupWithMsgs = groupWithMsgs.filter { it.group.unRead != 0 }
checkGroupMessages(context, groupWithMsgs)
}
}
fun showNotification(context: Context, dbRepository: DbRepository) {
CoroutineScope(Dispatchers.IO).launch {
var chatUserWithMessages = dbRepository.getChatUserWithMessagesList()
chatUserWithMessages = chatUserWithMessages.filter { it.user.unRead != 0 }
checkMessages(context, chatUserWithMessages)
}
}
private fun checkGroupMessages(context: Context, groupWithMsgs: List<GroupWithMessages>) {
messageCount = 0
personCount = 0
val myUserId = MPreference(context).getUid().toString()
val manager: NotificationManagerCompat = Utils.returnNManager(context)
val groupNotifications = ArrayList<Notification>()
if (!groupWithMsgs.isNullOrEmpty()) {
for (groupMsg in groupWithMsgs) {
/* if (groupMsg.messages.last().from==myUserId)
continue*/
personCount += 1
val person: Person = Person.Builder().setIcon(null)
.setKey(groupMsg.group.id).setName(Utils.getGroupName(groupMsg.group.id))
.build()
val builder = Utils.createBuilder(context, manager)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setStyle(
NotificationUtils.getGroupStyle(
context,
myUserId,
person,
groupMsg
)
)
.setContentIntent(
NotificationUtils.getGroupMsgIntent(
context,
groupMsg.group
)
)
.setGroup(GROUP_KEY)
builder.addAction(
R.drawable.ic_drafts,
"mark as read",
NotificationUtils.getGroupMarkAsPIntent(context, groupMsg)
)
builder.addAction(NotificationUtils.getGroupReplyAction(context, groupMsg))
val notification = builder.build()
groupNotifications.add(notification)
}
}
val summaryNotification = NotificationUtils.getSummaryNotification(context, manager)
for ((index, notification) in groupNotifications.withIndex()) {
val notIdString = groupWithMsgs[index].group.createdAt.toString()
val notId = notIdString.substring(notIdString.length - 4)
.toInt() //last 4 digits as notificationId
manager.notify(notId, notification)
}
if (groupNotifications.size > 1)
manager.notify(SUMMARY_ID, summaryNotification)
}
private fun checkMessages(
context: Context,
chatUserWithMessages: List<ChatUserWithMessages>
) {
if (chatUserWithMessages.isNullOrEmpty())
return
messageCount = 0
personCount = 0
val notifications = ArrayList<Notification>()
val myUserId = MPreference(context).getUid().toString()
val manager: NotificationManagerCompat = Utils.returnNManager(context)
for (user in chatUserWithMessages) {
val messages = user.messages.filter { it.status < 3 && it.from != myUserId }
if (messages.isNullOrEmpty())
continue
personCount += 1
Timber.v("DocId ${user.user.documentId}")
val person: Person = Person.Builder().setIcon(null)
.setKey(user.user.id).setName(user.user.localName).build()
val builder = Utils.createBuilder(context, manager)
.setStyle(NotificationUtils.getStyle(context, person, user))
.setContentIntent(NotificationUtils.getPIntent(context, user.user))
.setGroup(GROUP_KEY)
if (!user.user.documentId.isNullOrBlank()) {
builder.addAction(
R.drawable.ic_drafts,
"mark as read",
NotificationUtils.getMarkAsPIntent(context, user)
)
builder.addAction(NotificationUtils.getReplyAction(context, user))
}
val notification = builder.build()
notifications.add(notification)
}
val summaryNotification = NotificationUtils.getSummaryNotification(context, manager)
for ((index, notification) in notifications.withIndex()) {
val notIdString = chatUserWithMessages[index].user.user.createdAt.toString()
val notId = notIdString.substring(notIdString.length - 4)
.toInt() //last 4 digits as notificationId
manager.notify(notId, notification)
}
if (notifications.size > 1)
manager.notify(SUMMARY_ID, summaryNotification)
}
}
override fun onResult(success: Boolean, data: Any?) {
if (success) {
messageStatusUpdater.updateToDelivery(messagesOfChatUser, data as ChatUser)
}
}
}
================================================
FILE: app/src/main/java/com/gowtham/letschat/MApplication.kt
================================================
package com.gowtham.letschat
import android.content.Context
import androidx.hilt.work.HiltWorkerFactory
import androidx.lifecycle.LifecycleObserver
import androidx.multidex.MultiDexApplication
import androidx.work.Configuration
import com.google.firebase.FirebaseApp
import com.google.firebase.firestore.CollectionReference
import com.gowtham.letschat.db.daos.ChatUserDao
import com.gowtham.letschat.db.daos.MessageDao
import com.gowtham.letschat.models.UserProfile
import com.gowtham.letschat.utils.LogMessage
import com.gowtham.letschat.utils.MPreference
import com.gowtham.letschat.utils.UserUtils
import dagger.hilt.android.HiltAndroidApp
import timber.log.Timber
import javax.inject.Inject
@HiltAndroidApp
class MApplication : MultiDexApplication(), LifecycleObserver,Configuration.Provider {
@Inject
lateinit var preference: MPreference
@Inject
lateinit var userDao: ChatUserDao
@Inject
lateinit var messageDao: MessageDao
@Inject
lateinit var userCollection: CollectionReference
@Inject lateinit var workerFactory: HiltWorkerFactory
companion object {
lateinit var instance: MApplication
private set
var isAppRunning = false
lateinit var appContext: Context
lateinit var userDaoo: ChatUserDao
lateinit var messageDaoo: MessageDao
}
override fun onCreate() {
super.onCreate()
instance = this
appContext = this
userDaoo = userDao
messageDaoo=messageDao
FirebaseApp.initializeApp(this)
initTimber()
if (preference.isLoggedIn())
checkLastDevice() //looking for does user is logged in another device.if yes,need to shoe dialog for log in again
}
override fun getWorkManagerConfiguration() =
Configuration.Builder()
.setWorkerFactory(workerFactory)
.build()
private fun initTimber() {
if (BuildConfig.DEBUG) {
Timber.plant(object : Timber.DebugTree() {
override fun createStackElementTag(element: StackTraceElement): String {
return "LetsChat/${element.fileName}:${element.lineNumber})#${element.methodName}"
}
})
}
}
private fun checkLastDevice() {
userCollection.document(preference.getUid()!!).get().addOnSuccessListener { data ->
Timber.v("Device Checked")
val appUser = data.toObject(UserProfile::class.java)
checkDeviceDetails(appUser)
}.addOnFailureListener { e ->
LogMessage.v(e.message.toString())
}
}
private fun checkDeviceDetails(appUser: UserProfile?) {
val device = appUser?.deviceDetails
val localDevice = UserUtils.getDeviceId(this)
if (device != null) {
val sameDevice = device.device_id.equals(localDevice)
preference.setLastDevice(sameDevice)
Timber.v("Device Checked ${device.device_id.equals(localDevice)}")
if (sameDevice)
UserUtils.updatePushToken(this,userCollection, true)
}
}
}
================================================
FILE: app/src/main/java/com/gowtham/letschat/core/ChatHandler.kt
================================================
package com.gowtham.letschat.core
import android.content.Context
import androidx.lifecycle.MutableLiveData
import com.google.firebase.firestore.*
import com.gowtham.letschat.FirebasePush
import com.gowtham.letschat.db.DbRepository
import com.gowtham.letschat.db.data.ChatUser
import com.gowtham.letschat.db.data.Message
import com.gowtham.letschat.fragments.single_chat.toDataClass
import com.gowtham.letschat.utils.LogMessage
import com.gowtham.letschat.utils.MPreference
import com.gowtham.letschat.utils.UserUtils
import com.gowtham.letschat.utils.getUnreadCount
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.*
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class ChatHandler @Inject constructor(
@ApplicationContext private val context: Context,
private val dbRepository: DbRepository,
private val usersCollection: CollectionReference,
private val preference: MPreference,
private val messageStatusUpdater: MessageStatusUpdater
) {
private val messagesList: MutableList<Message> by lazy { mutableListOf() }
private var fromUser = preference.getUid()
val message = MutableLiveData<String>()
private lateinit var chatUsers: List<ChatUser>
private val listOfDocs = ArrayList<String>()
private lateinit var messageCollectionGroup: Query
private val chatUserUtil = ChatUserUtil(dbRepository, usersCollection, null)
private var isFirstQuery = false
companion object {
private var listenerDoc1: ListenerRegistration? = null
private var instanceCreated = false
fun removeListeners() {
instanceCreated = false
listenerDoc1?.remove()
}
}
fun initHandler() {
if (instanceCreated)
return
instanceCreated = true
fromUser = preference.getUid()
Timber.v("ChatHandler init")
messageCollectionGroup = UserUtils.getMessageSubCollectionRef()
preference.clearCurrentUser()
listenerDoc1 = messageCollectionGroup.whereArrayContains("chatUsers", fromUser!!)
.addSnapshotListener { snapShots, error ->
if (error != null || snapShots == null || snapShots.metadata.isFromCache) {
LogMessage.v("Error ${snapShots?.metadata?.isFromCache}")
if(snapShots?.metadata?.isFromCache == true)
onFetchDocuments()
return@addSnapshotListener
}else
onSnapShotChanged(snapShots)
}
}
private fun onFetchDocuments() {
messageCollectionGroup.whereArrayContains("chatUsers", fromUser!!).get().addOnSuccessListener {
isFirstQuery=true
onSnapShotChanged(it)
}
}
private fun onSnapShotChanged(snapShots: QuerySnapshot) {
messagesList.clear()
listOfDocs.clear()
val listOfIds = ArrayList<String>()
if (isFirstQuery) {
snapShots.forEach { doc ->
val parentDoc = doc.reference.parent.parent?.id!!
val message = doc.data.toDataClass<Message>()
message.chatUserId =
if (message.from != fromUser) message.from else message.to
messagesList.add(message)
if (!listOfDocs.contains(parentDoc)) {
listOfDocs.add(doc.reference.parent.parent?.id.toString())
listOfIds.add(message.chatUserId!!)
}
}
isFirstQuery=false
} else
for (shot in snapShots.documentChanges) {
if (shot.type == DocumentChange.Type.ADDED ||
shot.type == DocumentChange.Type.MODIFIED
) {
val document = shot.document
val parentDoc = document.reference.parent.parent?.id!!
val message = document.data.toDataClass<Message>()
message.chatUserId =
if (message.from != fromUser) message.from else message.to
messagesList.add(message)
if (!listOfDocs.contains(parentDoc)) {
listOfDocs.add(document.reference.parent.parent?.id.toString())
listOfIds.add(message.chatUserId!!)
}
}
}
if (!messagesList.isNullOrEmpty())
insertMessageOnDb(listOfIds)
}
private fun insertMessageOnDb(listOfIds: ArrayList<String>) {
CoroutineScope(Dispatchers.IO).launch {
val contacts = ArrayList<ChatUser>()
val newContactIds =
ArrayList<String>() //message from new user not saved in localdb yet
chatUsers = dbRepository.getChatUserList()
dbRepository.insertMultipleMessage(messagesList)
for ((index, doc) in listOfDocs.withIndex()) {
val chatUser = chatUsers.firstOrNull { it.id == listOfIds[index] }
if (chatUser == null) {
newContactIds.add(listOfIds[index])
//message from unsaved user
} else {
chatUser.unRead = if (preference.getOnlineUser() == chatUser.id) 0 else
dbRepository.getChatsOfFriend(chatUser.id).getUnreadCount(chatUser.id)
chatUser.documentId = doc
Timber.v("UserId ${chatUser.id} count ${chatUser.unRead}")
contacts.add(chatUser)
}
}
dbRepository.insertMultipleUsers(contacts)
val currentChatUser = if (preference.getOnlineUser().isNotEmpty())
contacts.firstOrNull { it.id == preference.getOnlineUser() }
else null
val allUnReadMsgs = dbRepository.getAllNonSeenMessage()
withContext(Dispatchers.Main) {
updateMsgStatus(newContactIds, currentChatUser, allUnReadMsgs)
}
}
}
private fun updateMsgStatus(
newContactIds: ArrayList<String>,
currentChatUser: ChatUser?,
allUnReadMsgs: List<Message>
) {
showNotification(newContactIds)
if (currentChatUser != null) {
val currentUserMsgs = allUnReadMsgs.filter {
it.chatUserId == currentChatUser.id
}
val otherUserMsgs = allUnReadMsgs.filter {
it.chatUserId != currentChatUser.id
}
messageStatusUpdater.updateToDelivery(otherUserMsgs, *chatUsers.toTypedArray())
messageStatusUpdater.updateToSeen(
currentChatUser.id, currentChatUser.documentId!!, currentUserMsgs
)
} else {
messageStatusUpdater.updateToDelivery(allUnReadMsgs, *chatUsers.toTypedArray())
}
}
private fun showNotification(
newContactIds: ArrayList<String>
) {
if (newContactIds.isEmpty()) {
val lastMsgId = messagesList.maxOf { it.createdAt }
val msg = messagesList.find { it.createdAt == lastMsgId }
if (msg != null && msg.from != fromUser)
FirebasePush.showNotification(context, dbRepository)
} else {
//unsaved new user
for (i in 0 until newContactIds.size) {
val userId = newContactIds[i]
if (userId == preference.getOnlineUser())
continue
val unreadCount = messagesList.getUnreadCount(userId)
chatUserUtil.queryNewUserProfile(
context,
userId,
listOfDocs.firstOrNull { it.contains(userId) }, unreadCount,
showNotification = i == newContactIds.lastIndex
)
}
}
}
}
================================================
FILE: app/src/main/java/com/gowtham/letschat/core/ChatUserProfileListener.kt
================================================
package com.gowtham.letschat.core
import android.content.Context
import com.google.firebase.firestore.CollectionReference
import com.google.firebase.firestore.ListenerRegistration
import com.gowtham.letschat.db.DbRepository
import com.gowtham.letschat.db.data.ChatUser
import com.gowtham.letschat.db.data.Group
import com.gowtham.letschat.models.UserProfile
import com.gowtham.letschat.utils.MPreference
import com.gowtham.letschat.utils.UserUtils
import com.gowtham.letschat.utils.Utils
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class ChatUserProfileListener @Inject
constructor(@ApplicationContext val context: Context,
private val userCollectionRef: CollectionReference,
private val preference: MPreference,
private val dbRepository: DbRepository){
private var instanceCreated=false
companion object{
private val listOfListeners=ArrayList<ListenerRegistration>()
fun removeListener(){
listOfListeners.forEach {
it.remove()
}
}
}
private fun getChatUsers() {
CoroutineScope(Dispatchers.IO).launch {
val users=dbRepository.getChatUserList()
withContext(Dispatchers.Main){
addSnapShotListener(users)
}
}
}
private fun addSnapShotListener(users: List<ChatUser>) {
val myUserId=preference.getUid().toString()
for (user in users){
if (user.id==myUserId)
continue
val listener= userCollectionRef.document(user.id).addSnapshotListener { profile, error ->
if (error!=null) {
Timber.v(error)
return@addSnapshotListener
}
val userProfile = profile?.toObject(UserProfile::class.java)
userProfile?.let { pro->
val chatUser=users.firstOrNull { it.id== pro.uId }
if (chatUser!=null){
chatUser.user=pro
checkForContactSaved(chatUser,pro.mobile?.number!!)
updateInLocal(chatUser)
}
}
}
listOfListeners.add(listener)
}
}
private fun updateInLocal(chatUser: ChatUser) {
val chatUserId=chatUser.id
dbRepository.insertUser(chatUser)
//updating in groups
CoroutineScope(Dispatchers.IO).launch {
val groups=dbRepository.getGroupList()
val containingList= mutableListOf<Group>()
for (group in groups){
val members=group.members
val isContains= members?.any { it.id == chatUserId } ?: false
if (isContains){
val index=members?.indexOfFirst { it.id==chatUserId }
members!![index!!]=chatUser
containingList.add(group)
}
}
dbRepository.insertMultipleGroup(containingList)
}
}
private fun checkForContactSaved(chatUser: ChatUser, mobileNo: String) {
if (Utils.isContactPermissionOk(context)) {
val contacts = UserUtils.fetchContacts(context)
val savedContact=contacts.firstOrNull { it.mobile.contains(mobileNo) }
if (savedContact!=null){
chatUser.localName=savedContact.name
chatUser.locallySaved=true
}else{
//contact deleted
val profile=chatUser.user
val mobile = profile.mobile?.country + " " + profile.mobile?.number
chatUser.localName=mobile
chatUser.locallySaved=false
}
}
}
fun initListener() {
if (!instanceCreated) {
getChatUsers()
instanceCreated=true
}
}
}
================================================
FILE: app/src/main/java/com/gowtham/letschat/core/ChatUserUtil.kt
================================================
package com.gowtham.letschat.core
import android.content.Context
import com.google.firebase.firestore.CollectionReference
import com.gowtham.letschat.FirebasePush
import com.gowtham.letschat.db.DbRepository
import com.gowtham.letschat.db.data.ChatUser
import com.gowtham.letschat.db.daos.ChatUserDao
import com.gowtham.letschat.models.UserProfile
import com.gowtham.letschat.utils.OnSuccessListener
import com.gowtham.letschat.utils.UserUtils
import com.gowtham.letschat.utils.Utils
class ChatUserUtil(private val dbRepository: DbRepository,
private val usersCollection: CollectionReference,
private val listener: OnSuccessListener?) {
fun queryNewUserProfile(context: Context,chatUserId: String,docId: String?, unReadCount: Int=1,
showNotification: Boolean=false) {
try {
usersCollection.document(chatUserId)
.get().addOnSuccessListener { profile ->
if (profile.exists()) {
val userProfile = profile.toObject(UserProfile::class.java)
val mobile = userProfile?.mobile?.country + " " + userProfile?.mobile?.number
val chatUser = ChatUser(userProfile?.uId!!, mobile, userProfile)
chatUser.unRead=unReadCount
if(docId!=null)
chatUser.documentId=docId
if (Utils.isContactPermissionOk(context)) {
val contacts = UserUtils.fetchContacts(context)
val savedContact=contacts.firstOrNull { it.mobile.contains(userProfile.mobile!!.number) }
savedContact?.let {
chatUser.localName=it.name
chatUser.locallySaved=true
}
}
listener?.onResult(true,chatUser)
dbRepository.insertUser(chatUser)
if(showNotification)
FirebasePush.showNotification(context,dbRepository)
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
================================================
FILE: app/src/main/java/com/gowtham/letschat/core/ContactsQuery.kt
================================================
package com.gowtham.letschat.core
import com.google.firebase.firestore.FirebaseFirestore
import com.gowtham.letschat.models.UserProfile
import com.gowtham.letschat.utils.UserUtils
import timber.log.Timber
interface QueryCompleteListener{
fun onQueryCompleted(queriedList: ArrayList<UserProfile>)
}
class ContactsQuery(val list: ArrayList<String>,val position: Int,val listener: QueryCompleteListener){
private val usersCollection = FirebaseFirestore.getInstance().collection("Users")
fun makeQuery() {
try {
usersCollection.whereIn("mobile.number", list).get()
.addOnSuccessListener { documents ->
for (document in documents) {
val contact = document.toObject(UserProfile::class.java)
UserUtils.queriedList.add(contact)
}
UserUtils.resultCount += 1
if(UserUtils.resultCount == UserUtils.totalRecursionCount){
listener.onQueryCompleted(UserUtils.queriedList)
}
}
.addOnFailureListener { exception ->
Timber.wtf("Error getting documents: ${exception.message}")
UserUtils.resultCount += 1
if(UserUtils.resultCount == UserUtils.totalRecursionCount)
listener.onQueryCompleted(UserUtils.queriedList)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
================================================
FILE: app/src/main/java/com/gowtham/letschat/core/GroupChatHandler.kt
================================================
package com.gowtham.letschat.core
import android.content.Context
import com.google.firebase.firestore.*
import com.gowtham.letschat.FirebasePush
import com.gowtham.letschat.db.DbRepository
import com.gowtham.letschat.db.data.Group
import com.gowtham.letschat.db.data.GroupMessage
import com.gowtham.letschat.di.GroupCollection
import com.gowtham.letschat.fragments.single_chat.toDataClass
import com.gowtham.letschat.utils.LogMessage
import com.gowtham.letschat.utils.MPreference
import com.gowtham.letschat.utils.UserUtils
import com.gowtham.letschat.utils.Utils
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class GroupChatHandler @Inject constructor(
@ApplicationContext private val context: Context,
private val preference: MPreference,
private val userCollection: CollectionReference,
@GroupCollection
private val groupCollection: CollectionReference,
private val dbRepository: DbRepository,
private val groupMsgStatusUpdater: GroupMsgStatusUpdater
) {
private var userId = preference.getUid()
private lateinit var messageCollectionGroup: Query
private val messagesList = mutableListOf<GroupMessage>()
private val listOfGroup = ArrayList<String>()
private var isFirstQuery = false
companion object {
private var groupListener: ListenerRegistration? = null
private var myProfileListener: ListenerRegistration? = null
private var instanceCreated = false
fun removeListener() {
instanceCreated = false
groupListener?.remove()
myProfileListener?.remove()
}
}
fun initHandler() {
if (instanceCreated)
return
else
instanceCreated = true
userId = preference.getUid()
Timber.v("GroupChatHandler init")
preference.clearCurrentGroup()
messageCollectionGroup = UserUtils.getGroupMsgSubCollectionRef()
addGroupsSnapShotListener()
addGroupMsgListener()
}
private fun addGroupMsgListener() {
try {
groupListener = messageCollectionGroup.whereArrayContains("to", userId!!)
.addSnapshotListener { snapshots, error ->
if (error != null || snapshots == null || snapshots.metadata.isFromCache) {
LogMessage.v("Error ${error?.localizedMessage}")
return@addSnapshotListener
}
messagesList.clear()
listOfGroup.clear()
onSnapShotChanged(snapshots)
if (messagesList.isNotEmpty())
updateGroupUnReadCount()
}
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun onSnapShotChanged(snapshots: QuerySnapshot) {
if(isFirstQuery){
snapshots.forEach { doc->
val message = doc.data.toDataClass<GroupMessage>()
if (!listOfGroup.contains(message.groupId))
listOfGroup.add(message.groupId)
messagesList.add(message)
}
isFirstQuery=false
}
else
for (shot in snapshots.documentChanges) {
if (shot.type == DocumentChange.Type.ADDED ||
shot.type == DocumentChange.Type.MODIFIED
) {
val message = shot.document.data.toDataClass<GroupMessage>()
if (!listOfGroup.contains(message.groupId))
listOfGroup.add(message.groupId)
messagesList.add(message)
}
}
}
private fun updateGroupUnReadCount() {
CoroutineScope(Dispatchers.IO).launch {
dbRepository.insertMultipleGroupMessage(messagesList)
val groupsWithMsgs = dbRepository.getGroupWithMessagesList()
messagesList.clear()
for (groupWithMsg in groupsWithMsgs) {
val unreadCount = groupWithMsg.messages.filter {
val myStatus = Utils.myMsgStatus(userId.toString(), it)
it.from != userId &&
it.groupId == groupWithMsg.group.id && myStatus < 3
}.size
groupWithMsg.group.unRead =
if (preference.getOnlineGroup() == groupWithMsg.group.id) 0
else unreadCount
messagesList.addAll(groupWithMsg.messages)
}
val groups = groupsWithMsgs.map {
it.group
}
dbRepository.insertMultipleGroup(groups)
changeMsgStatus(groups)
}
}
private fun changeMsgStatus(groups: List<Group>) {
if (groups.isNotEmpty())
FirebasePush.showGroupNotification(context, dbRepository)
val currentOnlineGroupId=preference.getOnlineGroup()
if(currentOnlineGroupId.isNotEmpty()){
val currentGroupMsgs = messagesList.filter {
it.groupId == currentOnlineGroupId
}
val otherGroupMsgs = messagesList.filter {
it.groupId != currentOnlineGroupId
}
groupMsgStatusUpdater.updateToSeen(userId!!, currentGroupMsgs,currentOnlineGroupId)
groupMsgStatusUpdater.updateToDelivery(userId!!, otherGroupMsgs, *listOfGroup.toTypedArray())
}else
groupMsgStatusUpdater.updateToDelivery(userId!!, messagesList, *listOfGroup.toTypedArray())
}
private fun addGroupsSnapShotListener() {
myProfileListener =
userCollection.document(userId.toString()).addSnapshotListener { snapshot, error ->
if (error == null) {
val groups = snapshot?.get("groups")
val listOfGroup =
if (groups == null) ArrayList() else groups as ArrayList<String>
CoroutineScope(Dispatchers.IO).launch {
val alreadySavedGroup = dbRepository.getGroupList().map { it.id }
val removedGroups = alreadySavedGroup.toSet().minus(listOfGroup.toSet())
val newGroups = listOfGroup.toSet().minus(alreadySavedGroup.toSet())
queryNewGroups(newGroups)
}
}
}
}
private fun queryNewGroups(newGroups: Set<String>) {
Timber.v("New groups ${newGroups.size}")
for (groupId in newGroups) {
val groupQuery = GroupQuery(groupId, dbRepository, preference)
groupQuery.getGroupData(groupCollection)
}
}
}
================================================
FILE: app/src/main/java/com/gowtham/letschat/core/GroupMsgSender.kt
================================================
package com.gowtham.letschat.core
import com.google.firebase.firestore.CollectionReference
import com.google.firebase.firestore.SetOptions
import com.gowtham.letschat.db.daos.GroupDao
import com.gowtham.letschat.db.data.Group
import com.gowtham.letschat.db.data.GroupMessage
import com.gowtham.letschat.db.data.Message
interface OnGrpMessageResponse{
fun onSuccess(message: GroupMessage)
fun onFailed(message: GroupMessage)
}
class GroupMsgSender(private val groupCollection: CollectionReference) {
fun sendMessage(message: GroupMessage,group: Group,listener: OnGrpMessageResponse){
message.status[0]=1
groupCollection.document(group.id).collection("group_messages")
.document(message.createdAt.toString()).set(message, SetOptions.merge())
.addOnSuccessListener {
listener.onSuccess(message)
}.addOnFailureListener {
message.status[0]=4
listener.onFailed(message)
}
}
}
================================================
FILE: app/src/main/java/com/gowtham/letschat/core/GroupMsgStatusUpdater.kt
================================================
package com.gowtham.letschat.core
import com.google.firebase.firestore.CollectionReference
import com.google.firebase.firestore.FirebaseFirestore
import com.gowtham.letschat.db.data.Group
import com.gowtham.letschat.db.data.GroupMessage
import com.gowtham.letschat.di.GroupCollection
import com.gowtham.letschat.fragments.single_chat.asMap
import com.gowtham.letschat.fragments.single_chat.serializeToMap
import com.gowtham.letschat.utils.LogMessage
import com.gowtham.letschat.utils.Utils.myIndexOfStatus
import com.gowtham.letschat.utils.Utils.myMsgStatus
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class GroupMsgStatusUpdater @Inject constructor(
@GroupCollection
private val groupCollection: CollectionReference,
private val firestore: FirebaseFirestore) {
fun updateToDelivery(myUserId: String, messageList: List<GroupMessage>, vararg groupId: String){
try {
val batch= firestore.batch()
for (id in groupId){
val msgSubCollection=groupCollection.document(id).collection("group_messages")
val filterList= messageList
.filter { it.from!=myUserId && myMsgStatus(myUserId,it)==0 && it.groupId==id }
.map {
val myIndex=myIndexOfStatus(myUserId,it)
it.status[myIndex]=2
it.deliveryTime[myIndex]=System.currentTimeMillis()
it
}
for (msg in filterList){
LogMessage.v("message date ${msg.deliveryTime}")
batch.update(msgSubCollection
.document(msg.createdAt.toString()),msg.asMap())
}
}
batch.commit().addOnSuccessListener {
LogMessage.v("Batch update success from group")
}.addOnFailureListener {
LogMessage.v("Batch update failure ${it.message} from group")
}
} catch (e: Exception) {
e.printStackTrace()
}
}
fun updateToSeen(myUserId: String, messageList: List<GroupMessage>, groupId: String){
val batch= firestore.batch()
val currentTime = System.currentTimeMillis()
val msgSubCollection=groupCollection.document(groupId).collection("group_messages")
val filterList= messageList
.filter { it.from!=myUserId && myMsgStatus(myUserId,it)<3 }
.map {
val myIndex=myIndexOfStatus(myUserId,it)
it.status[myIndex]=3
it.deliveryTime[myIndex]= if (it.deliveryTime[myIndex]==0L)
currentTime else it.deliveryTime[myIndex]
it.seenTime[myIndex]=currentTime
it
}
if (filterList.isNotEmpty()){
for (msg in filterList){
batch.update(msgSubCollection
.document(msg.createdAt.toString()),msg.serializeToMap())
}
}
batch.commit().addOnSuccessListener {
LogMessage.v("Seen Batch update success from group")
}.addOnFailureListener {
LogMessage.v("Seen Batch update failure ${it.message} from group")
}
}
}
================================================
FILE: app/src/main/java/com/gowtham/letschat/core/GroupQuery.kt
================================================
package com.gowtham.letschat.core
import com.google.firebase.firestore.CollectionReference
import com.gowtham.letschat.db.DbRepository
import com.gowtham.letschat.db.daos.ChatUserDao
import com.gowtham.letschat.db.daos.GroupDao
import com.gowtham.letschat.db.data.ChatUser
import com.gowtham.letschat.db.data.Group
import com.gowtham.letschat.utils.MPreference
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import timber.log.Timber
class GroupQuery(private val groupId: String,private val dbRepository: DbRepository,private val preference: MPreference) {
private val myUserId=preference.getUid()!!
fun getGroupData(groupCollection: CollectionReference){
val userId=preference.getUid()
groupCollection.document(groupId).get().addOnSuccessListener { snapshot->
snapshot?.let { data ->
if (!data.exists())
return@addOnSuccessListener
val group=data.toObject(Group::class.java)
val profiles=group?.profiles
val index=profiles!!.indexOfFirst { it.uId==userId }
profiles.removeAt(index)
profiles.add(0,preference.getUserProfile()!!) //moving localuser to 0 th index
group.profiles=profiles
CoroutineScope(Dispatchers.IO).launch {
checkAlreadySavedMember(group, dbRepository.getChatUserList())
}
}
}.addOnFailureListener {
Timber.v("GroupDataGrtting failed ${it.message}")
}
}
private fun checkAlreadySavedMember(group: Group, list: List<ChatUser>){
val chatUsers= ArrayList<ChatUser>()
for (profile in group.profiles!!){
if (profile.uId==myUserId) {
chatUsers.add(ChatUser(myUserId, "You", profile))
continue
}
val chatUser=list.firstOrNull { it.id==profile.uId }
if (chatUser==null){
val localName="${profile.mobile?.country} ${profile.mobile?.number}"
val user=ChatUser(profile.uId.toString(),localName,profile)
chatUsers.add(user)
}else
chatUsers.add(chatUser)
}
group.members=chatUsers
group.profiles= ArrayList()
dbRepository.insertMultipleUser(chatUsers)
dbRepository.insertGroup(group)
}
}
================================================
FILE: app/src/main/java/com/gowtham/letschat/core/MessageSender.kt
================================================
package com.gowtham.letschat.core
import com.google.firebase.firestore.CollectionReference
import com.google.firebase.firestore.FieldValue
import com.google.firebase.firestore.SetOptions
import com.gowtham.letschat.db.DbRepository
import com.gowtham.letschat.db.data.ChatUser
import com.gowtham.letschat.db.daos.ChatUserDao
import com.gowtham.letschat.db.data.Message
import com.gowtham.letschat.utils.LogMessage
import com.gowtham.letschat.utils.UserUtils
import timber.log.Timber
interface OnMessageResponse{
fun onSuccess(message: Message)
fun onFailed(message: Message)
}
class MessageSender(private val msgCollection: CollectionReference,
private val dbRepo: DbRepository, private val chatUser: ChatUser,
private val listener: OnMessageResponse) {
fun checkAndSend(fromUser: String, toUser: String, message: Message) {
val docId = chatUser.documentId
if (!docId.isNullOrEmpty()){
Timber.v("Case 0 ${chatUser.documentId}")
send(docId, message)
} else {
//so we don't create multiple nodes for same chat
msgCollection.document("${fromUser}_${toUser}").get()
.addOnSuccessListener { documentSnapshot ->
if (documentSnapshot.exists()) {
//this node exists send your message
Timber.v("Case 1")
send("${fromUser}_${toUser}", message)
} else {
//senderId_receiverId node doesn't exist check receiverId_senderId
msgCollection.document("${toUser}_${fromUser}").get()
.addOnSuccessListener { documentSnapshot2 ->
if (documentSnapshot2.exists()) {
Timber.v("Case 2")
send("${toUser}_${fromUser}", message)
} else {
//no previous chat history(senderId_receiverId & receiverId_senderId both don't exist)
//so we create document senderId_receiverId then messages array then add messageMap to messages
//this node exists send your message
//add ids of chat members
Timber.v("Case 3")
msgCollection.document("${fromUser}_${toUser}")
.set(mapOf("chat_members" to FieldValue.arrayUnion(fromUser, toUser)),
SetOptions.merge()
).addOnSuccessListener {
LogMessage.v("chat member update successfully")
send("${fromUser}_${toUser}", message)
}.addOnFailureListener {
LogMessage.v("chat member update failed ${it.message}")
}
}
}
}
}
}
}
private fun send(doc: String, message: Message){
try {
chatUser.documentId=doc
dbRepo.insertUser(chatUser)
val chatUserId=message.chatUserId
message.chatUserId=null //chatUserId field is being used only for relation query,changing to null will ignore this field
message.status=1
message.chatUsers= arrayListOf(message.from,message.to)
msgCollection.document(doc).collection("messages").document(message.createdAt.toString()).set(
message,
SetOptions.merge()
).addOnSuccessListener {
LogMessage.v("Message sender Sucesss ${message.createdAt}")
message.chatUserId=chatUserId
listener.onSuccess(message)
}.addOnFailureListener {
message.chatUserId=chatUserId
message.status=4
LogMessage.v("Message sender Failed ${it.message}")
listener.onFailed(message)
}
/* msgCollection.document(doc)
.update("messages",
FieldValue.arrayUnion(message.serializeToMap())).addOnSuccessListener {
LogMessage.v("Message sender Sucesss ${message.textMessage?.text}")
listener.onSuccess(message)
}.addOnFailureListener {
message.status=4
LogMessage.v("Message sender Failed ${it.message}")
listener.onFailed(message)
}*/
} catch (e: Exception) {
e.printStackTrace()
}
}
}
================================================
FILE: app/src/main/java/com/gowtham/letschat/core/MessageStatusUpdater.kt
================================================
package com.gowtham.letschat.core
import com.google.firebase.firestore.CollectionReference
import com.google.firebase.firestore.FirebaseFirestore
import com.gowtham.letschat.db.data.ChatUser
import com.gowtham.letschat.db.data.Message
import com.gowtham.letschat.di.MessageCollection
import com.gowtham.letschat.fragments.single_chat.asMap
import com.gowtham.letschat.fragments.single_chat.serializeToMap
import com.gowtham.letschat.utils.LogMessage
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class MessageStatusUpdater @Inject constructor(
@MessageCollection
private val msgCollection: CollectionReference,
private val firebaseFirestore: FirebaseFirestore
) {
fun updateToDelivery(messageList: List<Message>, vararg chatUsers: ChatUser) {
val batch = firebaseFirestore.batch()
for (chatUser in chatUsers) {
if (chatUser.documentId.isNullOrBlank())
continue
val msgSubCollection =
msgCollection.document(chatUser.documentId!!).collection("messages")
val filterList = messageList
.filter { msg -> msg.status == 1 && msg.from == chatUser.id }
.map {
it.chatUserId = null
it.status = 2
it.deliveryTime = System.currentTimeMillis()
it
}
if (filterList.isNotEmpty()) {
for (msg in filterList) {
batch.update(
msgSubCollection
.document(msg.createdAt.toString()), msg.serializeToMap()
)
}
}
}
batch.commit().addOnSuccessListener {
LogMessage.v("Batch update success from home")
}.addOnFailureListener {
LogMessage.v("Batch update failure ${it.message} from home")
}
}
fun updateToSeen(toUser: String, docId: String?, messageList: List<Message>) {
if(docId==null)
return
val msgSubCollection = msgCollection.document(docId).collection("messages")
val batch = firebaseFirestore.batch()
val currentTime = System.currentTimeMillis()
val filterList = messageList
.filter { msg -> msg.from == toUser && msg.status != 3 }
.map {
it.status = 3
it.chatUserId = null
it.deliveryTime = it.deliveryTime
it.seenTime = currentTime
it
}
if (filterList.isNotEmpty()) {
Timber.v("Size of list ${filterList.last().createdAt}")
for (message in filterList) {
batch.update(
msgSubCollection
.document(message.createdAt.toString()), message.serializeToMap()
)
}
batch.commit().addOnSuccessListener {
LogMessage.v("All Message Seen Batch update success")
}.addOnFailureListener {
LogMessage.v("All Message Seen Batch update failure ${it.message}")
}
} else
LogMessage.v("All message already seen")
}
}
================================================
FILE: app/src/main/java/com/gowtham/letschat/db/ChatUserDatabase.kt
================================================
package com.gowtham.letschat.db
import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import com.gowtham.letschat.db.daos.ChatUserDao
import com.gowtham.letschat.db.daos.GroupDao
import com.gowtham.letschat.db.daos.GroupMessageDao
import com.gowtham.letschat.db.daos.MessageDao
import com.gowtham.letschat.db.data.ChatUser
import com.gowtham.letschat.db.data.Group
import com.gowtham.letschat.db.data.GroupMessage
import com.gowtham.letschat.db.data.Message
@Database(entities = [ChatUser::class, Message::class,Group::class,GroupMessage::class],
version = 1, exportSchema = false)
@TypeConverters(TypeConverter::class)
abstract class ChatUserDatabase : RoomDatabase() {
abstract fun getChatUserDao(): ChatUserDao
abstract fun getMessageDao(): MessageDao
abstract fun getGroupDao(): GroupDao
abstract fun getGroupMessageDao(): GroupMessageDao
}
================================================
FILE: app/src/main/java/com/gowtham/letschat/db/DbRepository.kt
================================================
package com.gowtham.letschat.db
import android.content.Context
import com.gowtham.letschat.db.daos.ChatUserDao
import com.gowtham.letschat.db.daos.GroupDao
import com.gowtham.letschat.db.daos.GroupMessageDao
import com.gowtham.letschat.db.daos.MessageDao
import com.gowtham.letschat.db.data.ChatUser
import com.gowtham.letschat.db.data.Group
import com.gowtham.letschat.db.data.GroupMessage
import com.gowtham.letschat.db.data.Message
import com.gowtham.letschat.utils.LogMessage
import com.gowtham.letschat.utils.MPreference
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class DbRepository @Inject constructor(
private val userDao: ChatUserDao,
private val groupDao: GroupDao,
private val groupMsgDao: GroupMessageDao,
private val messageDao: MessageDao) : DefaultDbRepo {
override fun insertUser(user: ChatUser) {
CoroutineScope(Dispatchers.IO).launch {
userDao.insertUser(user)
}
}
override fun insertMultipleUser(users: List<ChatUser>) {
CoroutineScope(Dispatchers.IO).launch {
userDao.insertMultipleUser(users)
}
}
override fun getChatUserWithMessages() = userDao.getChatUserWithMessages()
override fun getChatUserList() = userDao.getChatUserList()
override fun getChatUserWithMessagesList() = userDao.getChatUserWithMessagesList()
override fun getChatUserById(id: String) = userDao.getChatUserById(id)
override fun getAllChatUser() = userDao.getAllChatUser()
override fun nukeTable() {
}
override fun deleteUserById(userId: String) {
}
fun insertMultipleUser(finalList: ArrayList<ChatUser>) {
CoroutineScope(Dispatchers.IO).launch {
userDao.insertMultipleUser(finalList)
}
}
suspend fun insertMultipleUsers(users: ArrayList<ChatUser>){
userDao.insertMultipleUser(users)
}
fun insertGroup(group: Group) {
CoroutineScope(Dispatchers.IO).launch {
groupDao.insertGroup(group)
}
}
suspend fun insertMultipleMessage(messagesList: MutableList<Message>) =
messageDao.insertMultipleMessage(messagesList)
suspend fun insertMultipleGroupMessage(messagesList: List<GroupMessage>) =
groupMsgDao.insertMultipleMessage(messagesList)
fun getAllNonSeenMessage() =
messageDao.getAllNotSeenMessages()
fun insertMessage(message: Message) {
CoroutineScope(Dispatchers.IO).launch {
messageDao.insertMessage(message)
}
}
fun insertMessage(message: GroupMessage) {
CoroutineScope(Dispatchers.IO).launch {
groupMsgDao.insertMessage(message)
}
}
suspend fun insertMultipleGroup(groups: List<Group>) =
groupDao.insertMultipleGroup(groups)
fun getGroupWithMessages() = groupDao.getGroupWithMessages()
fun getMessagesByChatUserId(chatUserId: String) = messageDao.getMessagesByChatUserId(chatUserId)
fun getChatsOfFriend(toUser: String) = messageDao.getChatsOfFriend(toUser)
fun getGroupById(groupId: String) = groupDao.getGroupById(groupId)
fun getChatsOfGroupList(groupId: String) = groupMsgDao.getChatsOfGroupList(groupId)
fun getChatsOfGroup(groupId: String) = groupMsgDao.getChatsOfGroup(groupId)
fun getGroupWithMessagesList() = groupDao.getGroupWithMessagesList()
fun getMessageList() = messageDao.getMessageList()
fun getGroupList() = groupDao.getGroupList()
}
================================================
FILE: app/src/main/java/com/gowtham/letschat/db/DefaultDbRepo.kt
================================================
package com.gowtham.letschat.db
import androidx.lifecycle.LiveData
import com.gowtham.letschat.db.data.ChatUser
import com.gowtham.letschat.db.data.ChatUserWithMessages
import kotlinx.coroutines.flow.Flow
interface DefaultDbRepo {
fun insertUser(user: ChatUser)
fun insertMultipleUser(users: List<ChatUser>)
fun getAllChatUser(): LiveData<List<ChatUser>>
fun getChatUserList(): List<ChatUser>
fun getChatUserById(id: String): ChatUser?
fun deleteUserById(userId: String)
fun getChatUserWithMessages(): Flow<List<ChatUserWithMessages>>
fun getChatUserWithMessagesList(): List<ChatUserWithMessages>
fun nukeTable()
}
================================================
FILE: app/src/main/java/com/gowtham/letschat/db/TypeConverter.kt
================================================
package com.gowtham.letschat.db
import androidx.room.TypeConverter
import com.gowtham.letschat.db.data.*
import com.gowtham.letschat.models.UserProfile
import kotlinx.serialization.json.Json
import kotlinx.serialization.encodeToString
import kotlinx.serialization.decodeFromString
class TypeConverter {
@TypeConverter
fun fromProfileToString(userProfile: UserProfile): String {
return Json.encodeToString(userProfile)
}
@TypeConverter
fun fromStringToProfile(userProfile: String): UserProfile {
return Json.decodeFromString(userProfile)
}
@TypeConverter
fun fromTextMessageToString(textMessage: TextMessage?): String {
return Json.encodeToString(textMessage ?: TextMessage())
}
@TypeConverter
fun fromStringToTextMessage(messageData: String): TextMessage {
return Json.decodeFromString(messageData)
}
@TypeConverter
fun fromImageMessageToString(imageMessage: ImageMessage?): String {
return Json.encodeToString(imageMessage ?: ImageMessage())
}
@TypeConverter
fun fromStringToImageMessage(messageData: String): ImageMessage {
return Json.decodeFromString(messageData)
}
@TypeConverter
fun fromAudioMessageToString(audioMessage: AudioMessage?): String {
return Json.encodeToString(audioMessage ?: AudioMessage())
}
@TypeConverter
fun fromStringToAudioMessage(messageData: String): AudioMessage {
return Json.decodeFromString(messageData)
}
@TypeConverter
fun fromVideoMessageToString(videoMessage: VideoMessage?): String {
return Json.encodeToString(videoMessage ?: VideoMessage())
}
@TypeConverter
fun fromStringToVideoMessage(messageData: String): VideoMessage {
return Json.decodeFromString(messageData)
}
@TypeConverter
fun fromFileMessageToString(fileMessage: FileMessage?): String {
return Json.encodeToString(fileMessage ?: FileMessage())
}
@TypeConverter
fun fromStringToFileMessage(messageData: String): FileMessage {
return Json.decodeFromString(messageData)
}
@TypeConverter
fun fromChatUserToString(chatUser: ChatUser): String {
return Json.encodeToString(chatUser)
}
@TypeConverter
fun fromStringToChatUser(chatUser: String): ChatUser {
return Json.decodeFromString(chatUser)
}
@TypeConverter
fun fromGroupToString(group: Group): String {
return Json.encodeToString(group)
}
@TypeConverter
fun fromStringToGroup(group: String): Group {
return Json.decodeFromString(group)
}
@TypeConverter
fun fromGroupMessageToString(groupMessage: GroupMessage): String {
return Json.encodeToString(groupMessage)
}
@TypeConverter
fun fromStringToGroupMessage(groupMessage: String): GroupMessage {
return Json.decodeFromString(groupMessage)
}
@TypeConverter
fun fromToMembersToString(to: ArrayList<String>): String {
return Json.encodeToString(to)
}
@TypeConverter
fun fromStringToMembers(to: String): ArrayList<String> {
return Json.decodeFromString(to)
}
@TypeConverter
fun fromProfilesToString(profiles: ArrayList<UserProfile>): String {
return Json.encodeToString(profiles)
}
@TypeConverter
fun fromStringToProfiles(profilesString: String): ArrayList<UserProfile> {
return Json.decodeFromString(profilesString)
}
@TypeConverter
fun fromGroupMembersToString(members: ArrayList<ChatUser>): String {
return Json.encodeToString(members)
}
@TypeConverter
fun fromStringToGroupMembers(members: String): ArrayList<ChatUser> {
return Json.decodeFromString(members)
}
@TypeConverter
fun fromGroupMsgStatusToString(status: ArrayList<Int>): String {
return Json.encodeToString(status)
}
@TypeConverter
fun fromStringToGroupMsgStatus(status: String): ArrayList<Int> {
return Json.decodeFromString(status)
}
@TypeConverter
fun fromSeenStatusListToString(status: ArrayList<Long>): String {
return Json.encodeToString(status)
}
@TypeConverter
fun fromStringToSeenStatusList(status: String): ArrayList<Long> {
return Json.decodeFromString(status)
}
}
================================================
FILE: app/src/main/java/com/gowtham/letschat/db/daos/ChatUserDao.kt
================================================
package com.gowtham.letschat.db.daos
import androidx.lifecycle.LiveData
import androidx.room.*
import com.gowtham.letschat.db.data.ChatUser
import com.gowtham.letschat.db.data.ChatUserWithMessages
import kotlinx.coroutines.flow.Flow
@Dao
interface ChatUserDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertUser(user: ChatUser)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertMultipleUser(users: List<ChatUser>)
@Query("SELECT * FROM ChatUser ORDER BY localName ASC")
fun getAllChatUser(): LiveData<List<ChatUser>>
@Query("SELECT * FROM ChatUser ORDER BY localName ASC")
fun getChatUserList(): List<ChatUser>
@Query("SELECT * FROM ChatUser WHERE id=:id")
fun getChatUserById(id: String): ChatUser?
@Query("SELECT * FROM ChatUser WHERE id=:id")
suspend fun getChatUserById2(id: String): ChatUser?
@Query("DELETE FROM ChatUser WHERE id=:userId")
suspend fun deleteUserById(userId: String)
@Query("DELETE FROM ChatUser")
fun nukeTable()
@Transaction
@Query("SELECT * FROM ChatUser")
fun getChatUserWithMessages(): Flow<List<ChatUserWithMessages>>
@Transaction
@Query("SELECT * FROM ChatUser")
fun getChatUserWithMessagesList(): List<ChatUserWithMessages>
}
================================================
FILE: app/src/main/java/com/gowtham/letschat/db/daos/GroupDao.kt
================================================
package com.gowtham.letschat.db.daos
import androidx.lifecycle.LiveData
import androidx.room.*
import com.gowtham.letschat.db.data.ChatUserWithMessages
import com.gowtham.letschat.db.data.Group
import com.gowtham.letschat.db.data.GroupWithMessages
import kotlinx.coroutines.flow.Flow
@Dao
interface GroupDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertGroup(group: Group)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertMultipleGroup(listOfGroup: List<Group>)
@Query("SELECT * FROM `Group` ORDER BY id ASC")
fun getAllGroup(): LiveData<List<Group>>
@Query("SELECT * FROM `Group` ORDER BY id ASC")
fun getGroupList(): List<Group>
@Query("SELECT * FROM `Group` WHERE id=:groupId")
fun getGroupById(groupId: String): Group?
@Query("DELETE FROM `Group` WHERE id=:groupId")
suspend fun deleteGroupById(groupId: String)
@Query("DELETE FROM `Group`")
fun nukeTable()
@Transaction
@Query("SELECT * FROM `Group`")
fun getGroupWithMessagesList(): List<GroupWithMessages>
@Transaction
@Query("SELECT * FROM `Group`")
fun getGroupWithMessages(): Flow<List<GroupWithMessages>>
}
================================================
FILE: app/src/main/java/com/gowtham/letschat/db/daos/GroupMessageDao.kt
================================================
package com.gowtham.letschat.db.daos
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.gowtham.letschat.db.data.GroupMessage
import com.gowtham.letschat.db.data.GroupWithMessages
import com.gowtham.letschat.db.data.Message
import kotlinx.coroutines.flow.Flow
@Dao
interface GroupMessageDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertMessage(message: GroupMessage)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertMultipleMessage(users: List<GroupMessage>)
@Query("SELECT * FROM GroupMessage")
fun getAllMessages(): LiveData<List<GroupMessage>>
@Query("SELECT * FROM GroupMessage")
fun getMessageList(): List<GroupMessage>
@Query("SELECT * FROM GroupMessage WHERE groupId=:groupId")
fun getChatsOfGroupList(groupId: String): List<GroupMessage>
@Query("SELECT * FROM GroupMessage WHERE groupId=:groupId")
fun getChatsOfGroup(groupId: String): Flow<List<GroupMessage>>
@Query("DELETE FROM GroupMessage WHERE createdAt=:createdAt")
suspend fun deleteMessageByCreatedAt(createdAt: Long)
@Query("DELETE FROM GroupMessage WHERE groupId=:groupId")
suspend fun deleteMessagesByGroupId(groupId: String)
@Query("DELETE FROM GroupMessage")
fun nukeTable()
}
================================================
FILE: app/src/main/java/com/gowtham/letschat/db/daos/MessageDao.kt
================================================
package com.gowtham.letschat.db.daos
import androidx.lifecycle.LiveData
import androidx.room.*
import com.gowtham.letschat.db.data.Message
import com.gowtham.letschat.utils.LogMessage
import kotlinx.coroutines.flow.Flow
@Dao
interface MessageDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertMessage(message: Message)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertMultipleMessage(users: List<Message>)
@Query("SELECT * FROM Message")
fun getAllMessages(): Flow<List<Message>>
@Query("SELECT * FROM Message")
fun getMessageList(): List<Message>
@Query("SELECT * FROM Message WHERE `chatUserId`=:chatUserId")
fun getChatsOfFriend(chatUserId: String): List<Message>
@Query("SELECT * FROM Message WHERE `chatUserId`=:chatUserId")
suspend fun getChatsOfFriend2(chatUserId: String): List<Message>
@Query("SELECT * FROM Message WHERE `to`=:chatUserId OR `from`=:chatUserId")
fun getMessagesByChatUserId(chatUserId: String): Flow<List<Message>>
@Query("SELECT * FROM Message WHERE createdAt=:createdAt")
suspend fun getMessageById(createdAt: Long): Message?
@Query("SELECT * FROM Message WHERE status<3")
fun getAllNotSeenMessages() : List<Message>
@Query("DELETE FROM Message WHERE createdAt=:createdAt")
suspend fun deleteMessageByCreatedAt(createdAt: Long)
@Query("DELETE FROM Message WHERE `to`=:userId")
suspend fun deleteMessagesByUserId(userId: String)
@Query("DELETE FROM Message")
fun nukeTable()
}
================================================
FILE: app/src/main/java/com/gowtham/letschat/db/data/ChatUser.kt
================================================
package com.gowtham.letschat.db.data
import android.os.Parcelable
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.google.firebase.firestore.IgnoreExtraProperties
import com.gowtham.letschat.models.UserProfile
import kotlinx.serialization.Serializable
@IgnoreExtraProperties
@Serializable
@kotlinx.parcelize.Parcelize
@Entity
data class ChatUser(
@PrimaryKey
var id: String,var localName: String,var user: UserProfile,
var documentId: String?=null,var locallySaved: Boolean=false,
var unRead: Int=0,var isSearchedUser: Boolean=false,var isSelected: Boolean=false): Parcelable
================================================
FILE: app/src/main/java/com/gowtham/letschat/db/data/ChatUserWithMessages.kt
================================================
package com.gowtham.letschat.db.data
import android.os.Parcelable
import androidx.room.Embedded
import androidx.room.Relation
@kotlinx.parcelize.Parcelize
class ChatUserWithMessages(
@Embedded
val user: ChatUser,
@Relation(
parentColumn = "id",
entityColumn = "chatUserId"
)
val messages: List<Message>) : Parcelable
================================================
FILE: app/src/main/java/com/gowtham/letschat/db/data/Group.kt
================================================
package com.gowtham.letschat.db.data
import android.os.Parcelable
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.google.firebase.firestore.Exclude
import com.google.firebase.firestore.IgnoreExtraProperties
import com.gowtham.letschat.models.UserProfile
import kotlinx.serialization.Serializable
@IgnoreExtraProperties
@Serializable
@kotlinx.parcelize.Parcelize
@Entity
data class Group(@PrimaryKey
var id: String="",var createdBy: String="",
var createdAt: Long=0,
var about: String="", var image: String="",
@set:Exclude @get:Exclude
var members: ArrayList<ChatUser>?=null, //only for storing in localdb
var profiles: ArrayList<UserProfile>?=null,
@set:Exclude @get:Exclude
var unRead: Int=0): Parcelable
================================================
FILE: app/src/main/java/com/gowtham/letschat/db/data/GroupMessage.kt
================================================
package com.gowtham.letschat.db.data
import android.os.Parcelable
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.google.firebase.firestore.IgnoreExtraProperties
import kotlinx.serialization.Serializable
@IgnoreExtraProperties
@Serializable
@kotlinx.parcelize.Parcelize
@Entity
data class GroupMessage(@PrimaryKey
val createdAt: Long, var groupId: String,
val from: String, val to: ArrayList<String>,
val senderName: String,
val senderImage: String,
val status: ArrayList<Int>,//0 th index is status of from user
val deliveryTime: ArrayList<Long>,
val seenTime: ArrayList<Long>,
var type: String="text",//0=text,1=audio,2=image,3=video,4=file,5=s_image
var textMessage: TextMessage?=null,
var imageMessage: ImageMessage?=null,
var audioMessage: AudioMessage?=null,
var videoMessage: VideoMessage?=null,
var fileMessage: FileMessage?=null): Parcelable
================================================
FILE: app/src/main/java/com/gowtham/letschat/db/data/GroupWithMessages.kt
================================================
package com.gowtham.letschat.db.data
import android.os.Parcelable
import androidx.room.Embedded
import androidx.room.Relation
@kotlinx.parcelize.Parcelize
class GroupWithMessages (
@Embedded
val group: Group,
@Relation(
parentColumn = "id",
entityColumn = "groupId"
)
val messages: List<GroupMessage>) : Parcelable
================================================
FILE: app/src/main/java/com/gowtham/letschat/db/data/Message.kt
================================================
package com.gowtham.letschat.db.data
import android.os.Parcelable
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.google.firebase.Timestamp
import com.google.firebase.firestore.Exclude
import com.google.firebase.firestore.IgnoreExtraProperties
import com.google.firebase.firestore.ServerTimestamp
import kotlinx.serialization.Serializable
@IgnoreExtraProperties
@Serializable
@kotlinx.parcelize.Parcelize
@Entity
data class Message(
@PrimaryKey
val createdAt: Long, var deliveryTime: Long=0L,
var seenTime: Long=0L,
val from: String, val to: String,
val senderName: String,
val senderImage: String,
var type: String="text",//0=text,1=audio,2=image,3=video,4=file
var status: Int=0,//0=sending,1=sent,2=delivered,3=seen,4=failed
var textMessage: TextMessage?=null,
var imageMessage: ImageMessage?=null,
var audioMessage: AudioMessage?=null,
var videoMessage: VideoMessage?=null,
var fileMessage: FileMessage?=null,
var chatUsers: ArrayList<String>?=null,
@set:Exclude @get:Exclude
var chatUserId: String?=null): Parcelable
@Serializable
@kotlinx.parcelize.Parcelize
data class TextMessage(val text: String?=null): Parcelable
@Serializable
@kotlinx.parcelize.Parcelize
data class AudioMessage(var uri: String?=null,val duration: Int=0): Parcelable
@Serializable
@kotlinx.parcelize.Parcelize
data class ImageMessage(var uri: String?=null,var imageType: String="image"): Parcelable
@Serializable
@kotlinx.parcelize.Parcelize
data class VideoMessage(val uri: String?=null,val duration: Int=0): Parcelable
@Serializable
@kotlinx.parcelize.Parcelize
data class FileMessage(val name: String?=null,
val uri: String?=null,val duration: Int=0): Parcelable
================================================
FILE: app/src/main/java/com/gowtham/letschat/di/AppModule.kt
================================================
package com.gowtham.letschat.di
import android.content.Context
import com.google.firebase.firestore.CollectionReference
import com.google.firebase.firestore.FirebaseFirestore
import com.gowtham.letschat.core.MessageStatusUpdater
import com.gowtham.letschat.db.DbRepository
import com.gowtham.letschat.db.DefaultDbRepo
import com.gowtham.letschat.db.daos.ChatUserDao
import com.gowtham.letschat.db.daos.GroupDao
import com.gowtham.letschat.db.daos.GroupMessageDao
import com.gowtham.letschat.db.daos.MessageDao
import com.gowtham.letschat.ui.activities.MainActivity
import com.gowtham.letschat.utils.MPreference
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.internal.managers.ApplicationComponentManager
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Qualifier
import javax.inject.Singleton
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class MessageCollection
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class GroupCollection
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Singleton
@Provides
fun provideFireStoreInstance(): FirebaseFirestore {
return FirebaseFirestore.getInstance()
}
@Singleton
@Provides
fun provideUsersCollectionRef(firestore: FirebaseFirestore): CollectionReference {
return firestore.collection("Users")
}
@MessageCollection
@Singleton
@Provides
fun provideMessagesCollectionRef(firestore: FirebaseFirestore): CollectionReference {
return firestore.collection("Messages")
}
@GroupCollection
@Singleton
@Provides
fun provideGroupCollectionRef(firestore: FirebaseFirestore): CollectionReference {
return firestore.collection("Groups")
}
@Provides
fun provideMainActivity(): MainActivity {
return MainActivity()
}
@Provides
fun provideDefaultDbRepo(userDao: ChatUserDao,
groupDao: GroupDao,
groupMsgDao: GroupMessageDao,
messageDao: MessageDao): DefaultDbRepo {
return DbRepository(userDao, groupDao, groupMsgDao, messageDao)
}
}
================================================
FILE: app/src/main/java/com/gowtham/letschat/di/DbModule.kt
================================================
package com.gowtham.letschat.di
import android.content.Context
import androidx.room.Room
import com.gowtham.letschat.db.ChatUserDatabase
import com.gowtham.letschat.utils.Constants.CHAT_USER_DB_NAME
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object DbModule {
@Singleton
@Provides
fun provideChatUserDb(@ApplicationContext context: Context): ChatUserDatabase{
return Room.databaseBuilder(context,ChatUserDatabase::class.java,
CHAT_USER_DB_NAME).build()
}
@Singleton
@Provides
fun provideChatUserDao(db: ChatUserDatabase) = db.getChatUserDao()
@Singleton
@Provides
fun provideMessageDao(db: ChatUserDatabase) = db.getMessageDao()
@Singleton
@Provides
fun provideGroupDao(db: ChatUserDatabase) = db.getGroupDao()
@Singleton
@Provides
fun provideGroupMessageDao(db: ChatUserDatabase) = db.getGroupMessageDao()
}
================================================
FILE: app/src/main/java/com/gowtham/letschat/fragments/FAttachment.kt
================================================
package com.gowtham.letschat.fragments
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.gowtham.letschat.databinding.FAttachmentBinding
import com.gowtham.letschat.databinding.FImageSrcSheetBinding
import com.gowtham.letschat.utils.BottomSheetEvent
import org.greenrobot.eventbus.EventBus
class FAttachment : BottomSheetDialogFragment() {
private lateinit var binding: FAttachmentBinding
companion object{
fun newInstance(bundle : Bundle) : FAttachment{
val fragment = FAttachment()
fragment.arguments=bundle
return fragment
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View {
binding = FAttachmentBinding.inflate(layoutInflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.imgCamera.setOnClickListener {
EventBus.getDefault().post(BottomSheetEvent(0))
dismiss()
}
binding.imgGallery.setOnClickListener {
EventBus.getDefault().post(BottomSheetEvent(1))
dismiss()
}
binding.videoGallery.setOnClickListener {
EventBus.getDefault().post(BottomSheetEvent(2))
dismiss()
}
binding.videoCamera.setOnClickListener {
EventBus.getDefault().post(BottomSheetEvent(3))
dismiss()
}
}
}
================================================
FILE: app/src/main/java/com/gowtham/letschat/fragments/FImageSrcSheet.kt
================================================
package com.gowtham.letschat.fragments
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.gowtham.letschat.databinding.FImageSrcSheetBinding
interface SheetListener{
fun selectedItem(index: Int)
}
class FImageSrcSheet constructor() : BottomSheetDialogFragment() {
private lateinit var binding: FImageSrcSheetBinding
private lateinit var listener: SheetListener
companion object{
fun newInstance(bundle : Bundle) : FImageSrcSheet{
val fragment = FImageSrcSheet()
fragment.arguments=bundle
return fragment
}
}
fun addListener(listener: SheetListener){
this.listener=listener
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View {
binding = FImageSrcSheetBinding.inflate(layoutInflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.txtCamera.setOnClickListener {
listener.selectedItem(0)
dismiss()
}
binding.txtGallery.setOnClickListener {
listener.selectedItem(1)
dismiss()
}
}
}
================================================
FILE: app/src/main/java/com/gowtham/letschat/fragments/MainFragmentFactory.kt
================================================
package com.gowtham.letschat.fragments
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentFactory
import com.gowtham.letschat.fragments.contacts.FContacts
import com.gowtham.letschat.utils.MPreference
import javax.inject.Inject
class MainFragmentFactory @Inject constructor(
private val preference: MPreference
) : FragmentFactory() {
override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
return when(className) {
FContacts::class.java.name -> FContacts(preference)
else -> super.instantiate(classLoader, className)
}
}
}
================================================
FILE: app/src/main/java/com/gowtham/letschat/fragments/add_group_members/AdAddMembers.kt
================================================
package com.gowtham.letschat.fragments.add_group_members
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.gowtham.letschat.databinding.RowAddMemberBinding
import com.gowtham.letschat.db.data.ChatUser
import com.gowtham.letschat.utils.DiffCallbackChatUser
import com.gowtham.letschat.utils.ItemClickListener
import java.util.*
import kotlin.collections.ArrayList
class AdAddMembers(private val context: Context) :
ListAdapter<ChatUser, RecyclerView.ViewHolder>(DiffCallbackChatUser()) {
companion object {
var allContacts = ArrayList<ChatUser>()
lateinit var listener: ItemClickListener
}
fun filter(query: String) {
val list = ArrayList<ChatUser>()
if (query.isEmpty()) {
list.addAll(allContacts)
} else {
val queryList = allContacts.filter {
it.localName.toLowerCase(Locale.getDefault())
.contains(query.toLowerCase(Locale.getDefault()))
}
list.addAll(queryList)
}
submitList(list as MutableList<ChatUser>)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = RowAddMemberBinding.inflate(layoutInflater, parent, false)
return MyViewHolder(binding)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder as MyViewHolder
holder.bind(getItem(position))
}
override fun submitList(list: List<ChatUser>?) {
super.submitList(list?.let { ArrayList(it) })
}
class MyViewHolder(val binding: RowAddMemberBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: ChatUser) {
binding.chatUser = item
binding.viewRoot.setOnClickListener {
listener.onItemClicked(it, bindingAdapterPosition)
}
binding.executePendingBindings()
}
}
}
================================================
FILE: app/src/main/java/com/gowtham/letschat/fragments/add_group_members/AdChip.kt
================================================
package com.gowtham.letschat.fragments.add_group_members
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.gowtham.letschat.databinding.RowChipBinding
import com.gowtham.letschat.db.data.ChatUser
import com.gowtham.letschat.utils.DiffCallbackChatUser
import com.gowtham.letschat.utils.ItemClickListener
import kotlin.collections.ArrayList
class AdChip (private val context: Context) :
ListAdapter<ChatUser, RecyclerView.ViewHolder>(DiffCallbackChatUser()) {
companion object{
var allAddedContacts=ArrayList<ChatUser>()
lateinit var listener: ItemClickListener
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding= RowChipBinding.inflate(layoutInflater, parent, false)
return MyViewHolder(binding)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder as MyViewHolder
holder.bind(getItem(position))
}
class MyViewHolder(val binding: RowChipBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: ChatUser) {
binding.chatUser = item
binding.chip.setOnCloseIconClickListener {
listener.onItemClicked(it,bindingAdapterPosition)
}
binding.executePendingBindings()
}
}
}
================================================
FILE: app/src/main/java/com/gowtham/letschat/fragments/add_group_members/AddGroupViewModel.kt
================================================
package com.gowtham.letschat.fragments.add_group_members
import android.content.Context
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.gowtham.letschat.core.QueryCompleteListener
import com.gowtham.letschat.db.DbRepository
import com.gowtham.letschat.db.data.ChatUser
import com.gowtham.letschat.models.UserProfile
import com.gowtham.letschat.utils.LoadState
import com.gowtham.letschat.utils.UserUtils
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@HiltViewModel
class AddGroupViewModel @Inject
constructor(@ApplicationContext context: Context,
private val dbRepository: DbRepository
) : ViewModel() {
private val chipList= MutableLiveData<ArrayList<ChatUser>>()
private val allContacts= ArrayList<ChatUser>()
val queryState = MutableLiveData<LoadState>()
private lateinit var chatUsers: List<ChatUser>
var isFirstCall=true
init {
Timber.v("AddGroupViewModel init")
viewModelScope.launch(Dispatchers.IO) {
chatUsers=dbRepository.getChatUserList().filter { it.locallySaved }
if (chatUsers.isNullOrEmpty())
startQuery()
}
}
fun getChatList() = dbRepository.getAllChatUser()
fun getChipList(): LiveData<ArrayList<ChatUser>>{
return chipList
}
fun setChipList(list: List<ChatUser>){
val newList=ArrayList<ChatUser>(list.filter { it.isSelected })
chipList.value=newList
}
fun setContactList(list: List<ChatUser>) {
allContacts.clear()
allContacts.addAll(list)
}
fun getContactList() = allContacts
private fun startQuery() {
try {
queryState.postValue(LoadState.OnLoading)
val success= UserUtils.updateContactsProfiles(onQueryCompleted)
if (!success) {
Timber.v("Recursion error")
queryState.postValue(LoadState.OnFailure(java.lang.Exception("Recursion exception")))
}
} catch (e: Exception) {
e.printStackTrace()
}
}
override fun onCleared() {
AdChip.allAddedContacts.clear()
allContacts.clear()
isFirstCall=true
Timber.v("OnClear AddGroup")
super.onCleared()
}
private val onQueryCompleted=object : QueryCompleteListener {
override fun onQueryCompleted(queriedList: ArrayList<UserProfile>) {
try {
val localContacts=UserUtils.fetchContacts(context)
val finalList = ArrayList<ChatUser>()
val queriedList=UserUtils.queriedList
//set localsaved name to queried users
for(doc in queriedList){
val savedNumber=localContacts.firstOrNull { it.mobile == doc.mobile?.number }
if(savedNumber!=null){
val chatUser= UserUtils.getChatUser(doc, chatUsers, savedNumber.name)
finalList.add(chatUser)
}
}
queryState.value=LoadState.OnSuccess(finalList)
CoroutineScope(Dispatchers.IO).launch {
dbRepository.insertMultipleUser(finalList)
}
setDefaultValues()
} catch (e: Exception) {
e.printStackTrace()
}
}
}
private fun setDefaultValues() {
//set default values
UserUtils.totalRecursionCount=0
UserUtils.resultCount=0
UserUtils.queriedList.clear()
}
}
================================================
FILE: app/src/main/java/com/gowtham/letschat/fragments/add_group_members/FAddGroupMembers.kt
================================================
package com.gowtham.letschat.fragments.add_group_members
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.SearchView
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import com.gowtham.letschat.R
import com.gowtham.letschat.databinding.FAddGroupMembersBinding
import com.gowtham.letschat.db.data.ChatUser
import com.gowtham.letschat.utils.*
import dagger.hilt.android.AndroidEntryPoint
import timber.log.Timber
import javax.inject.Inject
@AndroidEntryPoint
class FAddGroupMembers : Fragment(), ItemClickListener {
private lateinit var binding: FAddGroupMembersBinding
@Inject
lateinit var preference: MPreference
private lateinit var searchView: SearchView
private var contactList = ArrayList<ChatUser>()
private val adContact: AdAddMembers by lazy {
AdAddMembers(requireContext())
}
private val adChip: AdChip by lazy {
AdChip(requireContext())
}
private val viewModel: AddGroupViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FAddGroupMembersBinding.inflate(layoutInflater, container, false)
binding.lifecycleOwner = viewLifecycleOwner
setHasOptionsMenu(true)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Timber.v("onViewCreated")
binding.toolbar.setNavigationOnClickListener {
findNavController().popBackStack()
}
binding.fab.setOnClickListener {
val addedContacts = AdChip.allAddedContacts
if (addedContacts.isNotEmpty()) {
val action = FAddGroupMembersDirections.actionFAddGroupMembersToFCreateGroup(
addedContacts.toTypedArray()
)
findNavController().navigate(action)
}
}
setToolbar()
setDataInView()
subscribeObservers()
}
private fun setToolbar() {
binding.toolbar.inflateMenu(R.menu.menu_search)
val searchItem: MenuItem? = binding.toolbar.menu.findItem(R.id.action_search)
searchView = searchItem?.actionView as SearchView
searchView.apply {
maxWidth = Integer.MAX_VALUE
queryHint = getString(R.string.txt_search)
}
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
return true
}
override fun onQueryTextChange(newText: String?): Boolean {
adContact.filter(newText.toString())
return true
}
})
}
private fun subscribeObservers() {
viewModel.getChatList().observe(viewLifecycleOwner, { contacts ->
val allContacts = contacts.filter { it.locallySaved }
if (allContacts.isNotEmpty()) {
if (viewModel.isFirstCall) {
viewModel.setContactList(allContacts)
viewModel.isFirstCall=false
}
Timber.v("allContacts ->${viewModel.getContactList().first().localName}")
contactList.clear()
contactList.addAll(viewModel.getContactList())
AdAddMembers.allContacts = contactList
adContact.submitList(contactList)
if (!searchView.isIconified)
adContact.filter(searchView.query.toString())
}
})
viewModel.getChipList().observe(viewLifecycleOwner, { addedList ->
AdChip.allAddedContacts = addedList
adChip.submitList(addedList.toList())
adChip.notifyDataSetChanged()
if (addedList.isEmpty()) {
binding.txtEmptyMembers.show()
binding.fab.hide()} else {
binding.txtEmptyMembers.hide()
binding.fab.show()
binding.listChip.post {
binding.listChip.smoothScrollToPosition(addedList.lastIndex)
}
}
})
viewModel.queryState.observe(viewLifecycleOwner, {
searchView.isEnabled = it !is LoadState.OnLoading
when (it) {
is LoadState.OnSuccess -> {
val emptyList = it.data as ArrayList<*>
if (emptyList.isEmpty()) {
binding.viewEmpty.show()
binding.progress.hide()
binding.viewEmpty.playAnimation()
}else{
binding.viewHolder.show()
binding.progress.hide()
}
}
is LoadState.OnFailure -> {
binding.viewHolder.hide()
binding.progress.hide()
binding.viewEmpty.playAnimation()
}
is LoadState.OnLoading -> {
binding.viewEmpty.hide()
binding.viewHolder.hide()
binding.progress.show()
}
}
})
}
private fun setDataInView() {
binding.listContact.adapter = adContact
binding.listChip.adapter = adChip
AdAddMembers.listener = this
AdChip.listener = chipListener
adContact.addRestorePolicy()
adChip.addRestorePolicy()
}
override fun onItemClicked(v: View, position: Int) {
val currentList = ArrayList(adContact.currentList)
val user = currentList[position]
user.apply {
isSelected = !isSelected
}
currentList.set(position, user)
adContact.submitList(currentList)
adContact.notifyItemChanged(position)
val allContact = AdAddMembers.allContacts
val user1 = allContact.find { it.id == user.id }
val index = allContact.indexOf(user1)
allContact.set(index, user1!!)
viewModel.setContactList(allContact) //update in allContacts list
val chipList = AdChip.allAddedContacts
val contains = chipList.find { it.id == user.id }
if (contains == null)
chipList.add(user)
else
chipList.set(chipList.indexOf(contains), user)
viewModel.setChipList(chipList) //update chip list
}
private val chipListener: ItemClickListener = object : ItemClickListener {
override fun onItemClicked(v: View, position: Int) {
val added = AdChip.allAddedContacts
var index: Int?
val clickedUser = added.get(position)
val currentList = ArrayList(adContact.currentList)
val user = currentList.find { it.id == clickedUser.id }
if (user != null) { //update in current list
index = currentList.indexOf(user)
user.isSelected = false
currentList.set(index, user)
adContact.submitList(currentList)
adContact.notifyItemChanged(index)
}
val allUsers = AdAddMembers.allContacts //update allContactList
val user1 = allUsers.find { it.id == clickedUser.id }
val indexAllList = allUsers.indexOf(user1)
user1?.isSelected = false
allUsers.set(indexAllList, user1!!)
viewModel.setContactList(allUsers)
added.removeAt(position)
viewModel.setChipList(added)
if (!searchView.isIconified) //remove from chip list
adContact.filter(searchView.query.toString())
}
}
override fun onResume() {
super.onResume()
val chipList = AdChip.allAddedContacts
val allUsers = AdAddMembers.allContacts
for (user in allUsers)
user.isSelected = chipList.contains(user)
viewModel.setContactList(allUsers)
}
}
================================================
FILE: app/src/main/java/com/gowtham/letschat/fragments/contacts/AdContact.kt
================================================
package com.gowtham.letschat.fragments.contacts
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.gowtham.letschat.databinding.RowContactBinding
import com.gowtham.letschat.db.data.ChatUser
import com.gowtham.letschat.utils.ItemClickListener
import java.util.*
import kotlin.collections.ArrayList
class AdContact(context: Context, allUsers: ArrayList<ChatUser>) :
RecyclerView.Adapter<AdContact.UserViewModel>() {
private var users: ArrayList<ChatUser> = allUsers
private var allUsers: ArrayList<ChatUser> = ArrayList()
init {
this.allUsers.addAll(users)
}
companion object {
var itemClickListener: ItemClickListener? = null
}
fun filter(query: String) {
try {
users.clear()
if (query.isEmpty())
users.addAll(allUsers)
else {
for (country in allUsers) {
if (country.localName.toLowerCase(Locale.getDefault())
.contains(query.toLowerCase(Locale.getDefault())))
users.add(country)
}
}
notifyDataSetChanged()
} catch (e: Exception) {
e.stackTrace
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewModel {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = RowContactBinding.inflate(layoutInflater, parent, false)
return UserViewModel(binding)
}
override fun onBindViewHolder(holder: UserViewModel, position: Int) {
holder.bind(users[position])
}
class UserViewModel(val binding: RowContactBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: ChatUser) {
binding.chatUser = item
binding.viewRoot.setOnClickListener { v ->
itemClickListener?.onItemClicked(v, bindingAdapterPosition)
}
binding.executePendingBindings()
}
}
override fun getItemCount() = users.size
}
================================================
FILE: app/src/main/java/com/gowtham/letschat/fragments/contacts/ContactsViewModel.kt
================================================
package com.gowtham.letschat.fragments.contacts
import android.content.Context
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.gowtham.letschat.core.QueryCompleteListener
import com.gowtham.letschat.db.DbRepository
import com.gowtham.letschat.db.data.ChatUser
import com.gowtham.letschat.models.UserProfile
import com.gowtham.letschat.utils.LoadState
import com.gowtham.letschat.utils.LogMessage
import com.gowtham.letschat.utils.UserUtils
import com.gowtham.letschat.utils.toast
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@HiltViewModel
class ContactsViewModel @Inject constructor(
@ApplicationContext context: Context,
private val dbRepo: DbRepository) : ViewModel() {
val queryState = MutableLiveData<LoadState>()
val list= MutableLiveData<ArrayList<ChatUser>>()
val contactsCount = MutableLiveData("0 Contacts")
private lateinit var chatUsers: List<ChatUser>
init {
LogMessage.v("ContactsViewModel init")
CoroutineScope(Dispatchers.IO).launch{
chatUsers=dbRepo.getChatUserList()
}
}
fun getContacts()=dbRepo.getAllChatUser()
fun setContactCount(size: Int) {
contactsCount.value="$size Contacts"
}
fun startQuery() {
try {
queryState.value=LoadState.OnLoading
val success=UserUtils.updateContactsProfiles(onQueryCompleted)
if (!success)
queryState.value=LoadState.OnFailure(java.lang.Exception("Recursion exception"))
} catch (e: Exception) {
e.printStackTrace()
}
}
private val onQueryCompleted=object : QueryCompleteListener {
override fun onQueryCompleted(queriedList: ArrayList<UserProfile>) {
try {
LogMessage.v("Query Completed ${UserUtils.queriedList.size}")
val localContacts=UserUtils.fetchContacts(context)
val finalList = ArrayList<ChatUser>()
val list=UserUtils.queriedList
//set localsaved name to queried users
for(doc in list){
val savedNumber=localContacts.firstOrNull { it.mobile == doc.mobile?.number }
if(savedNumber!=null){
val chatUser= UserUtils.getChatUser(doc, chatUsers, savedNumber.name)
Timber.v("Contact ${chatUser.documentId}")
finalList.add(chatUser)
}
}
contactsCount.value="${finalList.size} Contacts"
queryState.value=LoadState.OnSuccess(finalList)
CoroutineScope(Dispatchers.IO).launch {
dbRepo.insertMultipleUser(finalList)
}
context.toast("Contacts refreshed")
setDefaultValues()
} catch (e: Exception) {
e.printStackTrace()
}
}
}
private fun setDefaultValues() {
//set default values
UserUtils.totalRecursionCount=0
UserUtils.resultCount=0
UserUtils.queriedList.clear()
}
override fun onCleared() {
LogMessage.v("ContactsViewModel OnCleared")
super.onCleared()
}
fun setUnReadCountZero(chatUser: ChatUser) {
UserUtils.setUnReadCountZero(dbRepo,chatUser)
}
}
================================================
FILE: app/src/main/java/com/gowtham/letschat/fragments/contacts/FContacts.kt
================================================
package com.gowtham.letschat.fragments.contacts
import android.app.Activity
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.SearchView
import androidx.datastore.preferences.core.Preferences
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.RecyclerView
import com.gowtham.letschat.R
import com.gowtham.letschat.databinding.FContactsBinding
import com.gowtham.letschat.db.data.ChatUser
import com.gowtham.letschat.db.daos.ChatUserDao
import com.gowtham.letschat.utils.*
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
class FContacts @Inject constructor(private val preference: MPreference) : Fragment(), ItemClickListener {
private lateinit var binding: FContactsBinding
private lateinit var context: Activity
private lateinit var searchView: SearchView
private lateinit var searchItem: MenuItem
private lateinit var menuRefresh: MenuItem
private val viewModel: ContactsViewModel by viewModels()
private var contactList = ArrayList<ChatUser>()
private lateinit var adContact: AdContact
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View {
binding = FContactsBinding.inflate(layoutInflater, container, false)
setHasOptionsMenu(true)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
context = requireActivity()
binding.lifecycleOwner = viewLifecycleOwner
binding.viewmodel = viewModel
setToolbar()
setDataInView()
subscribeObservers()
}
private fun subscribeObservers() {
viewModel.getContacts().observe(viewLifecycleOwner, { contacts->
LogMessage.v("Size ${contacts.size}")
val allContacts=contacts.filter { it.locallySaved }
if (allContacts.isEmpty() && viewModel.queryState.value == null)
viewModel.startQuery()
else {
viewModel.setContactCount(allContacts.size)
contactList.clear()
contactList= allContacts as ArrayList<ChatUser>
adContact = AdContact(requireContext(), contactList)
binding.listContact.adapter = adContact
if(searchItem.isActionViewExpanded)
adContact.filter(searchView.query.toString())
}
})
viewModel.queryState.observe(viewLifecycleOwner,{
searchItem.isEnabled = it !is LoadState.OnLoading
menuRefresh.isEnabled = it !is LoadState.OnLoading
})
}
private fun setDataInView() {
try {
adContact = AdContact(requireContext(), contactList)
binding.listContact.adapter = adContact
AdContact.itemClickListener = this
adContact.stateRestorationPolicy =
RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun setToolbar() {
try {
binding.toolbar.setNavigationIcon(R.drawable.ic_arrow_back)
binding.toolbar.setNavigationOnClickListener {
findNavController().popBackStack()
}
binding.toolbar.inflateMenu(R.menu.menu_contacts)
searchItem = binding.toolbar.menu.findItem(R.id.action_search)
menuRefresh = binding.toolbar.menu.findItem(R.id.action_refresh)
menuRefresh.setOnMenuItemClickListener {
viewModel.startQuery()
true
}
searchView = searchItem.actionView as SearchView
searchView.apply {
maxWidth = Integer.MAX_VALUE
queryHint = getString(R.string.txt_search)
}
searchItem.setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
override fun onMenuItemActionExpand(item: MenuItem?): Boolean {
menuRefresh.isVisible = false
return true
}
override fun onMenuItemActionCollapse(item: MenuItem?): Boolean {
menuRefresh.isVisible = true
return true
}
})
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
return true
}
override fun onQueryTextChange(newText: String?): Boolean {
adContact.filter(newText.toString())
return true
}
})
} catch (e: Exception) {
e.printStackTrace()
}
}
override fun onItemClicked(v: View, position: Int) {
viewModel.setUnReadCountZero(contactList[position])
preference.setCurrentUser(contactList[position].user.uId!!)
val action = FContactsDirections.actionFContactsToChat(
contactList[position]
)
findNavController().navigate(action)
}
}
================================================
FILE: app/src/main/java/com/gowtham/letschat/fragments/countries/AdCountries.kt
================================================
package com.gowtham.letschat.fragments.countries
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.RecyclerView
import com.gowtham.letschat.R
import com.gowtham.letschat.databinding.RowCountryBinding
import com.gowtham.letschat.models.Country
import com.gowtham.letschat.utils.Countries
import com.gowtham.letschat.utils.ItemClickListener
import java.util.*
import kotlin.collections.ArrayList
class AdCountries : RecyclerView.Adapter<AdCountries.UserViewModel>() {
lateinit var countries: ArrayList<Country>
private lateinit var allCountries: ArrayList<Country>
fun setData() {
this.countries = Countries.getCountries() as ArrayList<Country>
allCountries= ArrayList()
allCountries.addAll(countries)
}
companion object {
var itemClickListener: ItemClickListener? = null
}
fun filter(query: String) {
try {
countries.clear()
if (query.isEmpty())
countries.addAll(allCountries)
else {
for (country in allCountries) {
if (country.name.toLowerCase(Locale.getDefault())
.contains(query.toLowerCase(Locale.getDefault()))
)
countries.add(country)
}
}
notifyDataSetChanged()
} catch (e: Exception) {
e.stackTrace
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewModel {
val binding: RowCountryBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.row_country, parent, false
)
return UserViewModel(binding)
}
override fun onBindViewHolder(holder: UserViewModel, position: Int) {
holder.bind(countries[position])
}
class UserViewModel(val binding: RowCountryBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: Country) {
binding.country = item
binding.viewRoot.setOnClickListener { v ->
itemClickListener?.onItemClicked(v, bindingAdapterPosition)
}
binding.executePendingBindings()
}
}
override fun getItemCount() = countries.size
}
================================================
FILE: app/src/main/java/com/gowtham/letschat/fragments/countries/FCountries.kt
================================================
package com.gowtham.letschat.fragments.countries
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.SearchView
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.gowtham.letschat.R
import com.gowtham.letschat.databinding.FCountriesBinding
import com.gowtham.letschat.ui.activities.SharedViewModel
import com.gowtham.letschat.utils.ItemClickListener
import com.gowtham.letschat.utils.Utils
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class FCountries : Fragment(), ItemClickListener {
private lateinit var binding: FCountriesBinding
private lateinit var recyclerView: RecyclerView
private lateinit var searchView: SearchView
private lateinit var adCountry: AdCountries
private lateinit var sharedViewModel: SharedViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? {
binding = FCountriesBinding.inflate(layoutInflater, container, false)
setHasOptionsMenu(true)
binding.toolbar.setNavigationOnClickListener {
findNavController().popBackStack()
}
binding.lifecycleOwner = viewLifecycleOwner
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
recyclerView = binding.listCountry
sharedViewModel = ViewModelProvider(requireActivity()).get(SharedViewModel::class.java)
setDataInView()
}
private fun setDataInView() {
try {
binding.toolbar.inflateMenu(R.menu.menu_search)
val searchItem: MenuItem? = binding.toolbar.menu.findItem(R.id.action_search)
searchView = searchItem?.actionView as SearchView
searchView.apply {
maxWidth = Integer.MAX_VALUE
queryHint = getString(R.string.txt_search)
}
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
return true
}
override fun onQueryTextChange(newText: String?): Boolean {
adCountry.filter(newText.toString())
return true
}
})
AdCountries.itemClickListener = this
adCountry = AdCountries()
adCountry.setData()
recyclerView.adapter = adCountry
} catch (e: Exception) {
e.printStackTrace()
}
}
override fun onItemClicked(v: View, position: Int) {
findNavController().popBackStack()
sharedViewModel.setCountry(adCountry.countries[position])
}
}
==============================================
gitextract_cohzfmis/ ├── .firebaserc ├── .gitignore ├── .idea/ │ ├── assetWizardSettings.xml │ ├── caches/ │ │ └── build_file_checksums.ser │ ├── codeStyles/ │ │ ├── Project.xml │ │ └── codeStyleConfig.xml │ ├── compiler.xml │ ├── inspectionProfiles/ │ │ └── Project_Default.xml │ ├── jarRepositories.xml │ ├── misc.xml │ ├── modules.xml │ ├── navEditor.xml │ ├── runConfigurations.xml │ └── vcs.xml ├── LICENSE ├── README.md ├── app/ │ ├── .gitignore │ ├── app-debug.apk │ ├── build.gradle │ ├── google-services.json │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── gowtham/ │ │ └── letschat/ │ │ ├── FlowUtilAndroidTest.kt │ │ ├── HiltTestRunner.kt │ │ ├── LiveDataUtilAndroidTest.kt │ │ ├── db/ │ │ │ └── daos/ │ │ │ ├── ChatUserDaoTest.kt │ │ │ ├── GroupDaoTest.kt │ │ │ ├── GroupMessageDaoTest.kt │ │ │ └── MessageDaoTest.kt │ │ └── di/ │ │ └── TestAppModule.kt │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── gowtham/ │ │ │ └── letschat/ │ │ │ ├── FirebasePush.kt │ │ │ ├── MApplication.kt │ │ │ ├── core/ │ │ │ │ ├── ChatHandler.kt │ │ │ │ ├── ChatUserProfileListener.kt │ │ │ │ ├── ChatUserUtil.kt │ │ │ │ ├── ContactsQuery.kt │ │ │ │ ├── GroupChatHandler.kt │ │ │ │ ├── GroupMsgSender.kt │ │ │ │ ├── GroupMsgStatusUpdater.kt │ │ │ │ ├── GroupQuery.kt │ │ │ │ ├── MessageSender.kt │ │ │ │ └── MessageStatusUpdater.kt │ │ │ ├── db/ │ │ │ │ ├── ChatUserDatabase.kt │ │ │ │ ├── DbRepository.kt │ │ │ │ ├── DefaultDbRepo.kt │ │ │ │ ├── TypeConverter.kt │ │ │ │ ├── daos/ │ │ │ │ │ ├── ChatUserDao.kt │ │ │ │ │ ├── GroupDao.kt │ │ │ │ │ ├── GroupMessageDao.kt │ │ │ │ │ └── MessageDao.kt │ │ │ │ └── data/ │ │ │ │ ├── ChatUser.kt │ │ │ │ ├── ChatUserWithMessages.kt │ │ │ │ ├── Group.kt │ │ │ │ ├── GroupMessage.kt │ │ │ │ ├── GroupWithMessages.kt │ │ │ │ └── Message.kt │ │ │ ├── di/ │ │ │ │ ├── AppModule.kt │ │ │ │ └── DbModule.kt │ │ │ ├── fragments/ │ │ │ │ ├── FAttachment.kt │ │ │ │ ├── FImageSrcSheet.kt │ │ │ │ ├── MainFragmentFactory.kt │ │ │ │ ├── add_group_members/ │ │ │ │ │ ├── AdAddMembers.kt │ │ │ │ │ ├── AdChip.kt │ │ │ │ │ ├── AddGroupViewModel.kt │ │ │ │ │ └── FAddGroupMembers.kt │ │ │ │ ├── contacts/ │ │ │ │ │ ├── AdContact.kt │ │ │ │ │ ├── ContactsViewModel.kt │ │ │ │ │ └── FContacts.kt │ │ │ │ ├── countries/ │ │ │ │ │ ├── AdCountries.kt │ │ │ │ │ └── FCountries.kt │ │ │ │ ├── create_group/ │ │ │ │ │ ├── CreateGroupViewModel.kt │ │ │ │ │ └── FCreateGroup.kt │ │ │ │ ├── group_chat/ │ │ │ │ │ ├── AdGroupChat.kt │ │ │ │ │ ├── FGroupChat.kt │ │ │ │ │ └── GroupChatViewModel.kt │ │ │ │ ├── group_chat_home/ │ │ │ │ │ ├── AdGroupChatHome.kt │ │ │ │ │ ├── FGroupChatHome.kt │ │ │ │ │ └── GroupChatHomeViewModel.kt │ │ │ │ ├── login/ │ │ │ │ │ ├── FLogin.kt │ │ │ │ │ ├── FVerify.kt │ │ │ │ │ ├── LogInViewModel.kt │ │ │ │ │ └── LoginRepo.kt │ │ │ │ ├── myprofile/ │ │ │ │ │ ├── FMyProfile.kt │ │ │ │ │ └── FMyProfileViewModel.kt │ │ │ │ ├── profile/ │ │ │ │ │ ├── FProfile.kt │ │ │ │ │ └── ProfileViewModel.kt │ │ │ │ ├── search/ │ │ │ │ │ ├── FSearch.kt │ │ │ │ │ ├── FSearchViewModel.kt │ │ │ │ │ └── SearchRepo.kt │ │ │ │ ├── single_chat/ │ │ │ │ │ ├── AdChat.kt │ │ │ │ │ ├── FSingleChat.kt │ │ │ │ │ └── SingleChatViewModel.kt │ │ │ │ └── single_chat_home/ │ │ │ │ ├── AdSingleChatHome.kt │ │ │ │ ├── FSingleChatHome.kt │ │ │ │ └── SingleChatHomeViewModel.kt │ │ │ ├── models/ │ │ │ │ ├── Contact.kt │ │ │ │ ├── Country.kt │ │ │ │ ├── ModelDeviceDetails.kt │ │ │ │ ├── ModelMobile.kt │ │ │ │ ├── MyImage.kt │ │ │ │ ├── PushMsg.kt │ │ │ │ ├── UserProfile.kt │ │ │ │ └── UserStatus.kt │ │ │ ├── services/ │ │ │ │ ├── GroupUploadWorker.kt │ │ │ │ └── UploadWorker.kt │ │ │ ├── ui/ │ │ │ │ └── activities/ │ │ │ │ ├── ActBase.kt │ │ │ │ ├── ActSplash.kt │ │ │ │ ├── MainActivity.kt │ │ │ │ └── SharedViewModel.kt │ │ │ ├── utils/ │ │ │ │ ├── BindingAdapters.kt │ │ │ │ ├── BottomSheetEvent.kt │ │ │ │ ├── ConnectionChangeEvent.kt │ │ │ │ ├── Constants.kt │ │ │ │ ├── Countries.kt │ │ │ │ ├── DataStorePreference.kt │ │ │ │ ├── DiffCallbackChatUser.kt │ │ │ │ ├── Event.kt │ │ │ │ ├── Events/ │ │ │ │ │ ├── EventAudioMsg.kt │ │ │ │ │ └── EventUpdateRecycleItem.kt │ │ │ │ ├── GroupMsgActionReceiver.kt │ │ │ │ ├── ImageUtils.kt │ │ │ │ ├── ItemClickListener.kt │ │ │ │ ├── LoadState.kt │ │ │ │ ├── LogInFailedState.kt │ │ │ │ ├── LogMessage.kt │ │ │ │ ├── MPreference.kt │ │ │ │ ├── NActionReceiver.kt │ │ │ │ ├── NotificationUtils.kt │ │ │ │ ├── OnSuccessListener.kt │ │ │ │ ├── ScreenState.kt │ │ │ │ ├── UserUtils.kt │ │ │ │ ├── Utils.kt │ │ │ │ ├── Validator.kt │ │ │ │ └── ViewUtils.kt │ │ │ └── views/ │ │ │ ├── CustomEditText.kt │ │ │ ├── CustomProgress.kt │ │ │ ├── CustomProgressView.kt │ │ │ ├── MainNavHostFragment.kt │ │ │ ├── PausableProgressBar.kt │ │ │ ├── PausableScaleAnimation.kt │ │ │ └── StoriesProgressView.kt │ │ └── res/ │ │ ├── anim/ │ │ │ ├── slide_in_right.xml │ │ │ └── slide_out_left.xml │ │ ├── drawable/ │ │ │ ├── ic_arrow_back.xml │ │ │ ├── ic_arrow_down.xml │ │ │ ├── ic_arrow_r8.xml │ │ │ ├── ic_clear.xml │ │ │ ├── ic_close_24px.xml │ │ │ ├── ic_launcher_background.xml │ │ │ ├── ic_menu_24px.xml │ │ │ ├── ic_search.xml │ │ │ ├── selector_menu.xml │ │ │ ├── shape_audio_bg.xml │ │ │ ├── shape_border_line.xml │ │ │ ├── shape_btn_bg.xml │ │ │ ├── shape_circle.xml │ │ │ ├── shape_circle_blue.xml │ │ │ ├── shape_contact_selected.xml │ │ │ ├── shape_divider.xml │ │ │ ├── shape_edit_bg.xml │ │ │ ├── shape_gradient.xml │ │ │ ├── shape_home_bg.xml │ │ │ ├── shape_menu_active.xml │ │ │ ├── shape_menu_non_active.xml │ │ │ ├── shape_msg_bg.xml │ │ │ ├── shape_radius.xml │ │ │ ├── shape_receive_msg.xml │ │ │ ├── shape_receive_msg_corned.xml │ │ │ ├── shape_send_msg.xml │ │ │ ├── shape_send_msg_corned.xml │ │ │ └── shape_unread_count.xml │ │ ├── drawable-v24/ │ │ │ └── ic_launcher_foreground.xml │ │ ├── layout/ │ │ │ ├── act_chat.xml │ │ │ ├── act_splash.xml │ │ │ ├── activity_main.xml │ │ │ ├── activity_main2.xml │ │ │ ├── alert_dialog.xml │ │ │ ├── alert_logout.xml │ │ │ ├── f_add_group_members.xml │ │ │ ├── f_attachment.xml │ │ │ ├── f_contacts.xml │ │ │ ├── f_countries.xml │ │ │ ├── f_create_group.xml │ │ │ ├── f_group_chat.xml │ │ │ ├── f_group_chat_home.xml │ │ │ ├── f_image_src_sheet.xml │ │ │ ├── f_login.xml │ │ │ ├── f_my_profile.xml │ │ │ ├── f_profile.xml │ │ │ ├── f_search.xml │ │ │ ├── f_single_chat.xml │ │ │ ├── f_single_chat_home.xml │ │ │ ├── f_verify.xml │ │ │ ├── load_state_footer.xml │ │ │ ├── pausable_progress.xml │ │ │ ├── progress_dialog.xml │ │ │ ├── row_add_member.xml │ │ │ ├── row_audio_receive.xml │ │ │ ├── row_audio_sent.xml │ │ │ ├── row_chat.xml │ │ │ ├── row_chip.xml │ │ │ ├── row_contact.xml │ │ │ ├── row_country.xml │ │ │ ├── row_group_audio_receive.xml │ │ │ ├── row_group_audio_sent.xml │ │ │ ├── row_group_chat.xml │ │ │ ├── row_group_image_receive.xml │ │ │ ├── row_group_image_sent.xml │ │ │ ├── row_group_sticker_receive.xml │ │ │ ├── row_group_sticker_sent.xml │ │ │ ├── row_group_txt_sent.xml │ │ │ ├── row_grp_txt_receive.xml │ │ │ ├── row_image_receive.xml │ │ │ ├── row_image_sent.xml │ │ │ ├── row_receive_message.xml │ │ │ ├── row_search_contact.xml │ │ │ ├── row_sent_message.xml │ │ │ ├── row_sticker_receive.xml │ │ │ ├── row_sticker_sent.xml │ │ │ ├── view_chat_btm.xml │ │ │ ├── view_chat_toolbar.xml │ │ │ ├── view_group_chat_btm.xml │ │ │ └── view_group_chat_toolbar.xml │ │ ├── menu/ │ │ │ ├── menu_btm_nav.xml │ │ │ ├── menu_contacts.xml │ │ │ ├── menu_option.xml │ │ │ └── menu_search.xml │ │ ├── navigation/ │ │ │ └── nav_graph.xml │ │ ├── raw/ │ │ │ ├── empty_state.json │ │ │ ├── lottie_send.json │ │ │ ├── lottie_tick.json │ │ │ └── lottie_voice.json │ │ ├── values/ │ │ │ ├── attrs.xml │ │ │ ├── colors.xml │ │ │ ├── dimen.xml │ │ │ ├── ids.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── xml/ │ │ ├── network_security_config.xml │ │ └── provider_path.xml │ ├── release/ │ │ └── res/ │ │ └── values/ │ │ └── google_maps_api.xml │ └── test/ │ └── java/ │ └── com/ │ └── gowtham/ │ └── letschat/ │ ├── LiveDataUtilAndroid.kt │ ├── db/ │ │ └── DbRepositoryTest.kt │ ├── fragments/ │ │ └── single_chat_home/ │ │ └── SingleChatHomeViewModelTest.kt │ └── utils/ │ ├── MainCoroutineRule.kt │ └── ValidatorTest.kt ├── build.gradle ├── firebase.json ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat └── settings.gradle
Condensed preview — 254 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (793K chars).
[
{
"path": ".firebaserc",
"chars": 56,
"preview": "{\n \"projects\": {\n \"default\": \"letschat-31c80\"\n }\n}\n"
},
{
"path": ".gitignore",
"chars": 658,
"preview": "# Files for the ART/Dalvik VM\n*.dex\n\n# Java class files\n*.class\n\n# Generated files\nbin/\ngen/\nout/\n\n# Gradle files\n.gradl"
},
{
"path": ".idea/assetWizardSettings.xml",
"chars": 18780,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"WizardSettings\">\n <option name=\"child"
},
{
"path": ".idea/codeStyles/Project.xml",
"chars": 4353,
"preview": "<component name=\"ProjectCodeStyleConfiguration\">\n <code_scheme name=\"Project\" version=\"173\">\n <JetCodeStyleSettings>"
},
{
"path": ".idea/codeStyles/codeStyleConfig.xml",
"chars": 142,
"preview": "<component name=\"ProjectCodeStyleConfiguration\">\n <state>\n <option name=\"USE_PER_PROJECT_SETTINGS\" value=\"true\" />\n "
},
{
"path": ".idea/compiler.xml",
"chars": 169,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"CompilerConfiguration\">\n <bytecodeTar"
},
{
"path": ".idea/inspectionProfiles/Project_Default.xml",
"chars": 283,
"preview": "<component name=\"InspectionProjectProfileManager\">\n <profile version=\"1.0\">\n <option name=\"myName\" value=\"Project De"
},
{
"path": ".idea/jarRepositories.xml",
"chars": 1455,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"RemoteRepositoriesConfiguration\">\n <r"
},
{
"path": ".idea/misc.xml",
"chars": 372,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"ProjectRootManager\" version=\"2\" language"
},
{
"path": ".idea/modules.xml",
"chars": 427,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"ProjectModuleManager\">\n <modules>\n "
},
{
"path": ".idea/navEditor.xml",
"chars": 12843,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"navEditor-manualLayoutAlgorithm2\">\n <"
},
{
"path": ".idea/runConfigurations.xml",
"chars": 686,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"RunConfigurationProducerService\">\n <o"
},
{
"path": ".idea/vcs.xml",
"chars": 180,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"VcsDirectoryMappings\">\n <mapping dire"
},
{
"path": "LICENSE",
"chars": 1076,
"preview": "MIT License\n\nCopyright (c) 2021 Gowtham Balamurugan\n\nPermission is hereby granted, free of charge, to any person obtaini"
},
{
"path": "README.md",
"chars": 3991,
"preview": "# LetsChat\nLetsChat is a Sample Messaging Android application built to demonstrate the use of Modern Android development"
},
{
"path": "app/.gitignore",
"chars": 6,
"preview": "/build"
},
{
"path": "app/build.gradle",
"chars": 5940,
"preview": "apply plugin: 'com.android.application'\napply plugin: 'kotlin-android'\napply plugin: 'kotlin-parcelize'\napply plugin: 'k"
},
{
"path": "app/google-services.json",
"chars": 2002,
"preview": "{\n \"project_info\": {\n \"project_number\": \"713876726773\",\n \"firebase_url\": \"https://letschat-31c80.firebaseio.com\","
},
{
"path": "app/proguard-rules.pro",
"chars": 750,
"preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
},
{
"path": "app/src/androidTest/java/com/gowtham/letschat/FlowUtilAndroidTest.kt",
"chars": 1555,
"preview": "package com.gowtham.letschat\n\nimport androidx.annotation.VisibleForTesting\nimport androidx.lifecycle.LiveData\nimport and"
},
{
"path": "app/src/androidTest/java/com/gowtham/letschat/HiltTestRunner.kt",
"chars": 470,
"preview": "package com.gowtham.letschat\n\nimport android.app.Application\nimport android.content.Context\nimport androidx.test.runner."
},
{
"path": "app/src/androidTest/java/com/gowtham/letschat/LiveDataUtilAndroidTest.kt",
"chars": 1366,
"preview": "package com.gowtham.letschat\n\nimport androidx.annotation.VisibleForTesting\nimport androidx.lifecycle.LiveData\nimport and"
},
{
"path": "app/src/androidTest/java/com/gowtham/letschat/db/daos/ChatUserDaoTest.kt",
"chars": 2322,
"preview": "package com.gowtham.letschat.db.daos\n\nimport androidx.arch.core.executor.testing.InstantTaskExecutorRule\nimport androidx"
},
{
"path": "app/src/androidTest/java/com/gowtham/letschat/db/daos/GroupDaoTest.kt",
"chars": 2707,
"preview": "package com.gowtham.letschat.db.daos\n\nimport androidx.arch.core.executor.testing.InstantTaskExecutorRule\nimport androidx"
},
{
"path": "app/src/androidTest/java/com/gowtham/letschat/db/daos/GroupMessageDaoTest.kt",
"chars": 2839,
"preview": "package com.gowtham.letschat.db.daos\n\nimport androidx.arch.core.executor.testing.InstantTaskExecutorRule\nimport androidx"
},
{
"path": "app/src/androidTest/java/com/gowtham/letschat/db/daos/MessageDaoTest.kt",
"chars": 3978,
"preview": "package com.gowtham.letschat.db.daos\n\nimport androidx.arch.core.executor.testing.InstantTaskExecutorRule\nimport androidx"
},
{
"path": "app/src/androidTest/java/com/gowtham/letschat/di/TestAppModule.kt",
"chars": 693,
"preview": "package com.gowtham.letschat.di\n\nimport android.content.Context\nimport androidx.room.Room\nimport com.gowtham.letschat.db"
},
{
"path": "app/src/main/AndroidManifest.xml",
"chars": 4129,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:to"
},
{
"path": "app/src/main/java/com/gowtham/letschat/FirebasePush.kt",
"chars": 14269,
"preview": "package com.gowtham.letschat\n\nimport android.app.Notification\nimport android.app.PendingIntent\nimport android.content.Co"
},
{
"path": "app/src/main/java/com/gowtham/letschat/MApplication.kt",
"chars": 3120,
"preview": "package com.gowtham.letschat\n\nimport android.content.Context\nimport androidx.hilt.work.HiltWorkerFactory\nimport androidx"
},
{
"path": "app/src/main/java/com/gowtham/letschat/core/ChatHandler.kt",
"chars": 7895,
"preview": "package com.gowtham.letschat.core\n\nimport android.content.Context\nimport androidx.lifecycle.MutableLiveData\nimport com.g"
},
{
"path": "app/src/main/java/com/gowtham/letschat/core/ChatUserProfileListener.kt",
"chars": 4125,
"preview": "package com.gowtham.letschat.core\n\nimport android.content.Context\nimport com.google.firebase.firestore.CollectionReferen"
},
{
"path": "app/src/main/java/com/gowtham/letschat/core/ChatUserUtil.kt",
"chars": 2269,
"preview": "package com.gowtham.letschat.core\n\nimport android.content.Context\nimport com.google.firebase.firestore.CollectionReferen"
},
{
"path": "app/src/main/java/com/gowtham/letschat/core/ContactsQuery.kt",
"chars": 1537,
"preview": "package com.gowtham.letschat.core\n\nimport com.google.firebase.firestore.FirebaseFirestore\nimport com.gowtham.letschat.mo"
},
{
"path": "app/src/main/java/com/gowtham/letschat/core/GroupChatHandler.kt",
"chars": 6828,
"preview": "package com.gowtham.letschat.core\n\nimport android.content.Context\nimport com.google.firebase.firestore.*\nimport com.gowt"
},
{
"path": "app/src/main/java/com/gowtham/letschat/core/GroupMsgSender.kt",
"chars": 1002,
"preview": "package com.gowtham.letschat.core\n\nimport com.google.firebase.firestore.CollectionReference\nimport com.google.firebase.f"
},
{
"path": "app/src/main/java/com/gowtham/letschat/core/GroupMsgStatusUpdater.kt",
"chars": 3344,
"preview": "package com.gowtham.letschat.core\n\nimport com.google.firebase.firestore.CollectionReference\nimport com.google.firebase.f"
},
{
"path": "app/src/main/java/com/gowtham/letschat/core/GroupQuery.kt",
"chars": 2429,
"preview": "package com.gowtham.letschat.core\n\nimport com.google.firebase.firestore.CollectionReference\nimport com.gowtham.letschat."
},
{
"path": "app/src/main/java/com/gowtham/letschat/core/MessageSender.kt",
"chars": 4916,
"preview": "package com.gowtham.letschat.core\n\nimport com.google.firebase.firestore.CollectionReference\nimport com.google.firebase.f"
},
{
"path": "app/src/main/java/com/gowtham/letschat/core/MessageStatusUpdater.kt",
"chars": 3249,
"preview": "package com.gowtham.letschat.core\n\nimport com.google.firebase.firestore.CollectionReference\nimport com.google.firebase.f"
},
{
"path": "app/src/main/java/com/gowtham/letschat/db/ChatUserDatabase.kt",
"chars": 913,
"preview": "package com.gowtham.letschat.db\n\nimport androidx.room.Database\nimport androidx.room.RoomDatabase\nimport androidx.room.Ty"
},
{
"path": "app/src/main/java/com/gowtham/letschat/db/DbRepository.kt",
"chars": 3662,
"preview": "package com.gowtham.letschat.db\n\nimport android.content.Context\nimport com.gowtham.letschat.db.daos.ChatUserDao\nimport c"
},
{
"path": "app/src/main/java/com/gowtham/letschat/db/DefaultDbRepo.kt",
"chars": 661,
"preview": "package com.gowtham.letschat.db\n\nimport androidx.lifecycle.LiveData\nimport com.gowtham.letschat.db.data.ChatUser\nimport "
},
{
"path": "app/src/main/java/com/gowtham/letschat/db/TypeConverter.kt",
"chars": 4326,
"preview": "package com.gowtham.letschat.db\n\nimport androidx.room.TypeConverter\nimport com.gowtham.letschat.db.data.*\nimport com.gow"
},
{
"path": "app/src/main/java/com/gowtham/letschat/db/daos/ChatUserDao.kt",
"chars": 1296,
"preview": "package com.gowtham.letschat.db.daos\n\nimport androidx.lifecycle.LiveData\nimport androidx.room.*\nimport com.gowtham.letsc"
},
{
"path": "app/src/main/java/com/gowtham/letschat/db/daos/GroupDao.kt",
"chars": 1202,
"preview": "package com.gowtham.letschat.db.daos\n\nimport androidx.lifecycle.LiveData\nimport androidx.room.*\nimport com.gowtham.letsc"
},
{
"path": "app/src/main/java/com/gowtham/letschat/db/daos/GroupMessageDao.kt",
"chars": 1390,
"preview": "package com.gowtham.letschat.db.daos\n\nimport androidx.lifecycle.LiveData\nimport androidx.room.Dao\nimport androidx.room.I"
},
{
"path": "app/src/main/java/com/gowtham/letschat/db/daos/MessageDao.kt",
"chars": 1557,
"preview": "package com.gowtham.letschat.db.daos\n\nimport androidx.lifecycle.LiveData\nimport androidx.room.*\nimport com.gowtham.letsc"
},
{
"path": "app/src/main/java/com/gowtham/letschat/db/data/ChatUser.kt",
"chars": 615,
"preview": "package com.gowtham.letschat.db.data\n\nimport android.os.Parcelable\nimport androidx.room.Entity\nimport androidx.room.Prim"
},
{
"path": "app/src/main/java/com/gowtham/letschat/db/data/ChatUserWithMessages.kt",
"chars": 354,
"preview": "package com.gowtham.letschat.db.data\n\nimport android.os.Parcelable\nimport androidx.room.Embedded\nimport androidx.room.Re"
},
{
"path": "app/src/main/java/com/gowtham/letschat/db/data/Group.kt",
"chars": 867,
"preview": "package com.gowtham.letschat.db.data\n\nimport android.os.Parcelable\nimport androidx.room.Entity\nimport androidx.room.Prim"
},
{
"path": "app/src/main/java/com/gowtham/letschat/db/data/GroupMessage.kt",
"chars": 1187,
"preview": "package com.gowtham.letschat.db.data\n\nimport android.os.Parcelable\nimport androidx.room.Entity\nimport androidx.room.Prim"
},
{
"path": "app/src/main/java/com/gowtham/letschat/db/data/GroupWithMessages.kt",
"chars": 352,
"preview": "package com.gowtham.letschat.db.data\n\nimport android.os.Parcelable\nimport androidx.room.Embedded\nimport androidx.room.Re"
},
{
"path": "app/src/main/java/com/gowtham/letschat/db/data/Message.kt",
"chars": 1762,
"preview": "package com.gowtham.letschat.db.data\n\nimport android.os.Parcelable\nimport androidx.room.Entity\nimport androidx.room.Prim"
},
{
"path": "app/src/main/java/com/gowtham/letschat/di/AppModule.kt",
"chars": 2275,
"preview": "package com.gowtham.letschat.di\n\nimport android.content.Context\nimport com.google.firebase.firestore.CollectionReference"
},
{
"path": "app/src/main/java/com/gowtham/letschat/di/DbModule.kt",
"chars": 1110,
"preview": "package com.gowtham.letschat.di\n\nimport android.content.Context\nimport androidx.room.Room\nimport com.gowtham.letschat.db"
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/FAttachment.kt",
"chars": 1706,
"preview": "package com.gowtham.letschat.fragments\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view."
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/FImageSrcSheet.kt",
"chars": 1446,
"preview": "package com.gowtham.letschat.fragments\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view."
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/MainFragmentFactory.kt",
"chars": 625,
"preview": "package com.gowtham.letschat.fragments\n\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.FragmentFacto"
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/add_group_members/AdAddMembers.kt",
"chars": 2164,
"preview": "package com.gowtham.letschat.fragments.add_group_members\n\nimport android.content.Context\nimport android.view.LayoutInfla"
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/add_group_members/AdChip.kt",
"chars": 1573,
"preview": "package com.gowtham.letschat.fragments.add_group_members\n\nimport android.content.Context\nimport android.view.LayoutInfla"
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/add_group_members/AddGroupViewModel.kt",
"chars": 3846,
"preview": "package com.gowtham.letschat.fragments.add_group_members\n\nimport android.content.Context\nimport androidx.lifecycle.LiveD"
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/add_group_members/FAddGroupMembers.kt",
"chars": 8220,
"preview": "package com.gowtham.letschat.fragments.add_group_members\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nim"
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/contacts/AdContact.kt",
"chars": 2155,
"preview": "package com.gowtham.letschat.fragments.contacts\n\nimport android.content.Context\nimport android.view.LayoutInflater\nimpor"
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/contacts/ContactsViewModel.kt",
"chars": 3584,
"preview": "package com.gowtham.letschat.fragments.contacts\n\nimport android.content.Context\nimport androidx.lifecycle.MutableLiveDat"
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/contacts/FContacts.kt",
"chars": 5456,
"preview": "package com.gowtham.letschat.fragments.contacts\n\nimport android.app.Activity\nimport android.os.Bundle\nimport android.vie"
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/countries/AdCountries.kt",
"chars": 2367,
"preview": "package com.gowtham.letschat.fragments.countries\n\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimpor"
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/countries/FCountries.kt",
"chars": 3086,
"preview": "package com.gowtham.letschat.fragments.countries\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport and"
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/create_group/CreateGroupViewModel.kt",
"chars": 4752,
"preview": "package com.gowtham.letschat.fragments.create_group\n\nimport android.content.Context\nimport android.net.Uri\nimport androi"
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/create_group/FCreateGroup.kt",
"chars": 4988,
"preview": "package com.gowtham.letschat.fragments.create_group\n\nimport android.content.Intent\nimport android.net.Uri\nimport android"
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/group_chat/AdGroupChat.kt",
"chars": 13983,
"preview": "package com.gowtham.letschat.fragments.group_chat\n\nimport android.content.Context\nimport android.media.MediaPlayer\nimpor"
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/group_chat/FGroupChat.kt",
"chars": 15652,
"preview": "package com.gowtham.letschat.fragments.group_chat\n\nimport android.Manifest\nimport android.animation.Animator\nimport andr"
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/group_chat/GroupChatViewModel.kt",
"chars": 8500,
"preview": "package com.gowtham.letschat.fragments.group_chat\n\nimport android.content.Context\nimport android.os.Handler\nimport andro"
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/group_chat_home/AdGroupChatHome.kt",
"chars": 3070,
"preview": "package com.gowtham.letschat.fragments.group_chat_home\n\nimport android.content.Context\nimport android.view.LayoutInflate"
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/group_chat_home/FGroupChatHome.kt",
"chars": 4711,
"preview": "package com.gowtham.letschat.fragments.group_chat_home\n\nimport android.app.Activity\nimport android.os.Bundle\nimport andr"
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/group_chat_home/GroupChatHomeViewModel.kt",
"chars": 685,
"preview": "package com.gowtham.letschat.fragments.group_chat_home\n\nimport androidx.lifecycle.ViewModel\nimport com.google.firebase.f"
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/login/FLogin.kt",
"chars": 5844,
"preview": "package com.gowtham.letschat.fragments.login\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.tel"
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/login/FVerify.kt",
"chars": 7296,
"preview": "package com.gowtham.letschat.fragments.login\n\nimport android.os.Bundle\nimport android.text.Editable\nimport android.text."
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/login/LogInViewModel.kt",
"chars": 6677,
"preview": "package com.gowtham.letschat.fragments.login\n\nimport android.content.Context\nimport android.os.CountDownTimer\nimport and"
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/login/LoginRepo.kt",
"chars": 4151,
"preview": "package com.gowtham.letschat.fragments.login\n\nimport android.content.Context\nimport android.os.Handler\nimport android.os"
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/myprofile/FMyProfile.kt",
"chars": 4502,
"preview": "package com.gowtham.letschat.fragments.myprofile\n\nimport android.app.Activity\nimport android.app.Dialog\nimport android.c"
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/myprofile/FMyProfileViewModel.kt",
"chars": 3764,
"preview": "package com.gowtham.letschat.fragments.myprofile\n\nimport android.content.Context\nimport android.net.Uri\nimport androidx."
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/profile/FProfile.kt",
"chars": 4746,
"preview": "package com.gowtham.letschat.fragments.profile\n\nimport android.app.Activity\nimport android.content.Intent\nimport android"
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/profile/ProfileViewModel.kt",
"chars": 3933,
"preview": "package com.gowtham.letschat.fragments.profile\n\nimport android.content.Context\nimport android.net.Uri\nimport androidx.li"
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/search/FSearch.kt",
"chars": 5263,
"preview": "package com.gowtham.letschat.fragments.search\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport androi"
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/search/FSearchViewModel.kt",
"chars": 1963,
"preview": "package com.gowtham.letschat.fragments.search\n\nimport android.os.Handler\nimport android.os.Looper\nimport androidx.lifecy"
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/search/SearchRepo.kt",
"chars": 1929,
"preview": "package com.gowtham.letschat.fragments.search\n\nimport androidx.lifecycle.MutableLiveData\nimport com.google.firebase.fire"
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/single_chat/AdChat.kt",
"chars": 13704,
"preview": "package com.gowtham.letschat.fragments.single_chat\n\nimport android.content.Context\nimport android.media.MediaPlayer\nimpo"
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/single_chat/FSingleChat.kt",
"chars": 17479,
"preview": "package com.gowtham.letschat.fragments.single_chat\n\nimport android.Manifest\nimport android.animation.Animator\nimport and"
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/single_chat/SingleChatViewModel.kt",
"chars": 9229,
"preview": "package com.gowtham.letschat.fragments.single_chat\n\nimport android.content.Context\nimport android.os.Handler\nimport andr"
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/single_chat_home/AdSingleChatHome.kt",
"chars": 2922,
"preview": "package com.gowtham.letschat.fragments.single_chat_home\n\nimport android.content.Context\nimport android.view.LayoutInflat"
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/single_chat_home/FSingleChatHome.kt",
"chars": 5478,
"preview": "package com.gowtham.letschat.fragments.single_chat_home\n\nimport android.app.Activity\nimport android.os.Bundle\nimport and"
},
{
"path": "app/src/main/java/com/gowtham/letschat/fragments/single_chat_home/SingleChatHomeViewModel.kt",
"chars": 879,
"preview": "package com.gowtham.letschat.fragments.single_chat_home\n\nimport androidx.lifecycle.MutableLiveData\nimport androidx.lifec"
},
{
"path": "app/src/main/java/com/gowtham/letschat/models/Contact.kt",
"chars": 93,
"preview": "package com.gowtham.letschat.models\n\n\ndata class Contact(var name: String,var mobile: String)"
},
{
"path": "app/src/main/java/com/gowtham/letschat/models/Country.kt",
"chars": 345,
"preview": "package com.gowtham.letschat.models\n\nimport android.os.Parcel\nimport android.os.Parcelable\nimport kotlinx.parcelize.Parc"
},
{
"path": "app/src/main/java/com/gowtham/letschat/models/ModelDeviceDetails.kt",
"chars": 598,
"preview": "package com.gowtham.letschat.models\n\nimport android.os.Parcel\nimport android.os.Parcelable\nimport kotlinx.parcelize.Parc"
},
{
"path": "app/src/main/java/com/gowtham/letschat/models/ModelMobile.kt",
"chars": 290,
"preview": "package com.gowtham.letschat.models\n\nimport android.os.Parcelable\nimport kotlinx.parcelize.Parceler\nimport kotlinx.parce"
},
{
"path": "app/src/main/java/com/gowtham/letschat/models/MyImage.kt",
"chars": 72,
"preview": "package com.gowtham.letschat.models\n\ndata class MyImage(val url: String)"
},
{
"path": "app/src/main/java/com/gowtham/letschat/models/PushMsg.kt",
"chars": 344,
"preview": "package com.gowtham.letschat.models\n\nimport com.google.firebase.firestore.IgnoreExtraProperties\nimport kotlinx.serializa"
},
{
"path": "app/src/main/java/com/gowtham/letschat/models/UserProfile.kt",
"chars": 869,
"preview": "package com.gowtham.letschat.models\n\nimport android.os.Parcelable\nimport com.google.firebase.firestore.IgnoreExtraProper"
},
{
"path": "app/src/main/java/com/gowtham/letschat/models/UserStatus.kt",
"chars": 205,
"preview": "package com.gowtham.letschat.models\n\ndata class UserStatus (val status: String=\"offline\",val last_seen: Long=0,\n "
},
{
"path": "app/src/main/java/com/gowtham/letschat/services/GroupUploadWorker.kt",
"chars": 4535,
"preview": "package com.gowtham.letschat.services\n\nimport android.content.Context\nimport android.net.Uri\nimport androidx.hilt.work.H"
},
{
"path": "app/src/main/java/com/gowtham/letschat/services/UploadWorker.kt",
"chars": 4384,
"preview": "package com.gowtham.letschat.services\n\nimport android.content.Context\nimport android.net.Uri\nimport androidx.hilt.work.H"
},
{
"path": "app/src/main/java/com/gowtham/letschat/ui/activities/ActBase.kt",
"chars": 3931,
"preview": "package com.gowtham.letschat.ui.activities\n\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimpo"
},
{
"path": "app/src/main/java/com/gowtham/letschat/ui/activities/ActSplash.kt",
"chars": 1248,
"preview": "package com.gowtham.letschat.ui.activities\n\nimport android.content.Intent\nimport android.os.Bundle\nimport androidx.appco"
},
{
"path": "app/src/main/java/com/gowtham/letschat/ui/activities/MainActivity.kt",
"chars": 13195,
"preview": "package com.gowtham.letschat.ui.activities\n\nimport android.content.Intent\nimport android.os.Bundle\nimport android.os.Han"
},
{
"path": "app/src/main/java/com/gowtham/letschat/ui/activities/SharedViewModel.kt",
"chars": 1829,
"preview": "package com.gowtham.letschat.ui.activities\n\nimport androidx.lifecycle.LiveData\nimport androidx.lifecycle.MutableLiveData"
},
{
"path": "app/src/main/java/com/gowtham/letschat/utils/BindingAdapters.kt",
"chars": 11943,
"preview": "package com.gowtham.letschat.utils\n\nimport android.graphics.Typeface\nimport android.graphics.drawable.Drawable\nimport an"
},
{
"path": "app/src/main/java/com/gowtham/letschat/utils/BottomSheetEvent.kt",
"chars": 82,
"preview": "package com.gowtham.letschat.utils\n\ndata class BottomSheetEvent(var position: Int)"
},
{
"path": "app/src/main/java/com/gowtham/letschat/utils/ConnectionChangeEvent.kt",
"chars": 85,
"preview": "package com.gowtham.letschat.utils\n\nclass ConnectionChangeEvent(val message: String)\n"
},
{
"path": "app/src/main/java/com/gowtham/letschat/utils/Constants.kt",
"chars": 929,
"preview": "package com.gowtham.letschat.utils\n\nobject Constants {\n\n const val VIEW_TYPE=\"view_type\"\n\n const val VIEW_CT=43\n\n "
},
{
"path": "app/src/main/java/com/gowtham/letschat/utils/Countries.kt",
"chars": 13440,
"preview": "package com.gowtham.letschat.utils\n\nimport com.gowtham.letschat.models.Country\n\nobject Countries {\n\n private val COUN"
},
{
"path": "app/src/main/java/com/gowtham/letschat/utils/DataStorePreference.kt",
"chars": 1535,
"preview": "package com.gowtham.letschat.utils\n\nimport android.content.Context\nimport androidx.datastore.core.DataStore\nimport andro"
},
{
"path": "app/src/main/java/com/gowtham/letschat/utils/DiffCallbackChatUser.kt",
"chars": 639,
"preview": "package com.gowtham.letschat.utils\n\nimport androidx.recyclerview.widget.DiffUtil\nimport com.gowtham.letschat.db.data.Cha"
},
{
"path": "app/src/main/java/com/gowtham/letschat/utils/Event.kt",
"chars": 543,
"preview": "package com.gowtham.letschat.utils\n\nopen class Event<out T>(private val content: T) {\n\n var hasBeenHandled = false\n "
},
{
"path": "app/src/main/java/com/gowtham/letschat/utils/Events/EventAudioMsg.kt",
"chars": 86,
"preview": "package com.gowtham.letschat.utils.Events\n\nclass EventAudioMsg(val isPlaying: Boolean)"
},
{
"path": "app/src/main/java/com/gowtham/letschat/utils/Events/EventUpdateRecycleItem.kt",
"chars": 97,
"preview": "package com.gowtham.letschat.utils.Events\n\nclass EventUpdateRecycleItem(val adapterPosition: Int)"
},
{
"path": "app/src/main/java/com/gowtham/letschat/utils/GroupMsgActionReceiver.kt",
"chars": 4795,
"preview": "package com.gowtham.letschat.utils\n\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport andro"
},
{
"path": "app/src/main/java/com/gowtham/letschat/utils/ImageUtils.kt",
"chars": 9230,
"preview": "package com.gowtham.letschat.utils\n\nimport android.Manifest\nimport android.app.Activity\nimport android.content.ContentRe"
},
{
"path": "app/src/main/java/com/gowtham/letschat/utils/ItemClickListener.kt",
"chars": 139,
"preview": "package com.gowtham.letschat.utils\n\nimport android.view.View\n\ninterface ItemClickListener {\n fun onItemClicked(v: Vie"
},
{
"path": "app/src/main/java/com/gowtham/letschat/utils/LoadState.kt",
"chars": 494,
"preview": "package com.gowtham.letschat.utils\n\nsealed class LoadState {\n\n class OnSuccess(val data: Any?=null): LoadState(){\n "
},
{
"path": "app/src/main/java/com/gowtham/letschat/utils/LogInFailedState.kt",
"chars": 96,
"preview": "package com.gowtham.letschat.utils\n\nenum class LogInFailedState {\n Verification,\n SignIn\n}"
},
{
"path": "app/src/main/java/com/gowtham/letschat/utils/LogMessage.kt",
"chars": 339,
"preview": "package com.gowtham.letschat.utils\n\nimport android.util.Log\nimport com.gowtham.letschat.BuildConfig.DEBUG\n\nobject LogMes"
},
{
"path": "app/src/main/java/com/gowtham/letschat/utils/MPreference.kt",
"chars": 3426,
"preview": "package com.gowtham.letschat.utils\n\nimport android.content.Context\nimport android.content.SharedPreferences\nimport com.g"
},
{
"path": "app/src/main/java/com/gowtham/letschat/utils/NActionReceiver.kt",
"chars": 5261,
"preview": "package com.gowtham.letschat.utils\n\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport andro"
},
{
"path": "app/src/main/java/com/gowtham/letschat/utils/NotificationUtils.kt",
"chars": 7212,
"preview": "package com.gowtham.letschat.utils\n\nimport android.app.Notification\nimport android.app.PendingIntent\nimport android.cont"
},
{
"path": "app/src/main/java/com/gowtham/letschat/utils/OnSuccessListener.kt",
"chars": 118,
"preview": "package com.gowtham.letschat.utils\n\ninterface OnSuccessListener {\n fun onResult(success: Boolean,data: Any?=null)\n}"
},
{
"path": "app/src/main/java/com/gowtham/letschat/utils/ScreenState.kt",
"chars": 337,
"preview": "package com.gowtham.letschat.utils\n\nsealed class ScreenState {\n\n object IdleState : ScreenState(){\n override f"
},
{
"path": "app/src/main/java/com/gowtham/letschat/utils/UserUtils.kt",
"chars": 15118,
"preview": "package com.gowtham.letschat.utils\n\nimport android.annotation.SuppressLint\nimport android.app.Activity\nimport android.co"
},
{
"path": "app/src/main/java/com/gowtham/letschat/utils/Utils.kt",
"chars": 10585,
"preview": "package com.gowtham.letschat.utils\n\nimport android.annotation.SuppressLint\nimport android.app.*\nimport android.content.C"
},
{
"path": "app/src/main/java/com/gowtham/letschat/utils/Validator.kt",
"chars": 675,
"preview": "package com.gowtham.letschat.utils\n\nimport com.google.i18n.phonenumbers.PhoneNumberUtil\nimport com.google.i18n.phonenumb"
},
{
"path": "app/src/main/java/com/gowtham/letschat/utils/ViewUtils.kt",
"chars": 2870,
"preview": "package com.gowtham.letschat.utils\n\nimport android.app.Activity\nimport android.content.Context\nimport android.os.Handler"
},
{
"path": "app/src/main/java/com/gowtham/letschat/views/CustomEditText.kt",
"chars": 2890,
"preview": "package com.gowtham.letschat.views\n\nimport android.content.Context\nimport android.os.Build\nimport android.os.Bundle\nimpo"
},
{
"path": "app/src/main/java/com/gowtham/letschat/views/CustomProgress.kt",
"chars": 1676,
"preview": "package com.gowtham.letschat.views\n\nimport android.content.Context\nimport android.graphics.BlendMode\nimport android.grap"
},
{
"path": "app/src/main/java/com/gowtham/letschat/views/CustomProgressView.kt",
"chars": 842,
"preview": "package com.gowtham.letschat.views\n\nimport android.app.Dialog\nimport android.content.Context\nimport android.graphics.Col"
},
{
"path": "app/src/main/java/com/gowtham/letschat/views/MainNavHostFragment.kt",
"chars": 532,
"preview": "package com.gowtham.letschat.views\n\nimport android.content.Context\nimport androidx.navigation.fragment.NavHostFragment\ni"
},
{
"path": "app/src/main/java/com/gowtham/letschat/views/PausableProgressBar.kt",
"chars": 4296,
"preview": "package com.gowtham.letschat.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view."
},
{
"path": "app/src/main/java/com/gowtham/letschat/views/PausableScaleAnimation.kt",
"chars": 1077,
"preview": "package com.gowtham.letschat.views\n\nimport android.view.animation.ScaleAnimation\nimport android.view.animation.Transform"
},
{
"path": "app/src/main/java/com/gowtham/letschat/views/StoriesProgressView.kt",
"chars": 5432,
"preview": "package com.gowtham.letschat.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view."
},
{
"path": "app/src/main/res/anim/slide_in_right.xml",
"chars": 311,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n <translate "
},
{
"path": "app/src/main/res/anim/slide_out_left.xml",
"chars": 312,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n <translate "
},
{
"path": "app/src/main/res/drawable/ic_arrow_back.xml",
"chars": 329,
"preview": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n andr"
},
{
"path": "app/src/main/res/drawable/ic_arrow_down.xml",
"chars": 318,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "app/src/main/res/drawable/ic_arrow_r8.xml",
"chars": 337,
"preview": "<vector android:height=\"30dp\" android:tint=\"#FFFFFF\"\n android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n andr"
},
{
"path": "app/src/main/res/drawable/ic_clear.xml",
"chars": 391,
"preview": "<vector android:height=\"40dp\" android:tint=\"@color/colorBlack\"\n android:viewportHeight=\"24\" android:viewportWidth=\"24"
},
{
"path": "app/src/main/res/drawable/ic_close_24px.xml",
"chars": 360,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "app/src/main/res/drawable/ic_launcher_background.xml",
"chars": 5606,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:wi"
},
{
"path": "app/src/main/res/drawable/ic_menu_24px.xml",
"chars": 316,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "app/src/main/res/drawable/ic_search.xml",
"chars": 533,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\""
},
{
"path": "app/src/main/res/drawable/selector_menu.xml",
"chars": 365,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <item a"
},
{
"path": "app/src/main/res/drawable/shape_audio_bg.xml",
"chars": 198,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n<corners andro"
},
{
"path": "app/src/main/res/drawable/shape_border_line.xml",
"chars": 309,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:sha"
},
{
"path": "app/src/main/res/drawable/shape_btn_bg.xml",
"chars": 200,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <corners a"
},
{
"path": "app/src/main/res/drawable/shape_circle.xml",
"chars": 191,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:sha"
},
{
"path": "app/src/main/res/drawable/shape_circle_blue.xml",
"chars": 189,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:sha"
},
{
"path": "app/src/main/res/drawable/shape_contact_selected.xml",
"chars": 195,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:sha"
},
{
"path": "app/src/main/res/drawable/shape_divider.xml",
"chars": 257,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android"
},
{
"path": "app/src/main/res/drawable/shape_edit_bg.xml",
"chars": 284,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n <stroke a"
},
{
"path": "app/src/main/res/drawable/shape_gradient.xml",
"chars": 233,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:sha"
},
{
"path": "app/src/main/res/drawable/shape_home_bg.xml",
"chars": 245,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n <corners "
},
{
"path": "app/src/main/res/drawable/shape_menu_active.xml",
"chars": 189,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <corners a"
},
{
"path": "app/src/main/res/drawable/shape_menu_non_active.xml",
"chars": 206,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <corners a"
},
{
"path": "app/src/main/res/drawable/shape_msg_bg.xml",
"chars": 361,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:sha"
},
{
"path": "app/src/main/res/drawable/shape_radius.xml",
"chars": 236,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape\n xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n <corn"
},
{
"path": "app/src/main/res/drawable/shape_receive_msg.xml",
"chars": 323,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:sha"
},
{
"path": "app/src/main/res/drawable/shape_receive_msg_corned.xml",
"chars": 360,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:sha"
},
{
"path": "app/src/main/res/drawable/shape_send_msg.xml",
"chars": 322,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:sha"
},
{
"path": "app/src/main/res/drawable/shape_send_msg_corned.xml",
"chars": 360,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:sha"
},
{
"path": "app/src/main/res/drawable/shape_unread_count.xml",
"chars": 360,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:sha"
},
{
"path": "app/src/main/res/drawable-v24/ic_launcher_foreground.xml",
"chars": 1702,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:aapt=\"http://schemas.android.com/aapt\"\n "
},
{
"path": "app/src/main/res/layout/act_chat.xml",
"chars": 790,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:app="
},
{
"path": "app/src/main/res/layout/act_splash.xml",
"chars": 576,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android=\"http://schema"
},
{
"path": "app/src/main/res/layout/activity_main.xml",
"chars": 2252,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:tools=\"http://schemas.android.com/tools\"\n xmlns:android=\"http://"
},
{
"path": "app/src/main/res/layout/activity_main2.xml",
"chars": 2513,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:tools=\"http://schemas.android.com/tools\"\n xmlns:android=\"http://"
},
{
"path": "app/src/main/res/layout/alert_dialog.xml",
"chars": 876,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n xmlns:android=\"http://schemas.android.com/apk/res/android\"\n "
},
{
"path": "app/src/main/res/layout/alert_logout.xml",
"chars": 1843,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n <data>\n\n"
},
{
"path": "app/src/main/res/layout/f_add_group_members.xml",
"chars": 2748,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:app="
},
{
"path": "app/src/main/res/layout/f_attachment.xml",
"chars": 1260,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:app="
},
{
"path": "app/src/main/res/layout/f_contacts.xml",
"chars": 2899,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:app="
},
{
"path": "app/src/main/res/layout/f_countries.xml",
"chars": 1288,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:app="
},
{
"path": "app/src/main/res/layout/f_create_group.xml",
"chars": 3746,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:app="
},
{
"path": "app/src/main/res/layout/f_group_chat.xml",
"chars": 1966,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:bind"
},
{
"path": "app/src/main/res/layout/f_group_chat_home.xml",
"chars": 766,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:tool"
},
{
"path": "app/src/main/res/layout/f_image_src_sheet.xml",
"chars": 1617,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:tools=\"http://schemas.android.com/tools\"\n xmlns:app=\"http://sche"
},
{
"path": "app/src/main/res/layout/f_login.xml",
"chars": 3720,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:app="
},
{
"path": "app/src/main/res/layout/f_my_profile.xml",
"chars": 4342,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:tool"
},
{
"path": "app/src/main/res/layout/f_profile.xml",
"chars": 2588,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:app="
},
{
"path": "app/src/main/res/layout/f_search.xml",
"chars": 1158,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:tool"
},
{
"path": "app/src/main/res/layout/f_single_chat.xml",
"chars": 1952,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:bind"
},
{
"path": "app/src/main/res/layout/f_single_chat_home.xml",
"chars": 901,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:app="
},
{
"path": "app/src/main/res/layout/f_verify.xml",
"chars": 5168,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:tool"
},
{
"path": "app/src/main/res/layout/load_state_footer.xml",
"chars": 833,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n andr"
},
{
"path": "app/src/main/res/layout/pausable_progress.xml",
"chars": 978,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n andro"
},
{
"path": "app/src/main/res/layout/progress_dialog.xml",
"chars": 640,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n an"
},
{
"path": "app/src/main/res/layout/row_add_member.xml",
"chars": 2903,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:app="
},
{
"path": "app/src/main/res/layout/row_audio_receive.xml",
"chars": 2407,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:tool"
},
{
"path": "app/src/main/res/layout/row_audio_sent.xml",
"chars": 3071,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:tool"
},
{
"path": "app/src/main/res/layout/row_chat.xml",
"chars": 3599,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:tool"
},
{
"path": "app/src/main/res/layout/row_chip.xml",
"chars": 1361,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:app="
},
{
"path": "app/src/main/res/layout/row_contact.xml",
"chars": 2408,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:app="
}
]
// ... and 54 more files (download for full content)
About this extraction
This page contains the full source code of the a914-gowtham/LetsChat GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 254 files (16.7 MB), approximately 188.5k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.