├── .gitmodules ├── common ├── .gitignore ├── consumer-rules.pro ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ ├── res │ │ ├── values │ │ │ └── styles.xml │ │ └── layout │ │ │ └── include_base_appbar.xml │ │ └── kotlin │ │ └── com │ │ └── mobiledevpro │ │ └── common │ │ └── ui │ │ ├── base │ │ ├── ActivitySettings.kt │ │ ├── FragmentSettings.kt │ │ ├── BaseActivityInterface.kt │ │ └── BaseViewModel.kt │ │ ├── coroutines │ │ ├── BaseUseCase.kt │ │ ├── BaseCoroutinesUseCase.kt │ │ ├── BaseCoroutinesFLowUseCase.kt │ │ └── ResultExt.kt │ │ ├── extension │ │ └── LifecycleOwnerExtensions.kt │ │ ├── livedata │ │ ├── Event.kt │ │ └── SingleLiveData.kt │ │ └── lifecycle │ │ └── RuntimePermissionObserver.kt ├── proguard-rules.pro └── build.gradle ├── app ├── .gitignore ├── src │ ├── main │ │ ├── ic_launcher-playstore.png │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── mobiledevpro │ │ │ │ └── app │ │ │ │ ├── helper │ │ │ │ ├── TypeConverter.kt │ │ │ │ ├── ResourcesProvider.kt │ │ │ │ └── ImplResourcesProvider.kt │ │ │ │ ├── App.kt │ │ │ │ ├── di │ │ │ │ └── Module.kt │ │ │ │ └── ui │ │ │ │ └── mainscreen │ │ │ │ └── view │ │ │ │ └── MainActivity.kt │ │ ├── res │ │ │ ├── layouts │ │ │ │ ├── activity │ │ │ │ │ └── layout │ │ │ │ │ │ └── activity_main.xml │ │ │ │ └── include │ │ │ │ │ └── layout │ │ │ │ │ └── include_appbar_custom.xml │ │ │ └── values │ │ │ │ └── strings.xml │ │ └── AndroidManifest.xml │ └── test │ │ └── java │ │ └── com │ │ └── mobiledevpro │ │ └── app │ │ └── CheckKoinModulesTest.kt ├── google-services.json ├── proguard-rules.pro └── build.gradle ├── core ├── ui │ ├── .gitignore │ ├── consumer-rules.pro │ ├── src │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ └── res │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_round.png │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_round.png │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_round.png │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_round.png │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_round.png │ │ │ └── ic_launcher_foreground.png │ │ │ ├── values │ │ │ ├── theme_attrs.xml │ │ │ ├── style_image_button.xml │ │ │ ├── dimens.xml │ │ │ ├── style_toolbar.xml │ │ │ ├── style_textview.xml │ │ │ ├── style_button.xml │ │ │ ├── style_edittext.xml │ │ │ ├── colors_base.xml │ │ │ ├── colors_material3.xml │ │ │ └── themes.xml │ │ │ ├── drawable │ │ │ ├── background_edittext_cursor.xml │ │ │ ├── background_window_dark.xml │ │ │ ├── background_window_light.xml │ │ │ ├── ic_send_white_24dp.xml │ │ │ ├── background_layout_top_rounding.xml │ │ │ ├── background_layout_bottom_rounding.xml │ │ │ ├── background_window_default.xml │ │ │ ├── ic_back_arrow_light_24dp.xml │ │ │ ├── ic_list_dark_24dp.xml │ │ │ ├── ic_profile_avatar_dark_24dp.xml │ │ │ ├── background_profile_avatar.xml │ │ │ ├── ic_login_24dp.xml │ │ │ ├── background_edittext_login.xml │ │ │ ├── background_edittext_message.xml │ │ │ ├── background_button_common.xml │ │ │ ├── ic_settings_dark_24dp.xml │ │ │ ├── ic_facebook_circle_white_56dp.xml │ │ │ └── ic_google_circle_white_56dp.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ └── values-night │ │ │ └── themes.xml │ ├── build.gradle │ └── proguard-rules.pro ├── utils │ ├── .gitignore │ ├── build.gradle │ └── src │ │ └── main │ │ └── kotlin │ │ └── com │ │ └── mobiledevpro │ │ └── utils │ │ ├── Constant.kt │ │ ├── Error.kt │ │ └── TimeUtil.kt ├── database │ ├── .gitignore │ ├── consumer-rules.pro │ ├── src │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ └── kotlin │ │ │ │ └── com │ │ │ │ └── mobiledevpro │ │ │ │ └── database │ │ │ │ ├── di │ │ │ │ └── Module.kt │ │ │ │ ├── entity │ │ │ │ └── UserEntity.kt │ │ │ │ ├── dao │ │ │ │ └── BaseDao.kt │ │ │ │ └── AppDatabase.kt │ │ └── androidTest │ │ │ └── java │ │ │ └── com │ │ │ └── mobiledevpro │ │ │ └── database │ │ │ ├── ExampleInstrumentedTest.kt │ │ │ └── RoomMigrationTest.java │ ├── proguard-rules.pro │ ├── build.gradle │ └── schemas │ │ └── com.mobiledevpro.database.AppDatabase │ │ └── 1.json └── navigation │ ├── .gitignore │ ├── consumer-rules.pro │ ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ ├── res │ │ ├── transition │ │ │ ├── fade.xml │ │ │ ├── slide_left.xml │ │ │ └── slide_right.xml │ │ ├── values │ │ │ └── nav_ids.xml │ │ └── navigation │ │ │ └── nav_main.xml │ │ └── kotlin │ │ └── com │ │ └── mobiledevpro │ │ └── navigation │ │ ├── Navigation.kt │ │ └── ext │ │ └── NavigationExt.kt │ ├── build.gradle │ └── proguard-rules.pro ├── feature ├── chat_core │ ├── .gitignore │ ├── build.gradle │ └── src │ │ └── main │ │ ├── kotlin │ │ └── com │ │ │ └── mobiledevpro │ │ │ └── chat │ │ │ └── core │ │ │ ├── data │ │ │ └── model │ │ │ │ ├── ChatUserData.kt │ │ │ │ └── ChatMessageData.kt │ │ │ ├── view │ │ │ ├── recycler │ │ │ │ ├── RecyclerViewHandler.kt │ │ │ │ ├── RecyclerItem.kt │ │ │ │ └── RecyclerViewAdapter.kt │ │ │ ├── mapper │ │ │ │ └── RecyclerMapper.kt │ │ │ └── extension │ │ │ │ └── ImageViewExtension.kt │ │ │ ├── domain │ │ │ └── model │ │ │ │ ├── ChatUser.kt │ │ │ │ └── ChatMessage.kt │ │ │ └── mapper │ │ │ ├── ChatUserMapper.kt │ │ │ └── ChatMessageMapper.kt │ │ ├── AndroidManifest.xml │ │ └── res │ │ ├── drawable │ │ ├── background_message_sent.xml │ │ ├── background_message_received.xml │ │ └── ic_message_avatar_white_24dp.xml │ │ └── layout │ │ ├── item_chat_message_send.xml │ │ └── item_chat_message_received.xml ├── chat_main │ ├── .gitignore │ ├── build.gradle │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── res │ │ ├── menu │ │ │ └── menu_chat_public.xml │ │ ├── navigation │ │ │ └── nav_chat_main.xml │ │ └── layout │ │ │ └── fragment_chat_public.xml │ │ └── kotlin │ │ └── com │ │ └── mobiledevpro │ │ └── chat │ │ └── main │ │ ├── data │ │ ├── repository │ │ │ ├── ChatPublicRepository.kt │ │ │ └── ImplChatPublicRepository.kt │ │ └── local │ │ │ ├── ChatPublicLocalSource.kt │ │ │ └── ImplChatPublicLocalSource.kt │ │ ├── domain │ │ ├── interactor │ │ │ ├── ChatPublicInteractor.kt │ │ │ └── ImplChatPublicInteractor.kt │ │ └── usecase │ │ │ ├── GetCurrentUserUseCase.kt │ │ │ └── GetPublicChatMessagesUseCase.kt │ │ ├── view │ │ ├── RecyclerViewExtension.kt │ │ ├── ChatPublicViewModel.kt │ │ └── ChatPublicFragment.kt │ │ └── di │ │ └── Module.kt └── profile_settings │ ├── .gitignore │ ├── src │ └── main │ │ ├── kotlin │ │ └── com │ │ │ └── mobiledevpro │ │ │ └── profile │ │ │ └── settings │ │ │ ├── data │ │ │ └── local_and_remote_source │ │ │ ├── domain │ │ │ └── interactor_and_usecases │ │ │ ├── di │ │ │ └── Module.kt │ │ │ └── view │ │ │ ├── ProfileSettingsViewModel.kt │ │ │ └── ProfileSettingsFragment.kt │ │ ├── AndroidManifest.xml │ │ └── res │ │ └── navigation │ │ └── nav_profile_settings.xml │ └── build.gradle ├── github_social_preview.png ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── doc └── MVVM-Kotlin-Modularization - Frame.jpg ├── .gitignore ├── common-kotlin-library.gradle ├── settings.gradle.kts ├── .github └── FUNDING.yml ├── ci-deploy-dropbox.sh ├── common-android-library.gradle ├── common-dynamic-feature.gradle ├── gradle.properties ├── install-bundles-on-device.sh ├── gradlew.bat ├── .circleci └── config.yml ├── README.md └── gradlew /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /common/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /core/ui/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/ui/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/utils/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/database/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/database/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/navigation/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/navigation/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /feature/chat_core/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /feature/chat_main/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /feature/profile_settings/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /common/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | # Rules for this library -------------------------------------------------------------------------------- /core/utils/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "$rootDir/common-kotlin-library.gradle" 2 | -------------------------------------------------------------------------------- /common/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /core/ui/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /core/database/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /core/navigation/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /feature/profile_settings/src/main/kotlin/com/mobiledevpro/profile/settings/data/local_and_remote_source: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /feature/profile_settings/src/main/kotlin/com/mobiledevpro/profile/settings/domain/interactor_and_usecases: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /github_social_preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobiledevpro/Android-Kotlin-MVVM-Template/HEAD/github_social_preview.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobiledevpro/Android-Kotlin-MVVM-Template/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobiledevpro/Android-Kotlin-MVVM-Template/HEAD/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /doc/MVVM-Kotlin-Modularization - Frame.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobiledevpro/Android-Kotlin-MVVM-Template/HEAD/doc/MVVM-Kotlin-Modularization - Frame.jpg -------------------------------------------------------------------------------- /core/ui/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobiledevpro/Android-Kotlin-MVVM-Template/HEAD/core/ui/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /core/ui/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobiledevpro/Android-Kotlin-MVVM-Template/HEAD/core/ui/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /core/ui/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "$rootDir/common-android-library.gradle" 2 | 3 | android.namespace 'com.mobiledevpro.ui' 4 | 5 | dependencies { 6 | api deps.material 7 | } -------------------------------------------------------------------------------- /core/ui/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobiledevpro/Android-Kotlin-MVVM-Template/HEAD/core/ui/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /core/ui/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobiledevpro/Android-Kotlin-MVVM-Template/HEAD/core/ui/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobiledevpro/Android-Kotlin-MVVM-Template/HEAD/core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | *.properties 3 | .DS_Store 4 | /build 5 | /captures 6 | .idea 7 | *.txt 8 | *.apk 9 | *.iml 10 | wiki 11 | !gradle-wrapper.properties 12 | !gradle.properties -------------------------------------------------------------------------------- /core/ui/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobiledevpro/Android-Kotlin-MVVM-Template/HEAD/core/ui/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /core/ui/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobiledevpro/Android-Kotlin-MVVM-Template/HEAD/core/ui/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /core/ui/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobiledevpro/Android-Kotlin-MVVM-Template/HEAD/core/ui/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobiledevpro/Android-Kotlin-MVVM-Template/HEAD/core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobiledevpro/Android-Kotlin-MVVM-Template/HEAD/core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /core/ui/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobiledevpro/Android-Kotlin-MVVM-Template/HEAD/core/ui/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /core/ui/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobiledevpro/Android-Kotlin-MVVM-Template/HEAD/core/ui/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /core/ui/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobiledevpro/Android-Kotlin-MVVM-Template/HEAD/core/ui/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobiledevpro/Android-Kotlin-MVVM-Template/HEAD/core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobiledevpro/Android-Kotlin-MVVM-Template/HEAD/core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /common-kotlin-library.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java-library' 2 | apply plugin: 'kotlin' 3 | 4 | java { 5 | sourceCompatibility = JavaVersion.VERSION_17 6 | targetCompatibility = JavaVersion.VERSION_17 7 | } -------------------------------------------------------------------------------- /core/utils/src/main/kotlin/com/mobiledevpro/utils/Constant.kt: -------------------------------------------------------------------------------- 1 | package com.mobiledevpro.utils 2 | 3 | /** 4 | * All constants for the app 5 | */ 6 | 7 | const val LOG_TAG_DEBUG = "app.debug" 8 | const val LOG_TAG_ERROR = "app.error" 9 | -------------------------------------------------------------------------------- /core/navigation/src/main/res/transition/fade.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/ui/src/main/res/values/theme_attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /feature/profile_settings/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "$rootDir/common-dynamic-feature.gradle" 2 | android.namespace 'com.mobiledevpro.profile.settings' 3 | 4 | android.defaultConfig { 5 | applicationId "com.mobiledevpro.profile.settings" 6 | } 7 | 8 | -------------------------------------------------------------------------------- /core/ui/src/main/res/drawable/background_edittext_cursor.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /core/navigation/src/main/res/values/nav_ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | include( 2 | ":app", 3 | ":common", 4 | ":core:navigation", 5 | ":core:utils", 6 | ":core:database", 7 | ":feature:chat_core", 8 | ":feature:chat_main", 9 | ":feature:profile_settings" 10 | ) 11 | include(":core:ui") 12 | -------------------------------------------------------------------------------- /core/navigation/src/main/res/transition/slide_left.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/navigation/src/main/res/transition/slide_right.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /feature/chat_core/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "$rootDir/common-dynamic-feature.gradle" 2 | android.namespace 'com.mobiledevpro.chat.core' 3 | 4 | android.defaultConfig { 5 | applicationId "com.mobiledevpro.chat.core" 6 | } 7 | 8 | dependencies { 9 | implementation core.utils 10 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Apr 17 21:41:36 EEST 2024 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /feature/chat_main/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "$rootDir/common-dynamic-feature.gradle" 2 | android.namespace 'com.mobiledevpro.chat.main' 3 | 4 | android.defaultConfig { 5 | applicationId "com.mobiledevpro.chat.main" 6 | } 7 | 8 | dependencies { 9 | implementation features.chatCore 10 | } -------------------------------------------------------------------------------- /core/ui/src/main/res/drawable/background_window_dark.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /core/ui/src/main/res/drawable/background_window_light.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /core/navigation/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "$rootDir/common-android-library.gradle" 2 | 3 | android.namespace 'com.mobiledevpro.navigation' 4 | 5 | dependencies { 6 | api deps.activity 7 | api deps.fragment 8 | 9 | api deps.navigationUi 10 | api deps.navigationFragment 11 | api deps.navigationDynamic 12 | } -------------------------------------------------------------------------------- /core/ui/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /core/ui/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /core/ui/src/main/res/drawable/ic_send_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /feature/chat_core/src/main/kotlin/com/mobiledevpro/chat/core/data/model/ChatUserData.kt: -------------------------------------------------------------------------------- 1 | package com.mobiledevpro.chat.core.data.model 2 | 3 | import android.net.Uri 4 | 5 | /** 6 | * Data layer 7 | * 8 | * Created on Sep 09, 2022. 9 | * 10 | */ 11 | data class ChatUserData( 12 | val uid: String, 13 | val name: String, 14 | val avatarUrl: Uri?, 15 | val isItYou: Boolean 16 | ) 17 | -------------------------------------------------------------------------------- /core/ui/src/main/res/drawable/background_layout_top_rounding.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /core/database/src/main/kotlin/com/mobiledevpro/database/di/Module.kt: -------------------------------------------------------------------------------- 1 | package com.mobiledevpro.database.di 2 | 3 | import com.mobiledevpro.database.AppDatabase 4 | import org.koin.android.ext.koin.androidContext 5 | import org.koin.dsl.module 6 | 7 | /** 8 | * Koin module 9 | */ 10 | 11 | val coreDatabaseModule = module { 12 | 13 | single { 14 | AppDatabase.buildDatabase(androidContext()) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /core/ui/src/main/res/drawable/background_layout_bottom_rounding.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /feature/chat_core/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /feature/chat_main/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /feature/chat_main/src/main/res/menu/menu_chat_public.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | -------------------------------------------------------------------------------- /core/ui/src/main/res/drawable/background_window_default.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /feature/profile_settings/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /core/ui/src/main/res/values/style_image_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /feature/chat_core/src/main/res/drawable/background_message_sent.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /core/ui/src/main/res/drawable/ic_back_arrow_light_24dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /feature/chat_core/src/main/res/drawable/background_message_received.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /core/database/src/main/kotlin/com/mobiledevpro/database/entity/UserEntity.kt: -------------------------------------------------------------------------------- 1 | package com.mobiledevpro.database.entity 2 | 3 | import androidx.room.Entity 4 | import androidx.room.Index 5 | 6 | /** 7 | * User 8 | * 9 | * 10 | */ 11 | @Entity( 12 | tableName = "user", 13 | indices = [ 14 | Index(value = ["id"]) 15 | ], 16 | primaryKeys = ["id"] 17 | ) 18 | data class UserEntity( 19 | val id: String, 20 | val name: String, 21 | val email: String 22 | ) 23 | -------------------------------------------------------------------------------- /core/ui/src/main/res/drawable/ic_list_dark_24dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/mobiledevpro/app/helper/TypeConverter.kt: -------------------------------------------------------------------------------- 1 | package com.mobiledevpro.app.helper 2 | 3 | import androidx.databinding.InverseMethod 4 | 5 | /** 6 | * Converter helper for data binding 7 | * 8 | * 9 | * Created by Dmitriy Chernysh 10 | * 11 | * 12 | * http://androiddev.pro 13 | * 14 | * #MobileDevPro 15 | */ 16 | 17 | object TypeConverter { 18 | 19 | @InverseMethod("toInt") 20 | fun toString(value: Int): String = value.toString() 21 | 22 | fun toInt(value: String?): Int { 23 | return if (!value.isNullOrEmpty()) value.toInt() else 0 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /core/ui/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 16dp 6 | 7 | 4dp 8 | 48dp 9 | 10 | 32dp 11 | 24dp 12 | 24dp 13 | 24dp 14 | -------------------------------------------------------------------------------- /feature/chat_main/src/main/res/navigation/nav_chat_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /core/ui/src/main/res/values/style_toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 14 | 15 | -------------------------------------------------------------------------------- /core/ui/src/main/res/drawable/ic_profile_avatar_dark_24dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /feature/chat_core/src/main/res/drawable/ic_message_avatar_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/mobiledevpro/app/App.kt: -------------------------------------------------------------------------------- 1 | package com.mobiledevpro.app 2 | 3 | import android.app.Application 4 | import com.mobiledevpro.app.di.* 5 | import org.koin.android.ext.koin.androidContext 6 | import org.koin.core.context.startKoin 7 | 8 | /** 9 | * Main application class 10 | * 11 | * Created by Dmitriy Chernysh 12 | * 13 | * 14 | * http://mobile-dev.pro 15 | * 16 | * 17 | * #MobileDevPro 18 | */ 19 | 20 | class App : Application() { 21 | override fun onCreate() { 22 | super.onCreate() 23 | startKoin { 24 | androidContext(this@App) 25 | modules(getModules()) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /common/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 9 | 10 | 16 | 17 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Chat Template 3 | 4 | Public Chat 5 | Profile 6 | v%s 7 | 8 | Settings 9 | 10 | 11 | Let`s chat. Enter your message here 12 | Email 13 | Password 14 | Sign In with Google 15 | 16 | Sign In 17 | 18 | 19 | or 20 | 21 | 22 | There is a trouble connecting to server. Please, check your internet connection and try again. 23 | 24 | Chat core 25 | Profile Settings 26 | 27 | 28 | -------------------------------------------------------------------------------- /core/ui/src/main/res/values/style_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 15 | 16 | 22 | -------------------------------------------------------------------------------- /feature/chat_core/src/main/kotlin/com/mobiledevpro/chat/core/data/model/ChatMessageData.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 | Dmitri Chernysh | http://mobile-dev.pro 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | package com.mobiledevpro.chat.core.data.model 19 | 20 | 21 | /** 22 | * Data layer 23 | * 24 | * Created on Dec 15, 2020. 25 | * 26 | */ 27 | data class ChatMessageData( 28 | val uid: String, 29 | val timeSentUtc: Long, //in ms 30 | val text: String, 31 | val user: ChatUserData 32 | ) -------------------------------------------------------------------------------- /feature/chat_core/src/main/kotlin/com/mobiledevpro/chat/core/domain/model/ChatUser.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 | Dmitri Chernysh | http://mobile-dev.pro 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | package com.mobiledevpro.chat.core.domain.model 19 | 20 | import android.net.Uri 21 | 22 | /** 23 | * User model (domain layer) 24 | * 25 | * Created on Dec 15, 2020. 26 | * 27 | */ 28 | data class ChatUser( 29 | val uid: String, 30 | val name: String, 31 | val avatarUrl: Uri?, 32 | val isItYou: Boolean 33 | ) -------------------------------------------------------------------------------- /feature/chat_main/src/main/kotlin/com/mobiledevpro/chat/main/data/repository/ChatPublicRepository.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | package com.mobiledevpro.chat.main.data.repository 19 | 20 | import com.mobiledevpro.chat.core.data.model.ChatMessageData 21 | import com.mobiledevpro.chat.core.data.model.ChatUserData 22 | import kotlinx.coroutines.flow.Flow 23 | 24 | 25 | interface ChatPublicRepository { 26 | fun getMessagesList(user: ChatUserData): Flow> 27 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/com/mobiledevpro/common/ui/coroutines/BaseUseCase.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | package com.mobiledevpro.common.ui.coroutines 19 | 20 | import kotlinx.coroutines.CoroutineDispatcher 21 | 22 | /** 23 | * Base UseCase 24 | * 25 | * Created on Sep 12, 2022. 26 | * 27 | */ 28 | abstract class BaseUseCase( 29 | executionDispatcher: CoroutineDispatcher 30 | ) { 31 | protected val dispatcher = executionDispatcher 32 | 33 | abstract fun logException(e: Exception) 34 | } -------------------------------------------------------------------------------- /feature/chat_core/src/main/kotlin/com/mobiledevpro/chat/core/mapper/ChatUserMapper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | package com.mobiledevpro.chat.core.mapper 19 | 20 | import com.mobiledevpro.chat.core.data.model.ChatUserData 21 | import com.mobiledevpro.chat.core.domain.model.ChatUser 22 | 23 | fun ChatUserData.toDomain(): ChatUser = 24 | ChatUser( 25 | uid, name, avatarUrl, isItYou 26 | ) 27 | 28 | 29 | fun ChatUser.toData(): ChatUserData = 30 | ChatUserData( 31 | uid, name, avatarUrl, isItYou 32 | ) -------------------------------------------------------------------------------- /feature/chat_core/src/main/kotlin/com/mobiledevpro/chat/core/mapper/ChatMessageMapper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | package com.mobiledevpro.chat.core.mapper 19 | 20 | import com.mobiledevpro.chat.core.data.model.ChatMessageData 21 | import com.mobiledevpro.chat.core.domain.model.ChatMessage 22 | 23 | fun ChatMessageData.toDomain() = 24 | ChatMessage(uid, timeSentUtc, text, user = user.toDomain()) 25 | 26 | fun List.toDomain(): List = 27 | this.mapTo(ArrayList(), ChatMessageData::toDomain) -------------------------------------------------------------------------------- /feature/chat_main/src/main/kotlin/com/mobiledevpro/chat/main/domain/interactor/ChatPublicInteractor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 | Dmitri Chernysh | http://mobile-dev.pro 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | package com.mobiledevpro.chat.main.domain.interactor 19 | 20 | import com.mobiledevpro.chat.core.domain.model.ChatMessage 21 | import kotlinx.coroutines.flow.Flow 22 | 23 | /** 24 | * Interactor uses in Public chat View Model 25 | * 26 | * Created on Dec 15, 2020. 27 | * 28 | */ 29 | interface ChatPublicInteractor { 30 | suspend fun getMessagesList(): Flow>> 31 | 32 | 33 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/mobiledevpro/app/helper/ResourcesProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 | Dmitri Chernysh | http://mobile-dev.pro 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | package com.mobiledevpro.app.helper 19 | 20 | import androidx.annotation.StringRes 21 | 22 | /** 23 | * Provider for app resources (as example, from strings.xml) 24 | * 25 | * Created on Dec 15, 2020. 26 | * 27 | */ 28 | interface ResourcesProvider { 29 | fun getErrorMessage(throwable: Throwable?): String 30 | 31 | fun getStringMessage(@StringRes resId: Int): String 32 | 33 | fun getFormattedString(@StringRes resId: Int, vararg args: Any): String 34 | } -------------------------------------------------------------------------------- /feature/chat_main/src/main/kotlin/com/mobiledevpro/chat/main/data/local/ChatPublicLocalSource.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | package com.mobiledevpro.chat.main.data.local 19 | 20 | import com.mobiledevpro.chat.core.data.model.ChatMessageData 21 | import com.mobiledevpro.chat.core.data.model.ChatUserData 22 | import kotlinx.coroutines.flow.Flow 23 | 24 | /** 25 | * Local source to get data from database 26 | * 27 | * Created on Sep 12, 2022. 28 | * 29 | */ 30 | interface ChatPublicLocalSource { 31 | fun getFakeMessagesList(chatUser: ChatUserData): Flow> 32 | } -------------------------------------------------------------------------------- /core/ui/src/main/res/drawable/ic_settings_dark_24dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /core/ui/src/main/res/values/style_edittext.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 18 | 19 | 25 | -------------------------------------------------------------------------------- /feature/chat_main/src/main/kotlin/com/mobiledevpro/chat/main/data/repository/ImplChatPublicRepository.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | package com.mobiledevpro.chat.main.data.repository 19 | 20 | import com.mobiledevpro.chat.core.data.model.ChatMessageData 21 | import com.mobiledevpro.chat.core.data.model.ChatUserData 22 | import com.mobiledevpro.chat.main.data.local.ChatPublicLocalSource 23 | import kotlinx.coroutines.flow.Flow 24 | 25 | 26 | class ImplChatPublicRepository( 27 | private val localSource: ChatPublicLocalSource 28 | ) : ChatPublicRepository { 29 | 30 | override fun getMessagesList(user: ChatUserData): Flow> = 31 | localSource.getFakeMessagesList(user) 32 | } -------------------------------------------------------------------------------- /feature/chat_core/src/main/kotlin/com/mobiledevpro/chat/core/domain/model/ChatMessage.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 | Dmitri Chernysh | http://mobile-dev.pro 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | package com.mobiledevpro.chat.core.domain.model 19 | 20 | import android.net.Uri 21 | import com.mobiledevpro.utils.TimeFormat 22 | import com.mobiledevpro.utils.getTimeString 23 | 24 | 25 | /** 26 | * Domain layer 27 | * 28 | * Created on Dec 15, 2020. 29 | * 30 | */ 31 | data class ChatMessage( 32 | val uid: String, 33 | val timeSentUtc: Long, //in ms 34 | val text: String, 35 | val user: ChatUser 36 | ) { 37 | fun getFormattedTime(): String = timeSentUtc.getTimeString(TimeFormat.AM_PM) 38 | 39 | fun getAvatarUrl(): Uri? = user.avatarUrl 40 | 41 | fun isAvatarCircled() = true 42 | } -------------------------------------------------------------------------------- /core/database/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "$rootDir/common-android-library.gradle" 2 | 3 | android.namespace 'com.mobiledevpro.database' 4 | 5 | android.defaultConfig.javaCompileOptions { 6 | annotationProcessorOptions { 7 | arguments = [ 8 | "room.schemaLocation" : "$projectDir/schemas".toString(), 9 | "room.expandProjection": "true"] 10 | } 11 | } 12 | 13 | android.productFlavors { 14 | production { 15 | buildConfigField "String", "DB_NAME", "\"app_database\"" 16 | buildConfigField "int", "DB_VERSION", "1" 17 | } 18 | 19 | dev { 20 | buildConfigField "String", "DB_NAME", "\"app_database_demo\"" 21 | buildConfigField "int", "DB_VERSION", "1" 22 | } 23 | } 24 | 25 | // Add Room schemas path to src dirs 26 | android.sourceSets { 27 | main { 28 | androidTest.assets.srcDirs += files("$projectDir/schemas".toString()) 29 | } 30 | } 31 | 32 | dependencies { 33 | //Koin 34 | implementation deps.koinCore 35 | implementation deps.koinAndroid 36 | 37 | //Room 38 | implementation deps.room 39 | implementation deps.roomRx 40 | implementation deps.roomKtx 41 | kapt deps.roomCompiler 42 | 43 | androidTestImplementation deps.roomTesting 44 | testImplementation deps.jUnit 45 | androidTestImplementation deps.jUnitExt 46 | androidTestImplementation deps.testRunner 47 | } 48 | -------------------------------------------------------------------------------- /feature/chat_core/src/main/kotlin/com/mobiledevpro/chat/core/view/recycler/RecyclerItem.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 | Dmitri Chernysh | http://mobile-dev.pro 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | package com.mobiledevpro.chat.core.view.recycler 19 | 20 | import androidx.annotation.LayoutRes 21 | import androidx.databinding.ViewDataBinding 22 | import com.mobiledevpro.chat.core.BR 23 | 24 | /** 25 | * Model for RecyclerView item 26 | * 27 | * Created on Dec 14, 2020. 28 | * 29 | */ 30 | data class RecyclerItem( 31 | val data: Any?, 32 | @LayoutRes 33 | val layoutId: Int, 34 | val variableId: Int 35 | ) { 36 | fun bind(binding: ViewDataBinding, handler: RecyclerViewHandler?) { 37 | binding.setVariable(variableId, data) 38 | if (handler != null) 39 | binding.setVariable(BR.handler, handler) 40 | } 41 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/com/mobiledevpro/common/ui/extension/LifecycleOwnerExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 vmadalin.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.mobiledevpro.common.ui.extension 18 | 19 | import androidx.lifecycle.LifecycleOwner 20 | import androidx.lifecycle.LiveData 21 | 22 | /** 23 | * Adds the given observer to the observers list within the lifespan of the given 24 | * owner. The events are dispatched on the main thread. If LiveData already has data 25 | * set, it will be delivered to the observer. 26 | * 27 | * @param liveData The liveData to observe. 28 | * @param observer The observer that will receive the events. 29 | * @see LiveData.observe 30 | */ 31 | fun LifecycleOwner.observe(liveData: LiveData, observer: (T) -> Unit) { 32 | liveData.observe(this) { 33 | it?.let { t -> observer(t) } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /feature/profile_settings/src/main/kotlin/com/mobiledevpro/profile/settings/di/Module.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 | Dmitri Chernysh | http://mobile-dev.pro 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | package com.mobiledevpro.profile.settings.di 19 | 20 | import com.mobiledevpro.profile.settings.view.ProfileSettingsFragment 21 | import com.mobiledevpro.profile.settings.view.ProfileSettingsViewModel 22 | import org.koin.androidx.viewmodel.dsl.viewModel 23 | import org.koin.dsl.module 24 | 25 | /** 26 | * Koin module 27 | * 28 | * Created on Jan 26, 2021. 29 | * 30 | */ 31 | 32 | val featureProfileSettingsModule = module { 33 | scope { 34 | viewModel { 35 | ProfileSettingsViewModel( 36 | resourcesProvider = get() 37 | // interactor = get() 38 | ) 39 | } 40 | 41 | } 42 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/com/mobiledevpro/common/ui/base/BaseActivityInterface.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 http://mobile-dev.pro 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.mobiledevpro.common.ui.base 18 | 19 | import androidx.annotation.AttrRes 20 | import androidx.annotation.ColorRes 21 | import androidx.annotation.DrawableRes 22 | 23 | interface BaseActivityInterface { 24 | fun setAppBarTitle(titleString: String) 25 | 26 | fun setAppBarSubTitle(subTitleString: String) 27 | 28 | fun setStatusBarColor(@AttrRes colorResId: Int) 29 | 30 | fun setAppBarColor(@AttrRes colorResId: Int) 31 | 32 | fun setAppBarTitleColor(@ColorRes colorResId: Int) 33 | 34 | fun setNavigationBarColor(@AttrRes colorResId: Int) 35 | 36 | fun setAppWindowBackground(@DrawableRes backgroundResId: Int) 37 | 38 | fun setHomeAsUpIndicatorIcon(@DrawableRes drawable: Int) 39 | } -------------------------------------------------------------------------------- /core/utils/src/main/kotlin/com/mobiledevpro/utils/TimeUtil.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 | Dmitri Chernysh | http://mobile-dev.pro 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | package com.mobiledevpro.utils 19 | 20 | import java.text.SimpleDateFormat 21 | import java.util.* 22 | 23 | /** 24 | * Util class to format date and time 25 | * 26 | * Created on Dec 22, 2020. 27 | * 28 | */ 29 | 30 | enum class TimeFormat { 31 | AM_PM 32 | } 33 | 34 | fun Long.getTimeString(format: TimeFormat): String { 35 | val date = Date(this) 36 | 37 | return when (format) { 38 | TimeFormat.AM_PM -> 39 | SimpleDateFormat(TimePattern.amPm, Locale.getDefault()).apply { 40 | timeZone = Calendar.getInstance().timeZone 41 | }.format(date) 42 | 43 | } 44 | } 45 | 46 | private object TimePattern { 47 | const val amPm = "h:mm a" 48 | } 49 | -------------------------------------------------------------------------------- /core/ui/src/main/res/drawable/ic_facebook_circle_white_56dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 16 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layouts/include/layout/include_appbar_custom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 22 | 23 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /core/ui/src/main/res/values/colors_base.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #1C2E46 4 | 5 | #1C2E46 6 | #192A3E 7 | #2D5872 8 | #12B0BE 9 | 10 | @color/colorPrimary 11 | @color/colorWindowGreyBackground 12 | #757575 13 | 14 | #BDBDBD 15 | @color/colorPrimary 16 | #E5E5E5 17 | #D3D1D3 18 | 19 | #70000000 20 | #b4ffffff 21 | 22 | #ffffff 23 | 24 | @color/colorPrimary 25 | @color/colorPrimaryDark 26 | @color/colorPrimaryLight 27 | 28 | #BBDEFB 29 | 30 | @color/colorWhite 31 | @color/colorTextPrimary 32 | #BDBDBD 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 14 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /common/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: "$rootDir/common-android-library.gradle" 2 | apply plugin: 'maven-publish' 3 | 4 | def libraryVersion = '1.2.0' 5 | def publishingGroupId = 'com.github.mobiledevpro' 6 | def publishingArtifactId = 'android-kotlin-mvvm-template' 7 | 8 | android.namespace = 'com.mobiledevpro.common.ui' 9 | 10 | group = publishingGroupId 11 | version = libraryVersion 12 | 13 | //read more about publishing to JitPack.io https://github.com/jitpack-io/multi-flavor-lib-demo 14 | 15 | android.buildFeatures.dataBinding = true 16 | 17 | dependencies { 18 | 19 | api deps.coreKtx 20 | api deps.appcompat 21 | api deps.material 22 | 23 | api deps.activity 24 | api deps.fragment 25 | 26 | //Lifecycle 27 | api deps.viewModel 28 | api deps.liveData 29 | api deps.saveState 30 | kapt deps.lifecycleCompiler 31 | 32 | //Rx 33 | implementation deps.rxAndroid 34 | 35 | } 36 | 37 | //The following code needed to publishing this library to JitPack.io 38 | android.publishing { 39 | singleVariant('productionRelease') { 40 | withSourcesJar() 41 | } 42 | } 43 | 44 | afterEvaluate { 45 | publishing { 46 | publications { 47 | // Creates a Maven publication called "release". 48 | release(MavenPublication) { 49 | from components.productionRelease 50 | groupId = publishingGroupId 51 | artifactId = publishingArtifactId 52 | version = libraryVersion 53 | } 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/com/mobiledevpro/common/ui/base/BaseViewModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 http://mobile-dev.pro 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.mobiledevpro.common.ui.base 18 | 19 | import androidx.lifecycle.LifecycleEventObserver 20 | import androidx.lifecycle.ViewModel 21 | import io.reactivex.disposables.CompositeDisposable 22 | 23 | /** 24 | * Base class for ViewModels 25 | * 26 | * http://mobile-dev.pro 27 | * 28 | */ 29 | abstract class BaseViewModel : ViewModel(), LifecycleEventObserver { 30 | 31 | private var disposable: CompositeDisposable = CompositeDisposable() 32 | 33 | val subscriptions: CompositeDisposable 34 | get() { 35 | if (disposable.isDisposed) disposable = CompositeDisposable() 36 | return disposable 37 | } 38 | 39 | fun clearSubscriptions() { 40 | disposable.dispose() 41 | } 42 | 43 | override fun onCleared() { 44 | disposable.dispose() 45 | } 46 | } -------------------------------------------------------------------------------- /ci-deploy-dropbox.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | #Token from Dropbox Settings. 4 | #DROPBOX_TOKEN="add this token to Circle CI Environment Variables in the project settings" 5 | 6 | APK_FILES="./app/build/outputs/bundle/*/*-release.aab" 7 | MAPPING_FILES="./app/build/outputs/mapping/*/mapping.txt" 8 | 9 | for FILE in $APK_FILES; do 10 | 11 | #check file is exist and not empty 12 | if [ -f "${FILE}" ] && [ -s "${FILE}" ]; then 13 | 14 | echo "Found AAB: ${FILE}" 15 | FILE_NAME=$(basename "${FILE}") 16 | 17 | curl -X POST https://content.dropboxapi.com/2/files/upload \ 18 | --header "Authorization: Bearer ${DROPBOX_TOKEN}" \ 19 | --header "Dropbox-API-Arg: {\"path\": \"\/${FILE_NAME}\",\"mode\": \"overwrite\",\"autorename\": true,\"mute\": false}" \ 20 | --header "Content-Type: application/octet-stream" \ 21 | --data-binary @"${FILE}" 22 | else 23 | echo "File ${FILE} is empty" 24 | exit 1 25 | fi 26 | 27 | done 28 | 29 | for TXT in $MAPPING_FILES; do 30 | 31 | #check file is exist and not empty 32 | if [ -f "${TXT}" ] && [ -s "${TXT}" ]; then 33 | 34 | echo "Found mapping: ${TXT}" 35 | TXT_NAME=$(basename "${TXT}") 36 | 37 | curl -X POST https://content.dropboxapi.com/2/files/upload \ 38 | --header "Authorization: Bearer ${DROPBOX_TOKEN}" \ 39 | --header "Dropbox-API-Arg: {\"path\": \"\/${TXT_NAME}\",\"mode\": \"overwrite\",\"autorename\": true,\"mute\": false}" \ 40 | --header "Content-Type: application/octet-stream" \ 41 | --data-binary @"${FILE}" 42 | else 43 | echo "File ${TXT} is empty" 44 | exit 1 45 | fi 46 | 47 | done 48 | -------------------------------------------------------------------------------- /common-android-library.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-kapt' 4 | 5 | 6 | android { 7 | compileSdkVersion rootProject.compileSdkVersion 8 | 9 | defaultConfig { 10 | minSdkVersion rootProject.minSdkVersion 11 | targetSdkVersion rootProject.targetSdkVersion 12 | versionCode rootProject.appVersionCode 13 | versionName rootProject.appVersionName 14 | 15 | consumerProguardFiles "consumer-rules.pro" 16 | } 17 | 18 | compileOptions { 19 | sourceCompatibility JavaVersion.VERSION_17 20 | targetCompatibility JavaVersion.VERSION_17 21 | } 22 | 23 | kotlinOptions { 24 | jvmTarget = JavaVersion.VERSION_17.toString() 25 | } 26 | 27 | buildTypes { 28 | debug { 29 | debuggable true 30 | } 31 | release { 32 | debuggable false 33 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 34 | } 35 | } 36 | 37 | sourceSets { 38 | main { 39 | java.srcDirs = ['src/main/kotlin'] 40 | } 41 | } 42 | 43 | flavorDimensions "default" 44 | productFlavors { 45 | production { 46 | dimension "default" 47 | buildConfigField 'String', 'VERSION_NAME', "\"${rootProject.appVersionName}\"" 48 | } 49 | 50 | dev { 51 | dimension "default" 52 | buildConfigField 'String', 'VERSION_NAME', "\"${rootProject.appVersionName}\"" 53 | } 54 | } 55 | 56 | buildFeatures.buildConfig = true 57 | } -------------------------------------------------------------------------------- /common-dynamic-feature.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.dynamic-feature' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-kapt' 4 | apply plugin: "androidx.navigation.safeargs.kotlin" 5 | 6 | android { 7 | compileSdkVersion rootProject.compileSdkVersion 8 | 9 | defaultConfig { 10 | minSdkVersion rootProject.minSdkVersion 11 | targetSdkVersion rootProject.targetSdkVersion 12 | 13 | versionName rootProject.appVersionName 14 | versionCode rootProject.appVersionCode 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | 18 | consumerProguardFiles "consumer-rules.pro" 19 | } 20 | 21 | compileOptions { 22 | sourceCompatibility JavaVersion.VERSION_17 23 | targetCompatibility JavaVersion.VERSION_17 24 | } 25 | 26 | kotlinOptions { 27 | jvmTarget = JavaVersion.VERSION_17.toString() 28 | } 29 | 30 | buildTypes { 31 | debug { 32 | debuggable true 33 | } 34 | release { 35 | debuggable false 36 | } 37 | } 38 | 39 | sourceSets { 40 | main { 41 | res.srcDirs = ['src/main/res'] 42 | java.srcDirs = ['src/main/java', 'src/main/kotlin'] 43 | } 44 | } 45 | 46 | flavorDimensions "default" 47 | productFlavors { 48 | production { 49 | dimension "default" 50 | } 51 | dev { 52 | dimension "default" 53 | } 54 | } 55 | 56 | buildFeatures.dataBinding = true 57 | 58 | } 59 | 60 | dependencies { 61 | implementation project(":app") 62 | } 63 | -------------------------------------------------------------------------------- /feature/chat_main/src/main/kotlin/com/mobiledevpro/chat/main/domain/interactor/ImplChatPublicInteractor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 | Dmitri Chernysh | http://mobile-dev.pro 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | package com.mobiledevpro.chat.main.domain.interactor 19 | 20 | import com.mobiledevpro.chat.core.domain.model.ChatMessage 21 | import com.mobiledevpro.chat.main.domain.usecase.GetCurrentUserUseCase 22 | import com.mobiledevpro.chat.main.domain.usecase.GetPublicChatMessagesUseCase 23 | import com.mobiledevpro.common.ui.coroutines.andThenFlow 24 | import kotlinx.coroutines.flow.Flow 25 | 26 | /** 27 | * Interactor uses in Public chat View Model 28 | * 29 | * Created on Dec 15, 2020. 30 | * 31 | */ 32 | class ImplChatPublicInteractor( 33 | private val getPublicChatMessagesUseCase: GetPublicChatMessagesUseCase, 34 | private val getCurrentUserUseCase: GetCurrentUserUseCase 35 | ) : ChatPublicInteractor { 36 | 37 | override suspend fun getMessagesList(): Flow>> = 38 | getCurrentUserUseCase.execute() 39 | .andThenFlow(getPublicChatMessagesUseCase::execute) 40 | 41 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx4096m -Dkotlin.daemon.jvm.options\="-Xmx4096m" -XX:+UseParallelGC 13 | # Jetifier is no longer needed; it slowing down a build 14 | android.enableJetifier=false 15 | android.useAndroidX=true 16 | kapt.incremental.apt=true 17 | # When configured, Gradle will run in incubating parallel mode. 18 | # This option should only be used with decoupled projects. More details, visit 19 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 20 | # org.gradle.parallel=true 21 | # Abdroid Build features default values 22 | android.defaults.buildfeatures.aidl=false 23 | android.defaults.buildfeatures.buildconfig=false 24 | android.defaults.buildfeatures.databinding=false 25 | android.defaults.buildfeatures.renderscript=false 26 | android.defaults.buildfeatures.resvalues=false 27 | android.defaults.buildfeatures.shaders=false 28 | android.defaults.buildfeatures.viewbinding=false 29 | android.nonTransitiveRClass=true 30 | # Should be enabled for better performance 31 | org.gradle.daemon=true 32 | # With Configuration cache, 33 | # Gradle can skip the configuration phase entirely 34 | # when nothing that affects the build configuration has changed 35 | org.gradle.unsafe.configuration-cache=false -------------------------------------------------------------------------------- /app/src/main/kotlin/com/mobiledevpro/app/helper/ImplResourcesProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 | Dmitri Chernysh | http://mobile-dev.pro 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | package com.mobiledevpro.app.helper 19 | 20 | import android.content.res.Resources 21 | import com.mobiledevpro.app.R 22 | import com.mobiledevpro.errors.NetworkConnectionThrowable 23 | 24 | /** 25 | * Provider for app resources (as example, from strings.xml) 26 | * 27 | * Created on Dec 15, 2020. 28 | * 29 | */ 30 | class ImplResourcesProvider( 31 | private val resources: Resources 32 | ) : ResourcesProvider { 33 | 34 | override fun getErrorMessage(throwable: Throwable?): String = 35 | when (throwable) { 36 | 37 | is NetworkConnectionThrowable -> 38 | resources.getString(R.string.message_trouble_internet_connection) 39 | 40 | else -> throwable?.localizedMessage ?: "" 41 | } 42 | 43 | 44 | override fun getStringMessage(resId: Int): String = 45 | resources.getString(resId) 46 | 47 | override fun getFormattedString(resId: Int, vararg args: Any): String = 48 | resources.getString(resId, *args) 49 | } -------------------------------------------------------------------------------- /feature/chat_main/src/main/kotlin/com/mobiledevpro/chat/main/domain/usecase/GetCurrentUserUseCase.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | package com.mobiledevpro.chat.main.domain.usecase 19 | 20 | import androidx.core.net.toUri 21 | import com.mobiledevpro.chat.core.domain.model.ChatUser 22 | import com.mobiledevpro.common.ui.coroutines.BaseCoroutinesUseCase 23 | import com.mobiledevpro.common.ui.coroutines.None 24 | import kotlinx.coroutines.CoroutineDispatcher 25 | import kotlinx.coroutines.delay 26 | import java.util.* 27 | 28 | /** 29 | * Use case to get a current user (YOU) 30 | * 31 | * Created on Sep 08, 2022. 32 | * 33 | */ 34 | class GetCurrentUserUseCase( 35 | defaultDispatcher: CoroutineDispatcher, 36 | ) : BaseCoroutinesUseCase(defaultDispatcher) { 37 | 38 | override suspend fun buildUseCase(params: None?): ChatUser { 39 | 40 | delay(5000) 41 | 42 | return ChatUser( 43 | UUID.randomUUID().toString(), 44 | "Fake Name", 45 | "https://i.pravatar.cc/128?img=5".toUri(), 46 | false 47 | ) 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /core/database/schemas/com.mobiledevpro.database.AppDatabase/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 1, 5 | "identityHash": "8e35c964262241ddf94114427502c1ae", 6 | "entities": [ 7 | { 8 | "tableName": "user", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `email` TEXT NOT NULL, PRIMARY KEY(`id`))", 10 | "fields": [ 11 | { 12 | "fieldPath": "id", 13 | "columnName": "id", 14 | "affinity": "TEXT", 15 | "notNull": true 16 | }, 17 | { 18 | "fieldPath": "name", 19 | "columnName": "name", 20 | "affinity": "TEXT", 21 | "notNull": true 22 | }, 23 | { 24 | "fieldPath": "email", 25 | "columnName": "email", 26 | "affinity": "TEXT", 27 | "notNull": true 28 | } 29 | ], 30 | "primaryKey": { 31 | "columnNames": [ 32 | "id" 33 | ], 34 | "autoGenerate": false 35 | }, 36 | "indices": [ 37 | { 38 | "name": "index_user_id", 39 | "unique": false, 40 | "columnNames": [ 41 | "id" 42 | ], 43 | "createSql": "CREATE INDEX IF NOT EXISTS `index_user_id` ON `${TABLE_NAME}` (`id`)" 44 | } 45 | ], 46 | "foreignKeys": [] 47 | } 48 | ], 49 | "views": [], 50 | "setupQueries": [ 51 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 52 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '8e35c964262241ddf94114427502c1ae')" 53 | ] 54 | } 55 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/mobiledevpro/app/ui/mainscreen/view/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.mobiledevpro.app.ui.mainscreen.view 2 | 3 | import android.view.View 4 | import android.widget.TextView 5 | import androidx.appcompat.app.AppCompatDelegate 6 | import androidx.appcompat.widget.Toolbar 7 | import com.mobiledevpro.app.R 8 | import com.mobiledevpro.common.ui.base.ActivitySettings 9 | import com.mobiledevpro.common.ui.base.BaseActivity 10 | import com.mobiledevpro.common.ui.extension.getColorCompatible 11 | 12 | class MainActivity : BaseActivity( 13 | layoutId = R.layout.activity_main, 14 | ActivitySettings( 15 | isAdjustFontScaleToNormal = true 16 | ) 17 | ) { 18 | 19 | override fun initToolbar() { 20 | val toolbar = findViewById(R.id.toolbar) as Toolbar? 21 | toolbar?.let { 22 | setSupportActionBar(it) 23 | } 24 | 25 | } 26 | 27 | override fun initViews(layoutView: View) { 28 | //do something: as example, init bottom navigation. 29 | AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); //night mode theme is disabled right now 30 | 31 | } 32 | 33 | override fun setAppBarTitle(titleString: String) { 34 | supportActionBar?.apply { 35 | //custom title uses instead of default 36 | setDisplayShowTitleEnabled(false) 37 | findViewById(R.id.toolbar_title)?.text = titleString 38 | } 39 | } 40 | 41 | override fun setAppBarTitleColor(colorResId: Int) { 42 | supportActionBar?.apply { 43 | //custom title uses instead of default 44 | setDisplayShowTitleEnabled(false) 45 | findViewById(R.id.toolbar_title)?.setTextColor( 46 | getColorCompatible(colorResId) 47 | ) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /core/navigation/src/main/kotlin/com/mobiledevpro/navigation/ext/NavigationExt.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 | Dmitri Chernysh | http://mobile-dev.pro 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | package com.mobiledevpro.navigation.ext 19 | 20 | import androidx.fragment.app.Fragment 21 | import androidx.navigation.NavOptions 22 | import androidx.navigation.fragment.findNavController 23 | import com.mobiledevpro.navigation.NavigateTo 24 | import com.mobiledevpro.navigation.Navigation 25 | import com.mobiledevpro.navigation.R 26 | 27 | 28 | fun Fragment.launch(navigation: Navigation) { 29 | val commonNavOptionsBuilder = NavOptions.Builder() 30 | 31 | val navResId = when (navigation.to) { 32 | NavigateTo.CHAT_MAIN -> R.id.actionNavToChatMain 33 | NavigateTo.PROFILE_SETTINGS -> R.id.actionNavToProfileSettings 34 | else -> 0 35 | } 36 | 37 | if (navResId > 0) 38 | findNavController() 39 | .navigate( 40 | navResId, 41 | navigation.extras, 42 | commonNavOptionsBuilder.build() 43 | ) 44 | else 45 | when (navigation.to) { 46 | NavigateTo.BACK -> 47 | requireActivity().onBackPressed() 48 | else -> {} 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /feature/profile_settings/src/main/kotlin/com/mobiledevpro/profile/settings/view/ProfileSettingsViewModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 | Dmitri Chernysh | http://mobile-dev.pro 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | package com.mobiledevpro.profile.settings.view 19 | 20 | import androidx.lifecycle.Lifecycle 21 | import androidx.lifecycle.LifecycleOwner 22 | import androidx.lifecycle.LiveData 23 | import androidx.lifecycle.MutableLiveData 24 | import com.mobiledevpro.app.BuildConfig 25 | import com.mobiledevpro.app.helper.ResourcesProvider 26 | import com.mobiledevpro.common.ui.base.BaseViewModel 27 | import com.mobiledevpro.app.R as RApp 28 | 29 | /** 30 | * View model for Profile Settings screen 31 | * 32 | * Created on Jan 26, 2021. 33 | * 34 | */ 35 | 36 | class ProfileSettingsViewModel( 37 | private val resourcesProvider: ResourcesProvider 38 | ) : BaseViewModel() { 39 | 40 | private val _appVersion = MutableLiveData() 41 | val appVersion: LiveData = _appVersion 42 | 43 | init { 44 | initAppVersion() 45 | } 46 | 47 | override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { 48 | //ignore 49 | } 50 | 51 | private fun initAppVersion() { 52 | resourcesProvider.getFormattedString(RApp.string.app_version, BuildConfig.VERSION_NAME) 53 | .let(_appVersion::postValue) 54 | } 55 | } -------------------------------------------------------------------------------- /install-bundles-on-device.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # 3 | # Script to install AAB on connected device. 4 | # 5 | # Before continue please make sure 6 | # you have the following variables in the file keystore.properties: 7 | # 8 | # KEYPWD - it's keyPassword 9 | # KSTOREPWD - it's storePassword 10 | # KEYSTORE_ALIAS - alias 11 | # KEYSTORE_SECRET - it's a result of command 'gpg -c --armor release.keystore' 12 | # KEYSTORE_SECRET_PASSPHRASE - it's a phrase to TAXI_KEYSTORE_SECRET 13 | # 14 | 15 | #read variables from the file (key pass, key secret, secret passphrase) 16 | . './keystore.properties' && 17 | echo "$KEYSTORE_SECRET" >app/release.asc && 18 | gpg -d --passphrase "$KEYSTORE_SECRET_PASSPHRASE" --batch app/release.asc >app/release.jks && 19 | rm app/release.asc && 20 | 21 | # Export these variables to be able to use it in build.gradle signingConfigs 22 | export KEYPWD && 23 | export KSTOREPWD && 24 | export KEYSTORE_ALIAS && 25 | # ./gradlew clean bundleRelease && 26 | AAB_FILES="./app/build/outputs/bundle/*/*-release.aab" 27 | KEY_STORE_FILE_PATH="app/release.jks" 28 | BUNDLE_TOOL_PATH="/home/dmitri/Projects/bundletool-all-1.2.0.jar" 29 | 30 | for FILE in $AAB_FILES; do 31 | echo "Found AAB: ${FILE}" 32 | FILE_NAME=$(basename ${FILE}) 33 | DIR_PATH=$(dirname ${FILE}) 34 | 35 | #Remove existing APKs before creating a new one 36 | rm -f ${DIR_PATH}/${FILE_NAME}.apks && 37 | 38 | # Build APK set from bundles, for specific connected device 39 | java -jar ${BUNDLE_TOOL_PATH} \ 40 | build-apks \ 41 | --overwrite \ 42 | --connected-device \ 43 | --bundle=$FILE \ 44 | --output=${DIR_PATH}/${FILE_NAME}.apks \ 45 | --ks=$KEY_STORE_FILE_PATH \ 46 | --ks-pass=pass:$KSTOREPWD \ 47 | --ks-key-alias=$KEYSTORE_ALIAS \ 48 | --key-pass=pass:$KEYPWD && 49 | 50 | # Install APKs on connected device 51 | java -jar ${BUNDLE_TOOL_PATH} \ 52 | install-apks \ 53 | --apks=${DIR_PATH}/${FILE_NAME}.apks 54 | 55 | done && 56 | rm app/release.jks 57 | -------------------------------------------------------------------------------- /common/src/main/kotlin/com/mobiledevpro/common/ui/coroutines/BaseCoroutinesUseCase.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | package com.mobiledevpro.common.ui.coroutines 19 | 20 | import android.util.Log 21 | import kotlinx.coroutines.CoroutineDispatcher 22 | import kotlinx.coroutines.Dispatchers 23 | import kotlinx.coroutines.withContext 24 | 25 | /** 26 | * Base UseCase with Coroutines 27 | * 28 | * Created on Sep 12, 2022. 29 | * 30 | */ 31 | abstract class BaseCoroutinesUseCase( 32 | executionDispatcher: CoroutineDispatcher 33 | ) : BaseUseCase(executionDispatcher) { 34 | 35 | abstract suspend fun buildUseCase(params: Params? = null): Results 36 | 37 | suspend fun execute(params: Params? = null): Result = 38 | withContext(dispatcher) { 39 | try { 40 | if (dispatcher == Dispatchers.Main) 41 | throw RuntimeException("Use case '${this::class.simpleName}' cannot be executed in $dispatcher") 42 | 43 | resultOf { 44 | this@BaseCoroutinesUseCase.buildUseCase(params) 45 | } 46 | } catch (e: Exception) { 47 | logException(e) 48 | Result.failure(Throwable(e.localizedMessage)) 49 | } 50 | } 51 | 52 | override fun logException(e: Exception) { 53 | Log.e(this::class.simpleName, "${this::class.simpleName} : ${e.localizedMessage}") 54 | } 55 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/com/mobiledevpro/common/ui/livedata/Event.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.mobiledevpro.common.ui.livedata 17 | 18 | import androidx.lifecycle.Observer 19 | 20 | /** 21 | * Used as a wrapper for data that is exposed via a LiveData that represents an event. 22 | */ 23 | open class Event(private val content: T) { 24 | 25 | @Suppress("MemberVisibilityCanBePrivate") 26 | var hasBeenHandled = false 27 | private set // Allow external read but not write 28 | 29 | /** 30 | * Returns the content and prevents its use again. 31 | */ 32 | fun getContentIfNotHandled(): T? { 33 | return if (hasBeenHandled) { 34 | null 35 | } else { 36 | hasBeenHandled = true 37 | content 38 | } 39 | } 40 | 41 | /** 42 | * Returns the content, even if it's already been handled. 43 | */ 44 | fun peekContent(): T = content 45 | } 46 | 47 | /** 48 | * An [Observer] for [Event]s, simplifying the pattern of checking if the [Event]'s content has 49 | * already been handled. 50 | * 51 | * [onEventUnhandledContent] is *only* called if the [Event]'s contents has not been handled. 52 | */ 53 | class EventObserver(private val onEventUnhandledContent: (T) -> Unit) : Observer> { 54 | override fun onChanged(value: Event) { 55 | value.getContentIfNotHandled()?.let { 56 | onEventUnhandledContent(it) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /feature/chat_core/src/main/kotlin/com/mobiledevpro/chat/core/view/mapper/RecyclerMapper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 | Dmitri Chernysh | http://mobile-dev.pro 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | package com.mobiledevpro.chat.core.view.mapper 19 | 20 | import com.mobiledevpro.chat.core.BR 21 | import com.mobiledevpro.chat.core.R 22 | import com.mobiledevpro.chat.core.domain.model.ChatMessage 23 | import com.mobiledevpro.chat.core.view.recycler.RecyclerItem 24 | 25 | /** 26 | * Mapper for Presentation layer 27 | * 28 | * Created on Dec 15, 2020. 29 | * 30 | */ 31 | 32 | 33 | fun List.toRecyclerView(): List = 34 | this.mapTo(ArrayList()) { 35 | when (it) { 36 | is ChatMessage -> it.toRecyclerItem() 37 | else -> RecyclerItem(null, 0, 0) 38 | } 39 | } 40 | 41 | 42 | fun List.recyclerToList(): List<*> = 43 | this.mapTo(ArrayList()) { 44 | when (it.data) { 45 | is ChatMessage -> { 46 | it.data 47 | } 48 | else -> emptyList() 49 | } 50 | } 51 | 52 | fun T.toRecyclerItem() = when (this) { 53 | is ChatMessage -> RecyclerItem( 54 | data = this, 55 | layoutId = if (this.user.isItYou) 56 | R.layout.item_chat_message_send 57 | else 58 | R.layout.item_chat_message_received, 59 | variableId = BR.message 60 | ) 61 | else -> throw Throwable("Type $this is not defined in Mapper.toRecyclerItem() function") 62 | } -------------------------------------------------------------------------------- /app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "978007496687", 4 | "firebase_url": "https://apptemplate-mobile-dev-pro.firebaseio.com", 5 | "project_id": "apptemplate-mobile-dev-pro", 6 | "storage_bucket": "apptemplate-mobile-dev-pro.appspot.com" 7 | }, 8 | "client": [ 9 | { 10 | "client_info": { 11 | "mobilesdk_app_id": "1:978007496687:android:557267de154b93a1f4c81a", 12 | "android_client_info": { 13 | "package_name": "com.mobiledevpro.apptemplate.new" 14 | } 15 | }, 16 | "oauth_client": [ 17 | { 18 | "client_id": "978007496687-1vvqhcme1bv94ukprbtvtn1l1ags2v9m.apps.googleusercontent.com", 19 | "client_type": 3 20 | } 21 | ], 22 | "api_key": [ 23 | { 24 | "current_key": "AIzaSyBveOwDSB2vQMAyw3K3lgU-V6QB7mArqG0" 25 | } 26 | ], 27 | "services": { 28 | "appinvite_service": { 29 | "other_platform_oauth_client": [ 30 | { 31 | "client_id": "978007496687-1vvqhcme1bv94ukprbtvtn1l1ags2v9m.apps.googleusercontent.com", 32 | "client_type": 3 33 | } 34 | ] 35 | } 36 | } 37 | }, 38 | { 39 | "client_info": { 40 | "mobilesdk_app_id": "1:978007496687:android:311950bebdb86742f4c81a", 41 | "android_client_info": { 42 | "package_name": "com.mobiledevpro.apptemplate.new.dev" 43 | } 44 | }, 45 | "oauth_client": [ 46 | { 47 | "client_id": "978007496687-1vvqhcme1bv94ukprbtvtn1l1ags2v9m.apps.googleusercontent.com", 48 | "client_type": 3 49 | } 50 | ], 51 | "api_key": [ 52 | { 53 | "current_key": "AIzaSyBveOwDSB2vQMAyw3K3lgU-V6QB7mArqG0" 54 | } 55 | ], 56 | "services": { 57 | "appinvite_service": { 58 | "other_platform_oauth_client": [ 59 | { 60 | "client_id": "978007496687-1vvqhcme1bv94ukprbtvtn1l1ags2v9m.apps.googleusercontent.com", 61 | "client_type": 3 62 | } 63 | ] 64 | } 65 | } 66 | } 67 | ], 68 | "configuration_version": "1" 69 | } -------------------------------------------------------------------------------- /common/src/main/kotlin/com/mobiledevpro/common/ui/coroutines/BaseCoroutinesFLowUseCase.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | package com.mobiledevpro.common.ui.coroutines 19 | 20 | import android.util.Log 21 | import kotlinx.coroutines.CoroutineDispatcher 22 | import kotlinx.coroutines.Dispatchers 23 | import kotlinx.coroutines.flow.Flow 24 | import kotlinx.coroutines.flow.flowOf 25 | import kotlinx.coroutines.flow.flowOn 26 | import kotlinx.coroutines.flow.map 27 | 28 | /** 29 | * Base UseCase for Coroutines Flow result 30 | * 31 | * Created on Sep 12, 2022. 32 | * 33 | */ 34 | abstract class BaseCoroutinesFLowUseCase( 35 | executionDispatcher: CoroutineDispatcher 36 | ) : BaseUseCase(executionDispatcher) { 37 | 38 | abstract fun buildUseCaseFlow(params: Params? = null): Flow 39 | 40 | fun execute(params: Params? = null): Flow> = 41 | try { 42 | if (dispatcher == Dispatchers.Main) 43 | throw RuntimeException("Use case '${this::class.simpleName}' cannot be executed in $dispatcher") 44 | 45 | this.buildUseCaseFlow(params) 46 | .flowOn(dispatcher) 47 | .map { 48 | resultOf { it } 49 | } 50 | } catch (e: Exception) { 51 | logException(e) 52 | flowOf(Result.failure(Throwable(e.localizedMessage))) 53 | } 54 | 55 | override fun logException(e: Exception) { 56 | Log.e(this::class.simpleName, "${this::class.simpleName} : ${e.localizedMessage}") 57 | } 58 | } -------------------------------------------------------------------------------- /core/ui/src/main/res/drawable/ic_google_circle_white_56dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 16 | 21 | 26 | 31 | 32 | -------------------------------------------------------------------------------- /feature/chat_core/src/main/kotlin/com/mobiledevpro/chat/core/view/extension/ImageViewExtension.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 | Dmitri Chernysh | http://mobile-dev.pro 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | package com.mobiledevpro.chat.core.view.extension 19 | 20 | import android.annotation.SuppressLint 21 | import android.net.Uri 22 | import android.util.Log 23 | import android.widget.ImageView 24 | import androidx.databinding.BindingAdapter 25 | import com.bumptech.glide.Glide 26 | import com.bumptech.glide.load.engine.DiskCacheStrategy 27 | import com.mobiledevpro.utils.LOG_TAG_DEBUG 28 | 29 | /** 30 | * Extension 31 | * 32 | * Created on Dec 22, 2020. 33 | * 34 | */ 35 | 36 | /* 37 | * It uses for DataBinding 38 | * 39 | * NOTE: it has to be placed in the same module where layouts are 40 | */ 41 | object ImageViewExtension { 42 | 43 | @SuppressLint("CheckResult") 44 | @BindingAdapter( 45 | value = [ 46 | "bind:imageUrl", 47 | "bind:isCircle" 48 | ], 49 | requireAll = false 50 | ) 51 | @JvmStatic 52 | fun ImageView.setImageUrl(imageUri: Uri?, isCircle: Boolean? = true) { 53 | Glide.with(context) 54 | .clear(this) 55 | 56 | imageUri ?: return 57 | 58 | Log.d(LOG_TAG_DEBUG, "setImageUrl: $imageUri") 59 | Log.d(LOG_TAG_DEBUG, "isCircle: $isCircle") 60 | 61 | Glide.with(context) 62 | .load(imageUri) 63 | .centerCrop() 64 | .diskCacheStrategy(DiskCacheStrategy.NONE) 65 | .apply { 66 | isCircle?.let { 67 | Log.d(LOG_TAG_DEBUG, "isCircle: $it") 68 | if (it) 69 | this.circleCrop() 70 | } 71 | } 72 | .into(this) 73 | } 74 | } -------------------------------------------------------------------------------- /feature/chat_main/src/main/kotlin/com/mobiledevpro/chat/main/view/RecyclerViewExtension.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 | Dmitri Chernysh | http://mobile-dev.pro 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | package com.mobiledevpro.chat.main.view 19 | 20 | import androidx.databinding.BindingAdapter 21 | import androidx.recyclerview.widget.DefaultItemAnimator 22 | import androidx.recyclerview.widget.RecyclerView 23 | import com.mobiledevpro.chat.core.view.recycler.RecyclerItem 24 | import com.mobiledevpro.chat.core.view.recycler.RecyclerViewAdapter 25 | import com.mobiledevpro.chat.core.view.recycler.RecyclerViewHandler 26 | 27 | /** 28 | * Extension for RecyclerView with Data Binding 29 | * 30 | * Created on Dec 15, 2020. 31 | * 32 | */ 33 | object RecyclerViewExtension { 34 | @BindingAdapter( 35 | value = [ 36 | "bind:items", 37 | "bind:eventHandler" ], 38 | requireAll = false 39 | ) 40 | @JvmStatic 41 | fun RecyclerView.setItems( 42 | items: List?, 43 | handler: RecyclerViewHandler? 44 | ) { 45 | this.itemAnimator = DefaultItemAnimator() 46 | 47 | //This approach adds some vertical space we don't need 48 | // val divider = DividerItemDecoration(context, DividerItemDecoration.VERTICAL); 49 | // divider.setDrawable(BaseResourcesHelper.getDrawableCompatible(context, R.drawable.item_divider)) 50 | // this.addItemDecoration(divider) 51 | 52 | var adapter = (this.adapter as? RecyclerViewAdapter) 53 | if (adapter == null) { 54 | adapter = RecyclerViewAdapter() 55 | this.adapter = adapter 56 | } 57 | 58 | adapter.setEventHandler(handler) 59 | adapter.updateData(items.orEmpty()) 60 | 61 | this.scrollToPosition(adapter.itemCount - 1) 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /feature/chat_core/src/main/res/layout/item_chat_message_send.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 15 | 16 | 17 | 18 | 27 | 28 | 35 | 36 | 45 | 46 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /feature/chat_main/src/main/kotlin/com/mobiledevpro/chat/main/domain/usecase/GetPublicChatMessagesUseCase.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | package com.mobiledevpro.chat.main.domain.usecase 19 | 20 | import android.util.Log 21 | import com.mobiledevpro.chat.core.data.model.ChatMessageData 22 | import com.mobiledevpro.chat.core.domain.model.ChatMessage 23 | import com.mobiledevpro.chat.core.domain.model.ChatUser 24 | import com.mobiledevpro.chat.core.mapper.toData 25 | import com.mobiledevpro.chat.core.mapper.toDomain 26 | import com.mobiledevpro.chat.main.data.repository.ChatPublicRepository 27 | import com.mobiledevpro.common.ui.coroutines.BaseCoroutinesFLowUseCase 28 | import kotlinx.coroutines.CoroutineDispatcher 29 | import kotlinx.coroutines.flow.* 30 | 31 | /** 32 | * Use case to get public chat messages 33 | * 34 | * Created on Sep 06, 2022. 35 | * 36 | */ 37 | class GetPublicChatMessagesUseCase( 38 | defaultDispatcher: CoroutineDispatcher, 39 | private val repository: ChatPublicRepository 40 | ) : BaseCoroutinesFLowUseCase, ChatUser>(defaultDispatcher) { 41 | 42 | override fun buildUseCaseFlow(params: ChatUser?): Flow> = 43 | params?.toData()?.let { user -> 44 | repository.getMessagesList(user) 45 | .map(List::toDomain) 46 | } ?: throw RuntimeException("Unknown chat user") 47 | 48 | override fun logException(e: Exception) { 49 | //Add CrashlyticsUtil to core:utils 50 | // Add extension : Exception.toCrashlytics() 51 | // Crashlytics.log(Log.ERROR, this::class.simpleName, e.localizedMessage) 52 | // Crashlytics.logException(Throwable(e.localizedMessage)) 53 | Log.w("app.debug", "logException: in GetPublicChatMessagesUseCase") 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /core/ui/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 36 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /common/src/main/kotlin/com/mobiledevpro/common/ui/livedata/SingleLiveData.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 http://mobile-dev.pro 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.mobiledevpro.common.ui.livedata 18 | 19 | import androidx.annotation.MainThread 20 | import androidx.lifecycle.LifecycleOwner 21 | import androidx.lifecycle.MutableLiveData 22 | import androidx.lifecycle.Observer 23 | import java.util.concurrent.atomic.AtomicBoolean 24 | 25 | /** 26 | * A lifecycle-aware observable that sends only new updates after subscription, used for events 27 | * like navigation and Snackbar messages. 28 | * 29 | * This avoids a common problem with events: on configuration change (like rotation) an update 30 | * can be emitted if the observer is active. This LiveData only calls the observable if there's an 31 | * explicit call to setValue() or call(). 32 | * 33 | * Note that only one observer is going to be notified of changes. 34 | * 35 | * @see MutableLiveData 36 | */ 37 | class SingleLiveData : MutableLiveData() { 38 | 39 | private val pending = AtomicBoolean() 40 | 41 | /** 42 | * Adds the given observer to the observers list within the lifespan of the given 43 | * owner. The events are dispatched on the main thread. If LiveData already has data 44 | * set, it will be delivered to the observer. 45 | * 46 | * @param owner The LifecycleOwner which controls the observer 47 | * @param observer The observer that will receive the events 48 | * @see MutableLiveData.observe 49 | */ 50 | @MainThread 51 | override fun observe(owner: LifecycleOwner, observer: Observer) { 52 | super.observe(owner, { t -> 53 | if (pending.compareAndSet(true, false)) { 54 | observer.onChanged(t) 55 | } 56 | }) 57 | } 58 | 59 | /** 60 | * Sets the value. If there are active observers, the value will be dispatched to them. 61 | * 62 | * @param value The new value 63 | * @see MutableLiveData.setValue 64 | */ 65 | @MainThread 66 | override fun setValue(value: T?) { 67 | pending.set(true) 68 | super.setValue(value) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /feature/chat_core/src/main/kotlin/com/mobiledevpro/chat/core/view/recycler/RecyclerViewAdapter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 | Dmitri Chernysh | http://mobile-dev.pro 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | package com.mobiledevpro.chat.core.view.recycler 19 | 20 | import android.view.LayoutInflater 21 | import android.view.ViewGroup 22 | import androidx.databinding.DataBindingUtil 23 | import androidx.databinding.ViewDataBinding 24 | import androidx.recyclerview.widget.RecyclerView 25 | 26 | /** 27 | * Common adapter for Recycler Views 28 | * 29 | * Created on Dec 15, 2020. 30 | * 31 | */ 32 | class RecyclerViewAdapter : RecyclerView.Adapter() { 33 | 34 | private var items = ArrayList() 35 | private var eventHandler: RecyclerViewHandler? = null 36 | 37 | override fun onCreateViewHolder(parent: ViewGroup, 38 | viewType: Int): BindingViewHolder { 39 | val inflater = LayoutInflater.from(parent.context) 40 | val binding: ViewDataBinding = DataBindingUtil.inflate(inflater, viewType, parent, false) 41 | return BindingViewHolder(binding) 42 | } 43 | 44 | override fun onBindViewHolder(holder: BindingViewHolder, 45 | position: Int) { 46 | getItem(position).bind(holder.binding, eventHandler) 47 | holder.binding.executePendingBindings() 48 | } 49 | 50 | override fun getItemCount(): Int { 51 | return items.size 52 | } 53 | 54 | override fun getItemViewType(position: Int): Int { 55 | return getItem(position).layoutId 56 | } 57 | 58 | fun setEventHandler(handler: RecyclerViewHandler?) { 59 | eventHandler = handler 60 | } 61 | 62 | fun updateData(newItems: List) { 63 | //TODO: write a correct updating without clearing a whole list 64 | 65 | this.items.clear() 66 | this.items.addAll(newItems) 67 | notifyDataSetChanged() 68 | } 69 | 70 | 71 | private fun getItem(position: Int): RecyclerItem { 72 | return items[position] 73 | } 74 | 75 | } 76 | 77 | class BindingViewHolder( 78 | val binding: ViewDataBinding 79 | ) : RecyclerView.ViewHolder(binding.root) -------------------------------------------------------------------------------- /feature/chat_main/src/main/kotlin/com/mobiledevpro/chat/main/di/Module.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 | Dmitri Chernysh | http://mobile-dev.pro 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | package com.mobiledevpro.chat.main.di 19 | 20 | import com.mobiledevpro.chat.main.data.local.ChatPublicLocalSource 21 | import com.mobiledevpro.chat.main.data.local.ImplChatPublicLocalSource 22 | import com.mobiledevpro.chat.main.data.repository.ChatPublicRepository 23 | import com.mobiledevpro.chat.main.data.repository.ImplChatPublicRepository 24 | import com.mobiledevpro.chat.main.domain.interactor.ChatPublicInteractor 25 | import com.mobiledevpro.chat.main.domain.interactor.ImplChatPublicInteractor 26 | import com.mobiledevpro.chat.main.domain.usecase.GetCurrentUserUseCase 27 | import com.mobiledevpro.chat.main.domain.usecase.GetPublicChatMessagesUseCase 28 | import com.mobiledevpro.chat.main.view.ChatPublicFragment 29 | import com.mobiledevpro.chat.main.view.ChatPublicViewModel 30 | import kotlinx.coroutines.Dispatchers 31 | import org.koin.androidx.viewmodel.dsl.viewModel 32 | import org.koin.dsl.module 33 | 34 | /** 35 | * Koin module 36 | * 37 | * Created on Dec 11, 2020. 38 | * 39 | */ 40 | 41 | val featureChatMainModule = module { 42 | scope { 43 | viewModel { 44 | ChatPublicViewModel( 45 | resourcesProvider = get(), 46 | interactor = get() 47 | ) 48 | } 49 | 50 | scoped { 51 | ImplChatPublicInteractor( 52 | getPublicChatMessagesUseCase = get(), 53 | getCurrentUserUseCase = get() 54 | ) 55 | } 56 | 57 | scoped { 58 | GetPublicChatMessagesUseCase( 59 | defaultDispatcher = Dispatchers.IO, 60 | repository = get() 61 | ) 62 | } 63 | 64 | scoped { 65 | GetCurrentUserUseCase(Dispatchers.IO) 66 | } 67 | 68 | scoped { 69 | ImplChatPublicRepository( 70 | localSource = get() 71 | ) 72 | } 73 | 74 | scoped { 75 | ImplChatPublicLocalSource() 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /feature/chat_core/src/main/res/layout/item_chat_message_received.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 12 | 13 | 16 | 17 | 18 | 19 | 28 | 29 | 39 | 40 | 47 | 48 | 57 | 58 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /feature/chat_main/src/main/kotlin/com/mobiledevpro/chat/main/data/local/ImplChatPublicLocalSource.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | package com.mobiledevpro.chat.main.data.local 19 | 20 | import android.util.Log 21 | import androidx.core.net.toUri 22 | import com.mobiledevpro.chat.core.data.model.ChatMessageData 23 | import com.mobiledevpro.chat.core.data.model.ChatUserData 24 | import com.mobiledevpro.utils.LOG_TAG_DEBUG 25 | import kotlinx.coroutines.delay 26 | import kotlinx.coroutines.flow.Flow 27 | import kotlinx.coroutines.flow.flow 28 | import java.util.* 29 | 30 | /** 31 | * Local source to get data from database 32 | * 33 | * Created on Sep 12, 2022. 34 | * 35 | */ 36 | class ImplChatPublicLocalSource : ChatPublicLocalSource { 37 | 38 | override fun getFakeMessagesList(chatUser: ChatUserData): Flow> = 39 | flow { 40 | Log.d(LOG_TAG_DEBUG, "getFakeMessagesList: Thread : ${Thread.currentThread().name}") 41 | 42 | val messagesList = ArrayList() 43 | 44 | val userFirst = ChatUserData( 45 | UUID.randomUUID().toString(), 46 | "Fake Name", 47 | "https://i.pravatar.cc/128?img=5".toUri(), 48 | false 49 | ) 50 | 51 | val userSecond = ChatUserData( 52 | UUID.randomUUID().toString(), 53 | "Fake Name", 54 | null, 55 | true 56 | ) 57 | 58 | val messages = listOf( 59 | "Wuz Up! Lorem Ipsum is simply dummy text of printing", 60 | "How are you? =)", 61 | "It has survived not only five centuries, but also the leap into electronic typesetting", 62 | "Contrary to popular belief. is the Lorem Ipsum is not simply then random text", 63 | "Hi. I want to see you!", 64 | "Yeah. Me too. Let's go out" 65 | ) 66 | 67 | messages.forEachIndexed { index, message -> 68 | 69 | ChatMessageData( 70 | UUID.randomUUID().toString(), 71 | Date().time, 72 | message, 73 | if (index % 2 == 0) userFirst else userSecond 74 | ) 75 | .let(messagesList::add) 76 | .also { 77 | emit(messagesList) 78 | } 79 | 80 | delay(1000) 81 | } 82 | 83 | } 84 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /home/cdv/Dev/Tools/android-sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Remove all logs 20 | -assumenosideeffects class android.util.Log { 21 | public static boolean isLoggable(java.lang.String, int); 22 | public static int v(...); 23 | public static int i(...); 24 | public static int w(...); 25 | public static int d(...); 26 | #public static int e(...); 27 | } 28 | 29 | # Keep EventBus 30 | -keepattributes *Annotation* 31 | -keepclassmembers class ** { 32 | @org.greenrobot.eventbus.Subscribe ; 33 | } 34 | -keep enum org.greenrobot.eventbus.ThreadMode { *; } 35 | 36 | #Crashlytics 37 | -keepattributes *Annotation* 38 | -keepattributes SourceFile,LineNumberTable 39 | -keep public class * extends java.lang.Exception 40 | 41 | #keep retrofit2 42 | -dontwarn retrofit2.** 43 | -keep class retrofit2.** { *; } 44 | # Platform calls Class.forName on types which do not exist on Android to determine platform. 45 | -dontnote retrofit2.Platform 46 | # Platform used when running on RoboVM on iOS. Will not be used at runtime. 47 | -dontnote retrofit2.Platform$IOS$MainThreadExecutor 48 | # Platform used when running on Java 8 VMs. Will not be used at runtime. 49 | -dontwarn retrofit2.Platform$Java8 50 | # Retain generic type information for use by reflection by converters and adapters. 51 | -keepattributes Signature 52 | # Retain declared checked exceptions for use by a Proxy instance. 53 | -keepattributes Exceptions 54 | 55 | 56 | -keepclasseswithmembers class * { 57 | @retrofit2.http.* ; 58 | } 59 | -dontwarn okio.** 60 | 61 | #keep gson 62 | -keep class com.google.gson.** { *; } 63 | 64 | #keep Glide 65 | -keep public class * implements com.bumptech.glide.module.GlideModule 66 | -keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** { 67 | **[] $VALUES; 68 | public *; 69 | } 70 | -keep class com.bumptech.glide.load.data.ParcelFileDescriptorRewinder$InternalRewinder { 71 | *** rewind(); 72 | } 73 | 74 | #keep searchview 75 | -keep class android.support.v7.widget.SearchView { *; } 76 | 77 | #keep Spinner 78 | -keep class android.widget.ThemedSpinnerAdapter { *; } 79 | 80 | #keep fragments transition 81 | -keep class android.support.v4.app.FragmentTransitionCompat21 { *; } 82 | 83 | -keep class com.android.graphics.drawable.** { *; } 84 | 85 | #THIS IS DOESN`T WORK :( keep this, because Vector animations doesn`t work with minifying on api < 21 86 | -keep class android.support.graphics.drawable.** { *; } 87 | # keep setters in VectorDrawables so that animations can still work. 88 | -keepclassmembers class android.support.graphics.drawable.VectorDrawableCompat$* { 89 | void set*(***); 90 | *** get*(); 91 | } -------------------------------------------------------------------------------- /feature/chat_main/src/main/kotlin/com/mobiledevpro/chat/main/view/ChatPublicViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.mobiledevpro.chat.main.view 2 | 3 | import android.util.Log 4 | import androidx.lifecycle.* 5 | import com.mobiledevpro.app.helper.ResourcesProvider 6 | import com.mobiledevpro.chat.core.view.mapper.toRecyclerView 7 | import com.mobiledevpro.chat.core.view.recycler.RecyclerItem 8 | import com.mobiledevpro.chat.core.view.recycler.RecyclerViewHandler 9 | import com.mobiledevpro.chat.main.domain.interactor.ChatPublicInteractor 10 | import com.mobiledevpro.utils.LOG_TAG_DEBUG 11 | import kotlinx.coroutines.CoroutineScope 12 | import kotlinx.coroutines.Dispatchers 13 | import kotlinx.coroutines.cancel 14 | import kotlinx.coroutines.launch 15 | 16 | 17 | /** 18 | * ViewModel for public chat 19 | * 20 | */ 21 | 22 | class ChatPublicViewModel( 23 | private val resourcesProvider: ResourcesProvider, 24 | private val interactor: ChatPublicInteractor 25 | ) : ViewModel(), DefaultLifecycleObserver { 26 | 27 | private val coroutinesScope = CoroutineScope(Dispatchers.IO) 28 | 29 | private val _listMessages = MutableLiveData?>() 30 | val listMessages: LiveData?> = _listMessages 31 | 32 | private val _errorMessage = MutableLiveData() 33 | val errorMessage: LiveData = _errorMessage 34 | 35 | val listEventHandler = object : RecyclerViewHandler { 36 | override fun onClickItem(item: Any) { 37 | 38 | //TODO: handle clicking on items, if needed 39 | } 40 | } 41 | 42 | 43 | init { 44 | observeMessagesList() 45 | } 46 | 47 | override fun onDestroy(owner: LifecycleOwner) { 48 | Log.d(LOG_TAG_DEBUG, "onDestroy: ") 49 | viewModelScope.cancel() 50 | } 51 | 52 | fun isLoadingAnimationVisible(): LiveData { 53 | val isVisible = MediatorLiveData() 54 | 55 | val b: () -> Boolean = { 56 | listMessages.value?.isEmpty() ?: true 57 | } 58 | 59 | isVisible.value = b() 60 | 61 | isVisible.addSource(listMessages) { 62 | isVisible.value = b() 63 | } 64 | 65 | return isVisible 66 | } 67 | 68 | private fun observeMessagesList() { 69 | viewModelScope.launch { 70 | interactor.getMessagesList() 71 | .collect { 72 | it.onSuccess { messagesList -> 73 | _listMessages.value = messagesList.toRecyclerView() 74 | Log.d(LOG_TAG_DEBUG, "observeMessagesList: onSuccess: $messagesList ") 75 | Log.d( 76 | LOG_TAG_DEBUG, 77 | "observeMessagesList: onSuccess: Thread ${Thread.currentThread().name} " 78 | ) 79 | }.onFailure { 80 | Log.e( 81 | LOG_TAG_DEBUG, 82 | "observeMessagesList: onFailure: ${it.localizedMessage} " 83 | ) 84 | Log.e( 85 | LOG_TAG_DEBUG, 86 | "observeMessagesList: onFailure: Thread ${Thread.currentThread().name} " 87 | ) 88 | } 89 | } 90 | 91 | } 92 | 93 | 94 | } 95 | } -------------------------------------------------------------------------------- /feature/profile_settings/src/main/kotlin/com/mobiledevpro/profile/settings/view/ProfileSettingsFragment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 | Dmitri Chernysh | http://mobile-dev.pro 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | package com.mobiledevpro.profile.settings.view 19 | 20 | import com.mobiledevpro.common.ui.base.BaseFragment 21 | import com.mobiledevpro.common.ui.base.FragmentSettings 22 | import com.mobiledevpro.profile.settings.R 23 | import com.mobiledevpro.profile.settings.databinding.FragmentProfileSettingsBinding 24 | import com.mobiledevpro.profile.settings.di.featureProfileSettingsModule 25 | import org.koin.core.component.KoinScopeComponent 26 | import org.koin.core.component.getOrCreateScope 27 | import org.koin.core.context.loadKoinModules 28 | import org.koin.core.scope.Scope 29 | import com.mobiledevpro.navigation.R as RNav 30 | import com.mobiledevpro.ui.R as RApp 31 | import com.mobiledevpro.ui.R as RUi 32 | 33 | /** 34 | * Profile Settings screen 35 | * 36 | * Created on Jan 26, 2021. 37 | * 38 | */ 39 | class ProfileSettingsFragment : BaseFragment( 40 | layoutId = R.layout.fragment_profile_settings, 41 | FragmentSettings( 42 | statusBarColor = RUi.attr.themeColorWindowBackgroundDark, 43 | appBarColor = RUi.attr.themeColorWindowBackgroundDark, 44 | appBarTitle = 0, 45 | appBarTitleColor = RApp.color.colorWindowGreyBackground, 46 | navigationBarColor = RUi.attr.themeColorWindowBackgroundLight, 47 | appWindowBackground = RApp.drawable.background_window_light, 48 | homeIconId = RApp.drawable.ic_back_arrow_light_24dp, 49 | homeIconBackPressEnabled = true, 50 | enterTransition = RNav.transition.slide_right 51 | ) 52 | ), KoinScopeComponent { 53 | 54 | override val scope: Scope by getOrCreateScope() 55 | 56 | private val viewModel: ProfileSettingsViewModel 57 | by lazy(LazyThreadSafetyMode.NONE) { scope.get() } 58 | 59 | init { 60 | loadKoinModules(featureProfileSettingsModule) 61 | } 62 | 63 | override fun onInitDataBinding() { 64 | viewBinding.model = viewModel 65 | lifecycle.addObserver(viewModel) 66 | } 67 | 68 | override fun observeLifecycleEvents() { 69 | /* observe(viewModel.appbarTitle, observer = { title -> 70 | (requireActivity() is BaseActivityInterface).apply { 71 | (requireActivity() as BaseActivityInterface).setAppBarTitle(title) 72 | } 73 | }) 74 | 75 | observe(viewModel.eventNavigateTo, observer = { navigation -> 76 | try { 77 | openScreen(navigation) 78 | } catch (e: RuntimeException) { 79 | val err = e.localizedMessage 80 | if (!err.isNullOrEmpty()) 81 | showErrorDialog(err) 82 | } 83 | }) 84 | 85 | */ 86 | } 87 | 88 | 89 | } -------------------------------------------------------------------------------- /core/database/src/androidTest/java/com/mobiledevpro/database/RoomMigrationTest.java: -------------------------------------------------------------------------------- 1 | package com.mobiledevpro.database; 2 | 3 | import org.junit.Rule; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | 7 | import java.io.IOException; 8 | 9 | import androidx.room.testing.MigrationTestHelper; 10 | import androidx.test.ext.junit.runners.AndroidJUnit4; 11 | 12 | /** 13 | * Test a database migrations 14 | *

