Full Code of a914-gowtham/LetsChat for AI

master 8d8c2ffad6c7 cached
254 files
16.7 MB
188.5k tokens
1 requests
Download .txt
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 👇***

[![LetsChat App](https://img.shields.io/badge/LetsChat-APK-blue.svg?style=for-the-badge&logo=android)](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])
    }
}

==============================================
Download .txt
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.

Copied to clipboard!