15 | * Created by Dmitri Chernysh 16 | *

17 | * http://mobile-dev.pro 18 | */ 19 | @RunWith(AndroidJUnit4.class) 20 | public class RoomMigrationTest { 21 | // private static final String TEST_DB = BuildConfig.AppDatabaseName; 22 | 23 | @Rule 24 | public MigrationTestHelper helper; 25 | 26 | public RoomMigrationTest() { 27 | /* helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(), 28 | AppDatabase.class.getCanonicalName(), 29 | new FrameworkSQLiteOpenHelperFactory()); 30 | 31 | */ 32 | } 33 | 34 | @Test 35 | public void migrate5to6() throws IOException { 36 | /* int currentVersion = 5; 37 | int newVersion = 6; 38 | int examQuestionId = 11111111; 39 | // Create earliest version of the database. 40 | SupportSQLiteDatabase dbCurrent = helper.createDatabase(TEST_DB, currentVersion); 41 | dbCurrent.close(); 42 | 43 | //make project before run this 44 | try { 45 | helper.runMigrationsAndValidate(TEST_DB, newVersion, true, AppDatabase.MIGRATION_5_6); 46 | } catch (IllegalStateException e) { 47 | throw new IOException("Make project before run test for migration from 5 to 6. " + e.getLocalizedMessage()); 48 | } 49 | 50 | //test inserting 51 | AppDatabase newDb = getMigratedDatabase(); 52 | long[] result = newDb.examDao() 53 | .insertQuestionAttachments( 54 | buildQuestionAttachmentToInsert(examQuestionId) 55 | ); 56 | 57 | if (result.length == 0) 58 | throw new IOException("ExamQuestionAttachment is not inserting into a new database"); 59 | 60 | */ 61 | } 62 | 63 | 64 | // Array of all migrations 65 | /* private static final Migration[] ALL_MIGRATIONS = 66 | new Migration[]{ 67 | AppDatabase.MIGRATION_5_6 68 | }; 69 | 70 | private AppDatabase getMigratedDatabase() { 71 | // Open latest version of the database. Room will validate the schema 72 | // once all migrations execute. 73 | AppDatabase appDb = Room.databaseBuilder( 74 | InstrumentationRegistry.getInstrumentation().getTargetContext(), 75 | AppDatabase.class, 76 | TEST_DB) 77 | .addMigrations(ALL_MIGRATIONS).build(); 78 | appDb.getOpenHelper().getWritableDatabase(); 79 | appDb.close(); 80 | return appDb; 81 | } 82 | 83 | private ArrayList buildQuestionAttachmentToInsert(int questionId) { 84 | ExamQuestionAttachment.File file = new ExamQuestionAttachment.File(); 85 | file.setUploadTime(1593188528); 86 | 87 | ExamQuestionAttachment attachment = new ExamQuestionAttachment(); 88 | attachment.setQuestionPrimaryId(questionId); 89 | attachment.setFileName("test.jpg"); 90 | attachment.setFileType("image"); 91 | attachment.setWebUrlHash("56f4as4df654as6df46as4d6f6"); 92 | attachment.setStatus(2); 93 | attachment.setFilePath(""); 94 | attachment.setFile(file); 95 | 96 | ArrayList list = new ArrayList<>(); 97 | list.add(attachment); 98 | 99 | return list; 100 | } 101 | 102 | */ 103 | 104 | } 105 | -------------------------------------------------------------------------------- /core/ui/src/main/res/values/colors_material3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #1C2E46 5 | #1C2E46 6 | #FFFFFF 7 | #E5E5E5 8 | #001C39 9 | #00658E 10 | #FFFFFF 11 | #C7E7FF 12 | #001E2E 13 | #006972 14 | #FFFFFF 15 | #8AF2FF 16 | #001F23 17 | #BA1A1A 18 | #FFDAD6 19 | #FFFFFF 20 | #410002 21 | #F6FEFF 22 | #001F24 23 | #F6FEFF 24 | #001F24 25 | #DFE2EB 26 | #43474E 27 | #73777F 28 | #D0F8FF 29 | #00363D 30 | #A4C9FF 31 | #000000 32 | #1B60A5 33 | #1B60A5 34 | #A4C9FF 35 | #00315D 36 | #004884 37 | #D4E3FF 38 | #84CFFF 39 | #00344C 40 | #004C6C 41 | #C7E7FF 42 | #4ED8E7 43 | #00363B 44 | #004F56 45 | #8AF2FF 46 | #FFB4AB 47 | #93000A 48 | #690005 49 | #FFDAD6 50 | #001F24 51 | #97F0FF 52 | #001F24 53 | #97F0FF 54 | #43474E 55 | #C3C6CF 56 | #8D9199 57 | #001F24 58 | #97F0FF 59 | #1B60A5 60 | #000000 61 | #A4C9FF 62 | #A4C9FF 63 | -------------------------------------------------------------------------------- /common/src/main/kotlin/com/mobiledevpro/common/ui/coroutines/ResultExt.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | package com.mobiledevpro.common.ui.coroutines 19 | 20 | import kotlinx.coroutines.CancellationException 21 | import kotlinx.coroutines.TimeoutCancellationException 22 | import kotlinx.coroutines.flow.Flow 23 | import kotlinx.coroutines.flow.flowOf 24 | import kotlin.contracts.ExperimentalContracts 25 | import kotlin.contracts.InvocationKind 26 | import kotlin.contracts.contract 27 | 28 | 29 | // Based on: https://proandroiddev.com/resilient-use-cases-with-kotlin-result-coroutines-and-annotations-511df10e2e16 30 | 31 | /** 32 | * Like [runCatching], but with proper coroutines cancellation handling. Also only catches [Exception] instead of [Throwable]. 33 | * 34 | * Cancellation exceptions need to be rethrown. See https://github.com/Kotlin/kotlinx.coroutines/issues/1814. 35 | */ 36 | /* 37 | inline fun resultOf(block: () -> R): Result { 38 | return try { 39 | Result.success(block()) 40 | } catch (t: TimeoutCancellationException) { 41 | Result.failure(t) 42 | } catch (c: CancellationException) { 43 | throw c 44 | } catch (e: Exception) { 45 | Result.failure(e) 46 | } 47 | } 48 | 49 | */ 50 | 51 | /** 52 | * Like [runCatching], but with proper coroutines cancellation handling. Also only catches [Exception] instead of [Throwable]. 53 | * 54 | * Cancellation exceptions need to be rethrown. See https://github.com/Kotlin/kotlinx.coroutines/issues/1814. 55 | */ 56 | 57 | inline fun T.resultOf(block: T.() -> R): Result { 58 | return try { 59 | Result.success(block()) 60 | } catch (t: TimeoutCancellationException) { 61 | Result.failure(t) 62 | } catch (c: CancellationException) { 63 | throw c 64 | } catch (e: Exception) { 65 | Result.failure(e) 66 | } 67 | } 68 | 69 | /** 70 | * Like [mapCatching], but uses [resultOf] instead of [runCatching]. 71 | */ 72 | 73 | inline fun Result.mapResult(transform: (value: T) -> R): Result { 74 | val successResult = getOrNull() 75 | return when { 76 | successResult != null -> resultOf { transform(successResult) } 77 | else -> Result.failure(exceptionOrNull() ?: error("Unreachable state")) 78 | } 79 | } 80 | 81 | @OptIn(ExperimentalContracts::class) 82 | inline fun Result.andThen(transform: (value: T) -> Result): Result { 83 | contract { 84 | callsInPlace(transform, InvocationKind.AT_MOST_ONCE) 85 | } 86 | 87 | val successResult = getOrNull() 88 | return when { 89 | successResult != null -> transform(successResult) 90 | else -> Result.failure(exceptionOrNull() ?: error("Unreachable state")) 91 | } 92 | } 93 | 94 | 95 | @OptIn(ExperimentalContracts::class) 96 | inline fun Result.andThenFlow(transform: (value: T) -> Flow>): Flow> { 97 | contract { 98 | callsInPlace(transform, InvocationKind.AT_MOST_ONCE) 99 | } 100 | 101 | val successResult: T? = getOrNull() 102 | 103 | return when { 104 | successResult != null -> transform(successResult) 105 | else -> flowOf(Result.failure(exceptionOrNull() ?: error("Unreachable state"))) 106 | } 107 | } 108 | 109 | 110 | class None 111 | -------------------------------------------------------------------------------- /feature/chat_main/src/main/res/layout/fragment_chat_public.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 28 | 29 | 41 | 42 | 43 | 44 | 53 | 54 | 61 | 62 | 69 | 70 | 71 | 72 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /common/src/main/kotlin/com/mobiledevpro/common/ui/lifecycle/RuntimePermissionObserver.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 | Dmitri Chernysh | http://mobile-dev.pro 3 | * 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | package com.mobiledevpro.common.ui.lifecycle 19 | 20 | import android.content.pm.PackageManager 21 | import androidx.activity.result.ActivityResultLauncher 22 | import androidx.activity.result.contract.ActivityResultContracts 23 | import androidx.core.app.ActivityCompat 24 | import androidx.core.content.ContextCompat 25 | import androidx.fragment.app.FragmentActivity 26 | import androidx.lifecycle.Lifecycle 27 | import androidx.lifecycle.LifecycleObserver 28 | import androidx.lifecycle.OnLifecycleEvent 29 | 30 | /** 31 | * Observer to request and handle Runtime permissions 32 | * 33 | * Created on Apr 23, 2021. 34 | * 35 | */ 36 | class RuntimePermissionObserver( 37 | private val activity: FragmentActivity 38 | ) : LifecycleObserver { 39 | 40 | private var onGranted: () -> Unit = {} 41 | private var onDenied: () -> Unit = {} 42 | private var onShouldShowRationale: () -> Unit = {} 43 | 44 | private lateinit var launcher: ActivityResultLauncher> 45 | 46 | @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) 47 | fun onCreate() { 48 | launcher = activity 49 | .activityResultRegistry 50 | .register(this.javaClass.name, ActivityResultContracts.RequestMultiplePermissions()) { 51 | 52 | var isGranted = false 53 | 54 | for (result in it) { 55 | isGranted = result.value 56 | } 57 | 58 | if (isGranted) 59 | onGranted() 60 | else 61 | onDenied() 62 | 63 | } 64 | } 65 | 66 | fun launch( 67 | permissions: Array, 68 | onGranted: () -> Unit = {}, 69 | onDenied: () -> Unit = {}, 70 | onShouldShowRationale: () -> Unit = {} 71 | ) { 72 | this.onGranted = onGranted 73 | this.onDenied = onDenied 74 | this.onShouldShowRationale = onShouldShowRationale 75 | 76 | when { 77 | // You can use the API that requires the permission. 78 | permissions.checkSelfPermission() -> onGranted() 79 | // In an educational UI, explain to the user why your app requires this 80 | // permission for a specific feature to behave as expected. In this UI, 81 | // include a "cancel" or "no thanks" button that allows the user to 82 | // continue using your app without granting the permission. 83 | permissions.checkShouldShowRationale(activity) -> onShouldShowRationale() 84 | 85 | else -> launcher.launch(permissions) 86 | } 87 | } 88 | 89 | private fun Array.checkSelfPermission(): Boolean { 90 | 91 | var isGranted = false 92 | 93 | for (permission in this) { 94 | 95 | isGranted = ContextCompat.checkSelfPermission( 96 | activity, 97 | permission 98 | ) == PackageManager.PERMISSION_GRANTED 99 | 100 | //if at least one permission is not granted, stop checking 101 | if (!isGranted) break 102 | } 103 | 104 | return isGranted 105 | } 106 | 107 | private fun Array.checkShouldShowRationale( 108 | activity: FragmentActivity 109 | ): Boolean { 110 | 111 | var isShouldShow = false 112 | 113 | for (permission in this) { 114 | isShouldShow = ActivityCompat.shouldShowRequestPermissionRationale(activity, permission) 115 | 116 | //if at least one permission should be rationale, stop checking 117 | if (isShouldShow) break 118 | } 119 | 120 | return isShouldShow 121 | } 122 | } -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | config_docker: &config_docker 4 | docker: 5 | - image: cimg/android:2021.10.2 6 | resource_class: large 7 | working_directory: ~/code 8 | 9 | config_android: &config_env 10 | environment: 11 | JAVA_HOME: "/usr/lib/jvm/java-11-openjdk-amd64" 12 | JAVA_TOOL_OPTIONS: "-Xmx4096m -XX:+UseParallelGC" 13 | GRADLE_OPTS: "-Dorg.gradle.daemon=true -Dorg.gradle.workers.max=2 -Dkotlin.incremental=true -Dkotlin.compiler.execution.strategy=in-process" 14 | TERM: dumb 15 | 16 | update_sdk: &update_sdk 17 | run: 18 | name: Update SDK 19 | command: | 20 | yes | sdkmanager --licenses || true 21 | sdkmanager "platform-tools" "platforms;android-33" 22 | 23 | restore_cache: &restore_cache 24 | restore_cache: 25 | key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }} 26 | 27 | fix_permissions: &fix_permissions 28 | run: 29 | name: Chmod permissions #if permission for Gradlew Dependencies fail, use this. 30 | command: sudo chmod +x ./gradlew 31 | 32 | update_dependencies: &update_dependencies 33 | run: 34 | name: Download Dependencies 35 | command: ./gradlew dependencies 36 | 37 | update_cache: &update_cache 38 | save_cache: 39 | paths: 40 | - ~/.gradle/caches 41 | - ~/.gradle/wrapper 42 | key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }} 43 | 44 | decrypt_release_key: &decrypt_release_key 45 | deploy: 46 | name: Decrypt release key 47 | command: ./ci-decrypt-release-key.sh 48 | 49 | deploy_to_dropbox: &deploy_to_dropbox 50 | deploy: 51 | name: Deploy to DropBox 52 | command: ./ci-deploy-dropbox.sh 53 | 54 | cache_docker: &cache_docker 55 | setup_remote_docker: 56 | docker_layer_caching: true # it costs extra credits 57 | 58 | filter_master_only: &filter_master_only 59 | filters: 60 | branches: 61 | only: master 62 | 63 | filter_tag_version_only: &filter_tag_version_only 64 | filters: 65 | tags: 66 | only: /^v.*/ 67 | branches: 68 | ignore: /.*/ 69 | 70 | jobs: 71 | job_run_tests: 72 | <<: *config_docker 73 | <<: *config_env 74 | steps: 75 | - <<: *update_sdk 76 | - checkout 77 | # Docker caching is not available in free plans 78 | - <<: *cache_docker 79 | - <<: *restore_cache 80 | - <<: *fix_permissions 81 | - <<: *update_dependencies 82 | - <<: *update_cache 83 | - run: 84 | name: Run Tests 85 | command: ./gradlew testDevDebug 86 | - run: 87 | name: Save test results (html page, see Artifacts) 88 | command: | 89 | mkdir -p test-results/html/ 90 | find . -type d -regex ".*/*reports/tests" -exec cp -avr {} test-results/html/ \; 91 | when: always 92 | - run: 93 | name: Save test results (xml results, see Tests) 94 | command: | 95 | mkdir -p test-results/xml/ 96 | find . -type f -regex ".*/*test-results/.*xml" -exec cp {} test-results/xml/ \; 97 | when: always 98 | - store_test_results: 99 | path: test-results 100 | - store_artifacts: 101 | path: test-results 102 | destination: reports/ 103 | 104 | job_build_n_deploy_release: 105 | <<: *config_docker 106 | <<: *config_env 107 | steps: 108 | - <<: *update_sdk 109 | - checkout 110 | - <<: *restore_cache 111 | - <<: *fix_permissions 112 | - <<: *update_dependencies 113 | - <<: *update_cache 114 | - <<: *decrypt_release_key 115 | - run: 116 | name: Assemble release build 117 | command: ./gradlew clean bundleRelease --stacktrace 118 | - store_artifacts: 119 | path: app/build/outputs/apk/ 120 | destination: apks/ 121 | - run: 122 | name: Remove release key 123 | command: rm ./app/release.jks 124 | - <<: *deploy_to_dropbox 125 | 126 | 127 | workflows: 128 | version: 2 129 | 130 | # run tests on every commits to master 131 | run-tests-only: 132 | jobs: 133 | - job_run_tests 134 | 135 | # build release and deploy on tags and not branches 136 | build-n-deploy: 137 | jobs: 138 | - job_run_tests: 139 | <<: *filter_tag_version_only 140 | - job_build_n_deploy_release: 141 | <<: *filter_tag_version_only 142 | requires: 143 | - job_run_tests 144 | 145 | 146 | # See https://circleci.com/docs/2.0/deployment-integrations/ for deploy examples -------------------------------------------------------------------------------- /feature/chat_main/src/main/kotlin/com/mobiledevpro/chat/main/view/ChatPublicFragment.kt: -------------------------------------------------------------------------------- 1 | package com.mobiledevpro.chat.main.view 2 | 3 | import android.os.Build 4 | import android.os.Bundle 5 | import android.util.Log 6 | import android.view.MenuItem 7 | import android.view.View 8 | import android.view.WindowInsets.Type.ime 9 | import android.widget.Toast 10 | import androidx.annotation.RequiresApi 11 | import androidx.core.view.ViewCompat 12 | import com.mobiledevpro.chat.main.R 13 | import com.mobiledevpro.chat.main.databinding.FragmentChatPublicBinding 14 | import com.mobiledevpro.chat.main.di.featureChatMainModule 15 | import com.mobiledevpro.common.ui.base.BaseFragment 16 | import com.mobiledevpro.common.ui.base.FragmentSettings 17 | import com.mobiledevpro.common.ui.extension.observe 18 | import com.mobiledevpro.navigation.NavigateTo 19 | import com.mobiledevpro.navigation.Navigation 20 | import com.mobiledevpro.navigation.ext.launch 21 | import org.koin.android.scope.getOrCreateScope 22 | import org.koin.core.component.KoinScopeComponent 23 | import org.koin.core.context.loadKoinModules 24 | import org.koin.core.scope.Scope 25 | import com.mobiledevpro.app.R as RApp 26 | import com.mobiledevpro.navigation.R as RNav 27 | import com.mobiledevpro.ui.R as RUi 28 | 29 | 30 | /** 31 | * Main fragment for main activity 32 | * 33 | * 34 | * www.mobile-dev.pro 35 | */ 36 | 37 | class ChatPublicFragment : BaseFragment( 38 | layoutId = R.layout.fragment_chat_public, 39 | FragmentSettings( 40 | statusBarColor = RUi.attr.themeColorWindowBackgroundLight, 41 | appBarColor = RUi.attr.themeColorWindowBackgroundLight, 42 | appBarTitle = RApp.string.app_title_chat_public, 43 | appBarTitleColor = RUi.color.colorTextPrimary, 44 | navigationBarColor = RUi.attr.themeColorWindowBackgroundDark, 45 | appWindowBackground = RUi.drawable.background_window_dark, 46 | homeIconId = RUi.drawable.ic_list_dark_24dp, 47 | optionsMenuId = R.menu.menu_chat_public, 48 | homeIconBackPressEnabled = false, 49 | exitTransition = RNav.transition.slide_left 50 | ) 51 | ), KoinScopeComponent { 52 | 53 | override val scope: Scope by getOrCreateScope() 54 | 55 | private val viewModel: ChatPublicViewModel 56 | by lazy(LazyThreadSafetyMode.NONE) { scope.get() } 57 | 58 | init { 59 | loadKoinModules(featureChatMainModule) 60 | } 61 | 62 | override fun onInitDataBinding() { 63 | viewBinding.model = viewModel 64 | lifecycle.addObserver(viewModel) 65 | } 66 | 67 | override fun observeLifecycleEvents() { 68 | 69 | observe(viewModel.errorMessage, observer = { 70 | Toast.makeText(requireActivity(), it, Toast.LENGTH_SHORT).show() 71 | }) 72 | } 73 | 74 | 75 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 76 | super.onViewCreated(view, savedInstanceState) 77 | 78 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) 79 | addImeListener(view) 80 | } 81 | 82 | override fun onOptionsItemSelected(item: MenuItem): Boolean = 83 | when (item.itemId) { 84 | android.R.id.home -> { 85 | Toast.makeText( 86 | requireActivity(), 87 | "Chats list. Not implemented yet", 88 | Toast.LENGTH_SHORT 89 | ).show() 90 | true 91 | } 92 | R.id.menu_action_settings -> { 93 | // Toast.makeText(requireActivity(), "Settings. Not implemented yet", Toast.LENGTH_SHORT).show() 94 | Navigation(NavigateTo.PROFILE_SETTINGS) 95 | .let(this::launch) 96 | true 97 | } 98 | else -> super.onOptionsItemSelected(item) 99 | } 100 | 101 | @RequiresApi(Build.VERSION_CODES.R) 102 | private fun addImeListener(view: View) { 103 | ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets -> 104 | 105 | try { 106 | val imeVisible = insets.toWindowInsets()?.isVisible(ime()) ?: false 107 | // val imeHeight = insets.getInsets(Type.ime()).bottom 108 | Log.d(this::class.java.name, "imeVisible: $imeVisible") 109 | if (imeVisible) 110 | viewBinding.rvMessageList.apply { 111 | smoothScrollToPosition(this.adapter?.itemCount?.minus(1) ?: 0) 112 | } 113 | } catch (e: NoClassDefFoundError) { 114 | 115 | } 116 | insets 117 | } 118 | } 119 | 120 | 121 | } 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Android App Template | Kotlin | MVVM + Clean Architecture | Dynamic Feature modules | Android 13 support 2 | 3 | [![Kotlin Version](https://img.shields.io/badge/kotlin-1.9.10-blue.svg?style=for-the-badge)](http://kotlinlang.org/) 4 | [![Gradle](https://img.shields.io/badge/gradle-7.7-blue.svg?style=for-the-badge)](https://lv.binarybabel.org/catalog/gradle/latest) 5 | [![API](https://img.shields.io/badge/API-23%2B-blue.svg?style=for-the-badge)](https://android-arsenal.com/api?level=23) 6 | [![Target SDK](https://img.shields.io/badge/Target%20SDK-34-blue.svg?style=for-the-badge)](https://developer.android.com/about/versions/13) 7 | [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg?style=for-the-badge)](http://www.apache.org/licenses/LICENSE-2.0) 8 | 9 | [![CircleCI](https://circleci.com/gh/mobiledevpro/Android-Kotlin-MVVM-Template.svg?style=shield)](https://circleci.com/gh/mobiledevpro/Android-Kotlin-MVVM-Template 10 | ) 11 | [![CodeFactor](https://www.codefactor.io/repository/github/mobiledevpro/android-kotlin-mvvm-template/badge)](https://www.codefactor.io/repository/github/mobiledevpro/android-kotlin-mvvm-template) 12 | [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=mobiledevpro_Android-Kotlin-MVVM-Template&metric=alert_status)](https://sonarcloud.io/dashboard?id=mobiledevpro_Android-Kotlin-MVVM-Template) 13 | 14 | ![GitHub last commit](https://img.shields.io/github/last-commit/mobiledevpro/Android-Kotlin-MVVM-Template?color=red&style=for-the-badge) 15 | 16 | ![github_social_preview](github_social_preview.png) 17 | 18 | 19 | ## Under the hood: 20 | * [Clean Architecture with modularization (click to view a full scheme)](https://miro.com/app/board/uXjVOiQ2q3g=/?share_link_id=361857812650) 21 | * [Kotlin](https://developer.android.com/kotlin) 22 | * [ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel) 23 | * [LiveData](https://developer.android.com/topic/libraries/architecture/livedata) 24 | * [Data Binding](https://developer.android.com/topic/libraries/data-binding) 25 | * [RxKotlin](https://github.com/ReactiveX/RxKotlin) 26 | * [Koin](https://insert-koin.io/) 27 | * [Navigation Components](https://developer.android.com/guide/navigation/) 28 | * [Room](https://developer.android.com/topic/libraries/architecture/room) 29 | * [Retrofit](https://square.github.io/retrofit/) 30 | * [Material Components](https://github.com/material-components/material-components-android) 31 | * [Firebase Crashlytics](https://firebase.google.com/docs/crashlytics) 32 | * [Leak Canary](https://square.github.io/leakcanary/) 33 | * [Circle CI | Continuous Integration](https://circleci.com/gh/mobile-dev-pro/AppTemplate) 34 | * [My own Common-UI library (uses in production projects)](https://github.com/mobiledevpro/Android-Kotlin-MVVM-Template/tree/master/common-ui) [![](https://jitpack.io/v/mobiledevpro/android-kotlin-mvvm-template.svg)](https://jitpack.io/#mobiledevpro/android-kotlin-mvvm-template) 35 | 36 | ## How to 37 | 38 | * [Display content edge-to-edge | System insets](https://developer.android.com/develop/ui/views/layout/edge-to-edge) 39 | 40 | ## 41 | ## Author: 42 | 43 | 44 | 45 | 46 | 47 | **Dmitri Chernysh** 48 | 49 | [![Youtube](https://img.shields.io/badge/-youtube-red?logo=youtube&message=Youtube&style=for-the-badge&label=Watch+on)](https://www.youtube.com/@mobiledevpro?sub_confirmation=1) 50 | [![Instagram](https://img.shields.io/badge/-instagram-E4405F?logo=instagram&message=Behind+the+scenes+in+Storiesn&style=for-the-badge&logoColor=white)](https://www.instagram.com/mobiledevpro/) 51 | [![Twitter](https://img.shields.io/badge/-twitter-1DA1F2?logo=twitter&style=for-the-badge&logoColor=white)](https://twitter.com/mobiledev_pro) 52 | [![Linkedin](https://img.shields.io/badge/-linkedin-0A66C2?logo=linkedin&style=for-the-badge&logoColor=white)](https://www.linkedin.com/in/dmitriychernysh/) 53 | 54 | ## License: 55 | 56 | Copyright 2020 Dmitri Chernysh 57 | 58 | Licensed under the Apache License, Version 2.0 (the "License"); 59 | you may not use this file except in compliance with the License. 60 | You may obtain a copy of the License at 61 | 62 | http://www.apache.org/licenses/LICENSE-2.0 63 | 64 | Unless required by applicable law or agreed to in writing, software 65 | distributed under the License is distributed on an "AS IS" BASIS, 66 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 67 | See the License for the specific language governing permissions and 68 | limitations under the License. 69 | 70 | ## Thanks for support ! 71 | 72 | **Stargazers** 73 | 74 | [![Stargazers repo roster for @mobiledevpro/Android-Kotlin-MVVM-Template](http://reporoster.com/stars/dark/mobiledevpro/Android-Kotlin-MVVM-Template)](https://github.com/mobiledevpro/Android-Kotlin-MVVM-Template/stargazers) 75 | 76 | **Forkers** 77 | 78 | [![Forkers repo roster for @mobiledevpro/Android-Kotlin-MVVM-Template](http://reporoster.com/forks/dark/mobiledevpro/Android-Kotlin-MVVM-Template)](https://github.com/mobiledevpro/Android-Kotlin-MVVM-Template/network/members) 79 | 80 | -------------------------------------------------------------------------------- /core/ui/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 34 | 35 | 87 | 88 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'kotlin-android' 4 | id 'kotlin-kapt' 5 | id 'androidx.navigation.safeargs.kotlin' 6 | id 'com.google.gms.google-services' 7 | id 'com.google.firebase.crashlytics' 8 | id 'com.google.firebase.firebase-perf' 9 | } 10 | 11 | android { 12 | compileSdk rootProject.compileSdkVersion 13 | namespace 'com.mobiledevpro.app' 14 | 15 | defaultConfig { 16 | applicationId "com.mobiledevpro.apptemplate.new" 17 | versionName rootProject.appVersionName 18 | versionCode rootProject.appVersionCode 19 | 20 | minSdkVersion rootProject.minSdkVersion 21 | targetSdkVersion rootProject.targetSdkVersion 22 | vectorDrawables.useSupportLibrary = true 23 | multiDexEnabled false 24 | 25 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 26 | 27 | //custom naming for App Bundle builds 28 | archivesBaseName = "${applicationId}-v${versionName}-build${versionCode}" 29 | } 30 | 31 | signingConfigs { 32 | //DON"T forget to add APP_KEYPWD and APP_KSTOREPWD to ENV on CI 33 | release { 34 | try { 35 | keyAlias 'here alias value' 36 | keyPassword System.getenv("APP_KEYPWD") 37 | storeFile file('here key file name') 38 | storePassword System.getenv("APP_KSTOREPWD") 39 | } catch (e) { 40 | throw new InvalidUserDataException("You should define APP_KSTOREPWD and APP_KEYPWD in the system ENV. " + e) 41 | } 42 | } 43 | } 44 | packagingOptions { 45 | resources { 46 | excludes += ['META-INF/*.kotlin_module'] 47 | } 48 | } 49 | 50 | 51 | compileOptions { 52 | sourceCompatibility JavaVersion.VERSION_17 53 | targetCompatibility JavaVersion.VERSION_17 54 | } 55 | 56 | kotlinOptions { 57 | jvmTarget = JavaVersion.VERSION_17.toString() 58 | } 59 | 60 | buildTypes { 61 | debug { 62 | debuggable true 63 | firebaseCrashlytics { 64 | // False - don't need crash reporting for debug build. 65 | mappingFileUploadEnabled false 66 | } 67 | } 68 | release { 69 | debuggable false 70 | minifyEnabled true 71 | shrinkResources false 72 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 73 | // signingConfig signingConfigs.release 74 | } 75 | } 76 | 77 | flavorDimensions "default" 78 | productFlavors { 79 | production { 80 | dimension "default" 81 | applicationId defaultConfig.applicationId 82 | versionCode rootProject.appVersionCode 83 | versionName rootProject.appVersionName 84 | 85 | //it allows to use the same flavor in android modules/libraries 86 | matchingFallbacks = ["production"] 87 | } 88 | 89 | dev { 90 | dimension "default" 91 | applicationId defaultConfig.applicationId + ".dev" 92 | versionCode rootProject.appVersionCode 93 | versionName rootProject.appVersionName + "-dev" 94 | 95 | //it allows to use the same flavor in android modules/libraries 96 | matchingFallbacks = ["dev"] 97 | 98 | //use this flavor by default 99 | getIsDefault().set(true) 100 | } 101 | } 102 | 103 | sourceSets { 104 | main { 105 | res.srcDirs = [ 106 | 'src/main/res/layouts/activity', 107 | 'src/main/res/layouts/fragment', 108 | 'src/main/res/layouts/adapter', 109 | 'src/main/res/layouts/include', 110 | 'src/main/res/layouts/preference', 111 | 'src/main/res' //it should be the last item here (it affects on output directory in Assets Studio) 112 | ] 113 | java.srcDirs = ['src/main/kotlin'] 114 | } 115 | } 116 | 117 | kapt.correctErrorTypes = true 118 | 119 | testOptions { 120 | unitTests.includeAndroidResources = true 121 | } 122 | 123 | buildFeatures { 124 | dataBinding = true 125 | viewBinding = true 126 | buildConfig = true 127 | } 128 | 129 | dynamicFeatures = [ 130 | ':feature:chat_main', 131 | ':feature:chat_core', 132 | ':feature:profile_settings' 133 | ] 134 | } 135 | 136 | dependencies { 137 | implementation fileTree(dir: 'libs', include: ['*.jar']) 138 | 139 | //published library from jitpack.io 140 | productionApi deps.common 141 | //local module 142 | devApi project(':common') 143 | 144 | api deps.preference 145 | api deps.constraintLayout 146 | 147 | api deps.coreKtx 148 | 149 | //DI 150 | api deps.koinCore 151 | api deps.koinAndroid 152 | 153 | //memory leak detection in debug 154 | debugApi deps.leakcanaryDebug 155 | 156 | //Glide 157 | api deps.glide 158 | kapt deps.glideCompiler 159 | 160 | //Lottie animation 161 | api deps.lottie 162 | 163 | //Firebase 164 | api platform(deps.firebaseBom) 165 | api deps.firebasePerformance 166 | api deps.crashlytics 167 | api deps.firebaseAnalytics 168 | 169 | //core modules for the whole app including feature modules 170 | api core.navigation 171 | api core.utils 172 | api core.database 173 | api core.ui 174 | 175 | testImplementation deps.jUnit 176 | androidTestImplementation deps.jUnitExt 177 | androidTestImplementation deps.testRunner 178 | androidTestImplementation deps.espressoCore 179 | testImplementation deps.mockito 180 | testImplementation deps.koinTest 181 | } 182 | 183 | --------------------------------------------------------------------------------