diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml
new file mode 100644
index 0000000..2006f86
--- /dev/null
+++ b/.github/workflows/run_tests.yml
@@ -0,0 +1,72 @@
+name: Run Unit Tests
+
+# Controls when the workflow will run
+on:
+ # Triggers the workflow on push or pull request events but only for the "main" branch
+ push:
+ branches: [ "main" ]
+ pull_request:
+ branches: [ "main" ]
+
+ # Allows you to run this workflow manually from the Actions tab
+ workflow_dispatch:
+
+jobs:
+ run-tests:
+ runs-on: ubuntu-latest
+ steps:
+ # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
+ - uses: actions/checkout@v4
+
+ - name: Cache Gradle dependencies
+ uses: actions/cache@v4
+ with:
+ path: |
+ ~/.gradle/caches
+ ~/.gradle/wrapper
+ key: >
+ ${{ runner.os }}-gradle-
+ ${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
+ # It finds the cache by key "Linux-gradle-"
+ restore-keys: ${{ runner.os }}-gradle-
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@v3
+ with:
+ java-version: '17'
+ distribution: 'zulu'
+ cache: gradle
+
+ - name: Setup Android SDK
+ uses: android-actions/setup-android@v3
+
+ - name: Grant execute permission for gradlew
+ run: chmod +x ./gradlew
+
+ - name: Update dependencies
+ run: ./gradlew dependencies
+
+ # Run tests and merge all reports into a single one.
+ # It requires to add a plugin to project level build.gradle: apply(plugin = "android-reporting")
+ - name: Run Unit tests
+ run: ./gradlew test
+
+ # Optional: Run instrumented tests (Requires Android Emulator)
+ # - name: Run Instrumented Tests
+ # run: ./gradlew connectedAndroidTest
+
+ # - name: Upload Test Report
+ # uses: actions/upload-artifact@v4
+ # with:
+ # name: Test-Reports
+ # path: feature/glow_editor/build/reports/tests/testProductionDebugUnitTest
+
+ # - name: Deploy Test Reports to GitHub Pages
+ # uses: peaceiris/actions-gh-pages@v3
+ # with:
+ # github_token: ${{ secrets.GITHUB_TOKEN }}
+ # publish_dir: ./feature/glow_editor/build/reports/tests/testProductionDebugUnitTest # Adjust this to the path where your HTML reports are generated
+ # publish_branch: gh-pages
+
+ - name: Build status = ${{ job.status }}
+ run: echo "Build status report=${{ job.status }}."
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..60080a4
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,93 @@
+# Built application files
+*.apk
+*.aar
+*.ap_
+*.aab
+
+# Files for the ART/Dalvik VM
+*.dex
+
+# Java class files
+*.class
+
+# Generated files
+bin/
+gen/
+out/
+# Uncomment the following line in case you need and you don't have the release build type files in your app
+# release/
+
+# Gradle files
+.gradle/
+build/
+
+# Local configuration file (sdk path, etc)
+local.properties
+keystore.properties
+
+# Proguard folder generated by Eclipse
+proguard/
+
+# Log Files
+*.log
+
+# Android Studio Navigation editor temp files
+.navigation/
+
+# Android Studio captures folder
+captures/
+
+# IntelliJ
+*.iml
+.idea/workspace.xml
+.idea/tasks.xml
+.idea/gradle.xml
+.idea/assetWizardSettings.xml
+.idea/dictionaries
+.idea/libraries
+.idea/deploymentTargetDropDown.xml
+# Android Studio 3 in .gitignore file.
+.idea/caches
+.idea/modules.xml
+# Comment next line if keeping position of elements in Navigation Editor is relevant for you
+.idea/navEditor.xml
+.idea/appInsightsSettings.xml
+.idea/androidTestResultsUserPreferences.xml
+
+
+# Keystore files
+# Uncomment the following lines if you do not want to check your keystore files in.
+*.jks
+*.keystore
+*.asc
+!gradle-wrapper.properties
+!gradle.properties
+
+# External native build folder generated in Android Studio 2.2 and later
+.externalNativeBuild
+.cxx/
+
+# Google Services (e.g. APIs or Firebase)
+# google-services.json
+
+# Freeline
+freeline.py
+freeline/
+freeline_project_description.json
+
+# fastlane
+fastlane/report.xml
+fastlane/Preview.html
+fastlane/screenshots
+fastlane/test_output
+fastlane/readme.md
+
+# Version control
+vcs.xml
+
+# lint
+lint/intermediates/
+lint/generated/
+lint/outputs/
+lint/tmp/
+# lint/reports/
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..7029e33
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+App-Template-Compose
\ No newline at end of file
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..7643783
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ xmlns:android
+
+ ^$
+
+
+
+
+
+
+
+
+ xmlns:.*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:id
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:name
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ name
+
+ ^$
+
+
+
+
+
+
+
+
+ style
+
+ ^$
+
+
+
+
+
+
+
+
+ .*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+
+ http://schemas.android.com/apk/res/android
+
+
+ ANDROID_ATTRIBUTE_ORDER
+
+
+
+
+
+
+ .*
+
+ .*
+
+
+ BY_NAME
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..03ecb0a
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..b589d56
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..44ca2d9
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
new file mode 100644
index 0000000..8491b65
--- /dev/null
+++ b/.idea/kotlinc.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..d7916a5
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..4c43f6c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,86 @@
+# Android Chat App entirely built with Jetpack Compose
+
+![Run Unit Tests](https://github.com/mobiledevpro/Jetpack-Compose-ChatApp-Template/actions/workflows/run_tests.yml/badge.svg)
+[![CodeFactor](https://www.codefactor.io/repository/github/mobiledevpro/jetpack-compose-chatapp-template/badge)](https://www.codefactor.io/repository/github/mobiledevpro/jetpack-compose-chatapp-template)
+[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=mobiledevpro_Jetpack-Compose-ChatApp-Template&metric=alert_status)](https://sonarcloud.io/dashboard?id=mobiledevpro_Jetpack-Compose-ChatApp-Template)
+
+[![Kotlin Version](https://img.shields.io/badge/Kotlin-1.9.22-blue.svg?style=flat-square)](http://kotlinlang.org/)
+[![Compose Bom](https://img.shields.io/badge/Compose%20Bom-2024.01.00-blue.svg?style=flat-square)]([http://kotlinlang.org/](https://developer.android.com/jetpack/compose/bom/bom-mapping))
+[![Gradle](https://img.shields.io/badge/Gradle-8.2.0-blue.svg?style=flat-square)](https://developer.android.com/build/releases/gradle-plugin)
+[![API](https://img.shields.io/badge/Min%20SDK-24%20[Android%207.0]-blue.svg?style=flat-square)](https://github.com/AndroidSDKSources/android-sdk-sources-list)
+[![Target SDK](https://img.shields.io/badge/Target%20SDK-34%20[Android%2014]-blue.svg?style=flat-square)](https://developer.android.com/about/versions/13)
+[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=flat-square)](http://www.apache.org/licenses/LICENSE-2.0)
+[![Android Studio](https://img.shields.io/badge/Android%20Studio%20Iguana-2023.2.1-orange.svg?style=flat-square)](https://developer.android.com/studio/preview)
+
+![GitHub last commit](https://img.shields.io/github/last-commit/mobiledevpro/Jetpack-Compose-ChatApp-Template?color=red&style=flat-square)
+
+![closetalk_github_social_preview](https://github.com/mobiledevpro/Jetpack-Compose-ChatApp-Template/assets/5750211/343f1ab5-54e4-41c2-a554-af0526aee382)
+
+[![Youtube](https://img.shields.io/badge/-youtube-red?logo=youtube&message=Youtube&style=for-the-badge&label=Watch+on)](https://www.youtube.com/playlist?list=PL9IBbMupfHWrW419OtGlzc7cBEMNqyLa4)
+
+
+##
+### Tech. stack
+
+* [Kotlin](https://kotlinlang.org/docs/getting-started.html)
+* [Coroutines](https://kotlinlang.org/docs/coroutines-overview.html)
+* [Jetpack Compose](https://developer.android.com/jetpack/compose) modern toolkit for building native UI
+* [Jetpack libs: Navigation, Compose, etc.](https://developer.android.com/jetpack)
+* [Material 3](https://m3.material.io/)
+* [Koin](https://insert-koin.io/docs/reference/koin-android/compose) for dependency injection
+* [Coil](https://coil-kt.github.io/coil/compose/) for image loading
+* MVI + modularization with a clean architecture
+* [Circle CI](https://circleci.com/) for continuous integration and delivery
+* GitHub Actions [[Complete Guide](https://mobiledevpro.gumroad.com/l/zjbvsd)]
+
+##
+## UI testing with [Maestro](https://maestro.mobile.dev/):
+
+* Install Maestro: run in terminal ```curl -Ls "https://get.maestro.mobile.dev" | bash```
+* Install the app on emulator (doesn't work with physical device)
+* Run the flow: run in terminal ```maestro test -c maestro/people-profile-flow.yaml```
+* [Sample config](maestro/people-profile-flow.yaml)
+
+##
+## Modularization
+
+![modularization](doc/modularization.png)
+
+##
+## Author:
+
+
+
+
+
+**Dmitri Chernysh**
+
+[![Youtube](https://img.shields.io/badge/-youtube-red?logo=youtube&message=Youtube&style=for-the-badge)](https://www.youtube.com/@mobiledevpro?sub_confirmation=1)
+[![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/)
+[![Twitter](https://img.shields.io/badge/-twitter-1DA1F2?logo=twitter&style=for-the-badge&logoColor=white)](https://twitter.com/mobiledev_pro)
+[![Linkedin](https://img.shields.io/badge/-linkedin-0A66C2?logo=linkedin&style=for-the-badge&logoColor=white)](https://www.linkedin.com/in/dmitriychernysh/)
+
+## License:
+
+Copyright 2023 Dmitri Chernysh
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+## Thanks for the support
+**Stargazers**
+
+[![Stargazers repo roster for @mobiledevpro/Jetpack-Compose-ChatApp-Template](http://reporoster.com/stars/dark/mobiledevpro/Jetpack-Compose-ChatApp-Template)](https://github.com/mobiledevpro/Jetpack-Compose-ChatApp-Template/stargazers)
+
+**Forkers**
+
+[![Forkers repo roster for @mobiledevpro/Jetpack-Compose-ChatApp-Template](http://reporoster.com/forks/dark/mobiledevpro/Jetpack-Compose-ChatApp-Template)](https://github.com/mobiledevpro/Jetpack-Compose-ChatApp-Template/network/members)
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
new file mode 100644
index 0000000..e1eddc7
--- /dev/null
+++ b/app/build.gradle.kts
@@ -0,0 +1,145 @@
+import com.android.build.gradle.AppExtension
+
+@Suppress("DSL_SCOPE_VIOLATION")
+plugins {
+ id("com.android.application")
+ kotlin("android")
+ kotlin("kapt")
+}
+
+android {
+ namespace = "com.mobiledevpro.apptemplate.compose"
+ compileSdk = libs.versions.sdk.compile.get().toInt()
+
+
+ defaultConfig {
+ applicationId = "com.mobiledevpro"
+ minSdk = libs.versions.sdk.min.get().toInt()
+ targetSdk = libs.versions.sdk.target.get().toInt()
+ versionCode = libs.versions.app.version.code.get().toInt()
+ versionName = libs.versions.app.version.name.get()
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+
+ vectorDrawables {
+ useSupportLibrary = true
+ }
+ }
+
+ buildTypes {
+ getByName("debug") {
+ isDebuggable = true
+ }
+
+ getByName("release") {
+ isDebuggable = false
+ isMinifyEnabled = true
+ isShrinkResources = false
+ proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
+ }
+ }
+
+ flavorDimensions += listOf("default")
+
+ productFlavors {
+ create("production") {
+ dimension = "default"
+ applicationIdSuffix = ".closetalk"
+ }
+
+ create("dev") {
+ dimension = "default"
+ applicationIdSuffix = ".apptemplate.compose"
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_17.toString()
+ }
+
+ buildFeatures {
+ compose = true
+ }
+ composeOptions {
+ kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get()
+ }
+
+ packaging {
+ resources.excludes.add("/META-INF/{AL2.0,LGPL2.1}")
+ }
+}
+
+dependencies {
+
+ implementation(libs.androidx.core.ktx)
+ coreLibraryDesugaring(libs.desugaring)
+ implementation(libs.bundles.lifecycle)
+
+ implementation(projects.core.navigation)
+
+ testApi(libs.bundles.test.common)
+}
+
+
+// This task is used for .AAB file renaming to the following format:
+// {application_id}-v{version_name}-build{version_code}-{product_flavor}-{build_type}.aab
+
+tasks.register("renameBundle") {
+ doLast {
+ val androidExtension = project.extensions.findByType(AppExtension::class.java)
+
+ androidExtension?.also { android ->
+
+ android.applicationVariants.forEach { variant ->
+ println("===============================")
+ val flavorName = variant.productFlavors.map { it.name }.joinToString()
+ val variantName = variant.name
+ val buildType = variant.buildType.name
+ val versionName = variant.versionName
+ val versionCode = variant.versionCode
+ val bundleDir =
+ "$buildDir/outputs/bundle/${variantName}"
+
+ println("Variant name: $variantName")
+ println("Version name: $versionName")
+ println("Version code: $versionCode")
+ println("Flavor name: $flavorName")
+
+ val oldFileName = "app-${flavorName}-${buildType}.aab"
+ val newFileName =
+ "${variant.applicationId}-" +
+ "v$versionName-" +
+ "build$versionCode-" +
+ "$flavorName-" +
+ "$buildType.aab"
+
+ println("newFileName: $newFileName")
+
+ //remove an old file if exist
+ delete("$bundleDir/$newFileName")
+
+ val originalFile = file("$bundleDir/$oldFileName")
+ val newFile = file("$bundleDir/$newFileName")
+
+ if (originalFile.exists()) {
+ originalFile.renameTo(newFile)
+ println("Renamed '$oldFileName' file to $newFileName")
+ } else {
+ println("Original '${originalFile.absolutePath}' file not found")
+ }
+ }
+
+ }
+ ?: println("Android extension not found. This task is only valid for Android application projects.")
+ }
+}
+
+tasks.matching { task -> task.name.contains("release", ignoreCase = true) }
+ .configureEach {
+ finalizedBy("renameBundle")
+ }
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..b90a639
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png
new file mode 100644
index 0000000..148f706
Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ
diff --git a/app/src/main/kotlin/com/mobiledevpro/app/Application.kt b/app/src/main/kotlin/com/mobiledevpro/app/Application.kt
new file mode 100644
index 0000000..47b139c
--- /dev/null
+++ b/app/src/main/kotlin/com/mobiledevpro/app/Application.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.app
+
+import android.app.Application
+import org.koin.android.ext.koin.androidContext
+import org.koin.android.ext.koin.androidLogger
+import org.koin.core.context.startKoin
+
+
+class App : Application() {
+ override fun onCreate() {
+ super.onCreate()
+
+ startKoin {
+ // Log Koin into Android logger
+ androidLogger()
+ // Reference Android context
+ androidContext(this@App)
+ // Load common modules (feature modules will be loaded on demand)
+ // modules(myAppModules)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/com/mobiledevpro/app/MainActivity.kt b/app/src/main/kotlin/com/mobiledevpro/app/MainActivity.kt
new file mode 100644
index 0000000..5e32fda
--- /dev/null
+++ b/app/src/main/kotlin/com/mobiledevpro/app/MainActivity.kt
@@ -0,0 +1,29 @@
+package com.mobiledevpro.app
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.runtime.getValue
+import androidx.core.view.WindowCompat
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.mobiledevpro.app.ui.MainApp
+import com.mobiledevpro.ui.theme.AppTheme
+import com.mobiledevpro.ui.theme.darkModeState
+
+class MainActivity : ComponentActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ //False - allows to drawing the content "edge-to-edge"
+ WindowCompat.setDecorFitsSystemWindows(window, false)
+
+ setContent {
+ val darkModeState by darkModeState.collectAsStateWithLifecycle()
+
+ AppTheme(darkTheme = darkModeState) {
+ MainApp()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/com/mobiledevpro/app/ui/MainApp.kt b/app/src/main/kotlin/com/mobiledevpro/app/ui/MainApp.kt
new file mode 100644
index 0000000..9f19f40
--- /dev/null
+++ b/app/src/main/kotlin/com/mobiledevpro/app/ui/MainApp.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.app.ui
+
+import androidx.compose.runtime.Composable
+import androidx.navigation.compose.rememberNavController
+import com.mobiledevpro.navigation.Screen
+import com.mobiledevpro.navigation.graph.RootNavGraph
+
+
+@Composable
+fun MainApp() {
+ val navController = rememberNavController()
+
+ RootNavGraph(
+ navController = navController,
+ startDestination = Screen.Home
+ )
+
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..5cc10bf
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..3f21f07
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..3f21f07
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..9841941
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..02b7990
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..34027f5
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..398faee
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..d5f904d
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..e996ddd
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..7dff8c5
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..30c463d
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..ee5a145
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..b6c709e
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..e9b36db
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..ccd8ddf
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..3f2ae6b
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..50334d0
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..03ead73
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..f8b8efe
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,4 @@
+
+
+ #FF06283D
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..f9d739c
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ Close Talk
+
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..19d38e9
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000..fa0f996
--- /dev/null
+++ b/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..9ee9997
--- /dev/null
+++ b/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/build-logic/.gitignore b/build-logic/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/build-logic/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts
new file mode 100644
index 0000000..9c2a4a8
--- /dev/null
+++ b/build-logic/build.gradle.kts
@@ -0,0 +1,21 @@
+plugins {
+ `kotlin-dsl`
+ `kotlin-dsl-precompiled-script-plugins`
+}
+
+repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+}
+
+dependencies {
+ implementation(gradleApi())
+ implementation(libs.kotlin.gradle.plugin)
+ implementation(libs.gradle)
+ implementation(libs.gradle.api)
+}
+
+tasks.test {
+ useJUnitPlatform()
+}
diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts
new file mode 100644
index 0000000..a07ca75
--- /dev/null
+++ b/build-logic/settings.gradle.kts
@@ -0,0 +1,11 @@
+enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
+
+dependencyResolutionManagement {
+ versionCatalogs {
+ create("libs") {
+ from(files("../gradle/libs.versions.toml"))
+ }
+ }
+}
+
+rootProject.name = "build-logic"
\ No newline at end of file
diff --git a/build-logic/src/main/kotlin/VersionCatalogExt.kt b/build-logic/src/main/kotlin/VersionCatalogExt.kt
new file mode 100644
index 0000000..ac20321
--- /dev/null
+++ b/build-logic/src/main/kotlin/VersionCatalogExt.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2023 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import org.gradle.api.Project
+import org.gradle.api.artifacts.VersionCatalog
+import org.gradle.api.artifacts.VersionCatalogsExtension
+import org.gradle.kotlin.dsl.getByType
+import kotlin.jvm.optionals.getOrNull
+
+internal val Project.libs: VersionCatalog
+ get() = extensions.getByType().named("libs")
+
+internal val VersionCatalog.plugins: List
+ get() = pluginAliases
+
+internal fun VersionCatalog.versionStr(alias: String): String =
+ this.findVersion(alias).getOrNull()?.toString() ?: ""
+
+internal fun VersionCatalog.versionInt(alias: String): Int =
+ versionStr(alias).toInt()
+
+internal fun VersionCatalog.library(alias: String) =
+ findLibrary(alias).get()
+
+internal fun VersionCatalog.bundle(alias: String) =
+ findBundle(alias).get()
+
diff --git a/build-logic/src/main/kotlin/core-compose-module.gradle.kts b/build-logic/src/main/kotlin/core-compose-module.gradle.kts
new file mode 100644
index 0000000..27e3a31
--- /dev/null
+++ b/build-logic/src/main/kotlin/core-compose-module.gradle.kts
@@ -0,0 +1,18 @@
+plugins {
+ id("core-module")
+}
+
+android {
+ buildFeatures.compose = true
+
+ composeOptions {
+ kotlinCompilerExtensionVersion = libs.versionStr("compose.compiler")
+ }
+}
+
+dependencies {
+ //Compose
+ api(platform(libs.library("compose.bom")))
+ api(libs.bundle("compose"))
+ debugApi(libs.bundle("compose.debug"))
+}
\ No newline at end of file
diff --git a/build-logic/src/main/kotlin/core-module.gradle.kts b/build-logic/src/main/kotlin/core-module.gradle.kts
new file mode 100644
index 0000000..f79f9ae
--- /dev/null
+++ b/build-logic/src/main/kotlin/core-module.gradle.kts
@@ -0,0 +1,65 @@
+plugins {
+ id("com.android.library")
+ kotlin("android")
+ kotlin("kapt")
+ id("kotlin-convention")
+}
+
+val projectName = project.name.replace("_", ".")
+
+android {
+ namespace = "com.mobiledevpro.$projectName"
+ compileSdk = libs.versionInt("sdk.compile")
+
+ defaultConfig {
+ minSdk = libs.versionInt("sdk.min")
+ }
+
+ compileOptions {
+ android.compileOptions.isCoreLibraryDesugaringEnabled = true
+
+ targetCompatibility = JavaVersion.VERSION_17
+ sourceCompatibility = JavaVersion.VERSION_17
+ }
+
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_17.toString()
+ }
+
+ flavorDimensions += listOf("default")
+
+ productFlavors {
+ create("production") {
+ dimension = "default"
+ }
+
+ create("dev") {
+ dimension = "default"
+ }
+ }
+
+ println("# Namespace: $projectName")
+ println("# Compile SDK version: ${libs.versionStr("sdk.compile")}")
+ println("# Min SDK version: ${libs.versionStr("sdk.min")}")
+ println("# Compose Compiler version: ${libs.versionStr("compose.compiler")}")
+}
+
+android.sourceSets {
+ getByName("main") {
+ kotlin.srcDir("src/main/kotlin")
+ res.srcDir("src/main/res")
+ }
+
+ getByName("production") {
+ kotlin.srcDir("src/production/kotlin")
+ res.srcDir("src/production/res")
+ }
+ getByName("dev") {
+ kotlin.srcDir("src/dev/kotlin")
+ res.srcDir("src/dev/res")
+ }
+}
+
+dependencies {
+ coreLibraryDesugaring(libs.library("desugaring"))
+}
\ No newline at end of file
diff --git a/build-logic/src/main/kotlin/feature-module.gradle.kts b/build-logic/src/main/kotlin/feature-module.gradle.kts
new file mode 100644
index 0000000..75697df
--- /dev/null
+++ b/build-logic/src/main/kotlin/feature-module.gradle.kts
@@ -0,0 +1,22 @@
+plugins {
+ id("core-module")
+}
+
+android {
+ buildFeatures.compose = true
+
+ composeOptions {
+ kotlinCompilerExtensionVersion = libs.versionStr("compose.compiler")
+ }
+}
+
+dependencies {
+ implementation(project(":core:ui"))
+ implementation(project(":core:di"))
+ implementation(project(":core:domain"))
+ implementation(project(":core:coroutines"))
+
+ implementation(libs.bundle("lifecycle"))
+ implementation(libs.library("coil"))
+ implementation(libs.library("activity.ktx"))
+}
\ No newline at end of file
diff --git a/build-logic/src/main/kotlin/kotlin-convention.gradle.kts b/build-logic/src/main/kotlin/kotlin-convention.gradle.kts
new file mode 100644
index 0000000..192f410
--- /dev/null
+++ b/build-logic/src/main/kotlin/kotlin-convention.gradle.kts
@@ -0,0 +1,17 @@
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
+import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+tasks.withType {
+ compilerOptions {
+ jvmTarget.set(JvmTarget.JVM_17)
+ freeCompilerArgs.set(
+ listOf(
+ "-Xallow-result-return-type",
+ "-Xcontext-receivers",
+ ),
+ )
+ languageVersion.set(KotlinVersion.KOTLIN_1_9)
+ apiVersion.set(KotlinVersion.KOTLIN_1_9)
+ }
+}
\ No newline at end of file
diff --git a/build-logic/src/main/kotlin/testing-module.gradle.kts b/build-logic/src/main/kotlin/testing-module.gradle.kts
new file mode 100644
index 0000000..99cffb8
--- /dev/null
+++ b/build-logic/src/main/kotlin/testing-module.gradle.kts
@@ -0,0 +1,20 @@
+plugins {
+ id("core-module")
+}
+
+android.defaultConfig.testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+
+// It solve the issue with testing Coroutines Flow / ViewModel state
+// and getting the error "app.cash.turbine.TurbineAssertionError: No value produced in 3s"
+android.testOptions {
+ unitTests.isReturnDefaultValues = true
+}
+
+dependencies {
+ androidTestImplementation(platform(libs.library("compose.bom")))
+ androidTestImplementation(libs.bundle("test.android"))
+ androidTestImplementation(libs.bundle("test.koin"))
+
+ testImplementation(libs.bundle("test.common"))
+ testImplementation(libs.bundle("test.koin"))
+}
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..59964e9
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,11 @@
+@Suppress("DSL_SCOPE_VIOLATION")
+plugins {
+ alias(libs.plugins.android.application) apply false
+ alias(libs.plugins.android.library) apply false
+ alias(libs.plugins.kotlin.android) apply false
+ alias(libs.plugins.kotlin.kapt) apply false
+}
+
+tasks.register("clean", Delete::class){
+ delete(rootProject.buildDir)
+}
diff --git a/build.sh b/build.sh
new file mode 100755
index 0000000..6c3509b
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,41 @@
+#! /bin/sh
+#
+# Script to build AAB release on local machine.
+#
+# Before continue please make sure
+# you have the following variables in the file keystore.properties:
+#
+# KEYPWD - it's keyPassword
+# KSTOREPWD - it's storePassword
+# KEYSTORE_ALIAS - alias
+# KEYSTORE_SECRET - it's a result of command 'gpg -c --armor release.keystore'
+# KEYSTORE_SECRET_PASSPHRASE - it's a phrase to KEYSTORE_SECRET
+#
+# !!! Don't push 'release.keystore' file to Git repo.
+
+echo y | android update sdk --filter "build-tools-34.0.0,android-34,extra-android-m2repository" --no-ui -a # Grab the Android Support Repo which isn't included in the container
+mkdir "${ANDROID_HOME}/licenses" || true
+echo "8933bad161af4178b1185d1a37fbf41ea5269c55" >"${ANDROID_HOME}/licenses/android-sdk-license"
+
+#clone remote modules
+git submodule init
+git submodule update
+
+#build dependencies
+./gradlew dependencies
+
+#read variables from the file (key pass, key secret, secret passphrase)
+. './keystore.properties' &&
+ echo "$KEYSTORE_SECRET" >app/release.asc &&
+ gpg -d --passphrase "$KEYSTORE_SECRET_PASSPHRASE" --batch app/release.asc >app/release.jks &&
+ rm app/release.asc &&
+
+ # Export these variables to be able to use it in build.gradle signingConfigs
+ export KEYPWD &&
+ export KSTOREPWD &&
+ export KEYSTORE_ALIAS &&
+
+ #build AAB bundles
+ ./gradlew clean bundleRelease --stacktrace --no-build-cache
+
+rm app/release.jks
diff --git a/core/coroutines/.gitignore b/core/coroutines/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/core/coroutines/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/core/coroutines/build.gradle.kts b/core/coroutines/build.gradle.kts
new file mode 100644
index 0000000..1c0cc1f
--- /dev/null
+++ b/core/coroutines/build.gradle.kts
@@ -0,0 +1,7 @@
+plugins {
+ id("core-module")
+}
+
+dependencies {
+ api(libs.coroutines.android)
+}
\ No newline at end of file
diff --git a/core/coroutines/consumer-rules.pro b/core/coroutines/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/core/coroutines/proguard-rules.pro b/core/coroutines/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/core/coroutines/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/core/coroutines/src/main/AndroidManifest.xml b/core/coroutines/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..568741e
--- /dev/null
+++ b/core/coroutines/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/core/coroutines/src/main/kotlin/com/mobiledevpro/coroutines/BaseCoroutinesFLowUseCase.kt b/core/coroutines/src/main/kotlin/com/mobiledevpro/coroutines/BaseCoroutinesFLowUseCase.kt
new file mode 100644
index 0000000..4bde739
--- /dev/null
+++ b/core/coroutines/src/main/kotlin/com/mobiledevpro/coroutines/BaseCoroutinesFLowUseCase.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.coroutines
+
+import android.util.Log
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+
+/**
+ * Base UseCase for Coroutines Flow result
+ *
+ * Created on Sep 12, 2022.
+ *
+ */
+abstract class BaseCoroutinesFLowUseCase(
+ executionDispatcher: CoroutineDispatcher
+) : BaseUseCase(executionDispatcher) {
+
+ abstract fun buildUseCaseFlow(params: Params? = null): Flow
+
+ fun execute(params: Params? = null): Flow> =
+ try {
+ if (dispatcher == Dispatchers.Main)
+ throw RuntimeException("Use case '${this::class.simpleName}' cannot be executed in $dispatcher")
+
+ this.buildUseCaseFlow(params)
+ .flowOn(dispatcher)
+ .map {
+ resultOf { it }
+ }
+ } catch (e: Exception) {
+ logException(e.localizedMessage ?: e.cause?.message ?: e.toString())
+ flowOf(Result.failure(Throwable(e.localizedMessage)))
+ } catch (t: Throwable) {
+ logException(t.message ?: t.cause?.message ?: t.toString())
+ flowOf(Result.failure(t))
+ }
+
+ override fun logException(errMessage: String) {
+ Log.e(this::class.simpleName, "${this::class.simpleName} : $errMessage")
+ }
+}
\ No newline at end of file
diff --git a/core/coroutines/src/main/kotlin/com/mobiledevpro/coroutines/BaseCoroutinesUseCase.kt b/core/coroutines/src/main/kotlin/com/mobiledevpro/coroutines/BaseCoroutinesUseCase.kt
new file mode 100644
index 0000000..ac2ad41
--- /dev/null
+++ b/core/coroutines/src/main/kotlin/com/mobiledevpro/coroutines/BaseCoroutinesUseCase.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.coroutines
+
+import android.util.Log
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+
+/**
+ * Base UseCase with Coroutines
+ *
+ * Created on Sep 12, 2022.
+ *
+ */
+abstract class BaseCoroutinesUseCase(
+ executionDispatcher: CoroutineDispatcher
+) : BaseUseCase(executionDispatcher) {
+
+ abstract suspend fun buildUseCase(params: Params? = null): Results
+
+ suspend fun execute(params: Params? = null): Result =
+ withContext(dispatcher) {
+ try {
+ if (dispatcher == Dispatchers.Main)
+ throw RuntimeException("Use case '${this::class.simpleName}' cannot be executed in $dispatcher")
+
+ resultOf {
+ this@BaseCoroutinesUseCase.buildUseCase(params)
+ }
+ } catch (t: Throwable) {
+ logException(t.message ?: t.cause?.message ?: t.toString())
+ Result.failure(t)
+ } catch (e: Exception) {
+ logException(e.localizedMessage ?: e.cause?.message ?: e.toString())
+ Result.failure(Throwable(e.localizedMessage))
+ }
+ }
+
+
+ override fun logException(errMessage: String) {
+ Log.e(this::class.simpleName, "${this::class.simpleName} : $errMessage")
+
+ }
+}
\ No newline at end of file
diff --git a/core/coroutines/src/main/kotlin/com/mobiledevpro/coroutines/BaseUseCase.kt b/core/coroutines/src/main/kotlin/com/mobiledevpro/coroutines/BaseUseCase.kt
new file mode 100644
index 0000000..fecbb7c
--- /dev/null
+++ b/core/coroutines/src/main/kotlin/com/mobiledevpro/coroutines/BaseUseCase.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.coroutines
+
+import kotlinx.coroutines.CoroutineDispatcher
+
+/**
+ * Base UseCase
+ *
+ * Created on Sep 12, 2022.
+ *
+ */
+abstract class BaseUseCase(
+ executionDispatcher: CoroutineDispatcher
+) {
+ protected val dispatcher = executionDispatcher
+
+ abstract fun logException(errMessage: String)
+}
\ No newline at end of file
diff --git a/core/coroutines/src/main/kotlin/com/mobiledevpro/coroutines/ResultExt.kt b/core/coroutines/src/main/kotlin/com/mobiledevpro/coroutines/ResultExt.kt
new file mode 100644
index 0000000..fd15ddd
--- /dev/null
+++ b/core/coroutines/src/main/kotlin/com/mobiledevpro/coroutines/ResultExt.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.coroutines
+
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.TimeoutCancellationException
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
+
+
+// Based on: https://proandroiddev.com/resilient-use-cases-with-kotlin-result-coroutines-and-annotations-511df10e2e16
+
+/**
+ * Like [runCatching], but with proper coroutines cancellation handling. Also only catches [Exception] instead of [Throwable].
+ *
+ * Cancellation exceptions need to be rethrown. See https://github.com/Kotlin/kotlinx.coroutines/issues/1814.
+ */
+/*
+inline fun resultOf(block: () -> R): Result {
+ return try {
+ Result.success(block())
+ } catch (t: TimeoutCancellationException) {
+ Result.failure(t)
+ } catch (c: CancellationException) {
+ throw c
+ } catch (e: Exception) {
+ Result.failure(e)
+ }
+}
+
+ */
+
+/**
+ * Like [runCatching], but with proper coroutines cancellation handling. Also only catches [Exception] instead of [Throwable].
+ *
+ * Cancellation exceptions need to be rethrown. See https://github.com/Kotlin/kotlinx.coroutines/issues/1814.
+ */
+
+inline fun T.resultOf(block: T.() -> R): Result {
+ return try {
+ Result.success(block())
+ } catch (t: TimeoutCancellationException) {
+ Result.failure(t)
+ } catch (c: CancellationException) {
+ throw c
+ } catch (e: Exception) {
+ Result.failure(e)
+ }
+}
+
+/**
+ * Like [mapCatching], but uses [resultOf] instead of [runCatching].
+ */
+
+inline fun Result.mapResult(transform: (value: T) -> R): Result {
+ val successResult = getOrNull()
+ return when {
+ successResult != null -> resultOf { transform(successResult) }
+ else -> Result.failure(exceptionOrNull() ?: error("Unreachable state"))
+ }
+}
+
+@OptIn(ExperimentalContracts::class)
+inline fun Result.andThen(transform: (value: T) -> Result): Result {
+ contract {
+ callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
+ }
+
+ val successResult = getOrNull()
+ return when {
+ successResult != null -> transform(successResult)
+ else -> Result.failure(exceptionOrNull() ?: error("Unreachable state"))
+ }
+}
+
+
+@OptIn(ExperimentalContracts::class)
+inline fun Result.andThenFlow(transform: (value: T) -> Flow>): Flow> {
+ contract {
+ callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
+ }
+
+ val successResult: T? = getOrNull()
+
+ return when {
+ successResult != null -> transform(successResult)
+ else -> flowOf(Result.failure(exceptionOrNull() ?: error("Unreachable state")))
+ }
+}
+
+
+class None
diff --git a/core/di/.gitignore b/core/di/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/core/di/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/core/di/build.gradle.kts b/core/di/build.gradle.kts
new file mode 100644
index 0000000..0e2a775
--- /dev/null
+++ b/core/di/build.gradle.kts
@@ -0,0 +1,7 @@
+plugins {
+ id("core-compose-module")
+}
+
+dependencies {
+ api(libs.bundles.koin)
+}
\ No newline at end of file
diff --git a/core/di/consumer-rules.pro b/core/di/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/core/di/proguard-rules.pro b/core/di/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/core/di/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/core/di/src/main/AndroidManifest.xml b/core/di/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..568741e
--- /dev/null
+++ b/core/di/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/core/di/src/main/kotlin/com/mobiledevpro/di/KoinExt.kt b/core/di/src/main/kotlin/com/mobiledevpro/di/KoinExt.kt
new file mode 100644
index 0000000..17bf6e3
--- /dev/null
+++ b/core/di/src/main/kotlin/com/mobiledevpro/di/KoinExt.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2023 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.di
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisallowComposableCalls
+import androidx.compose.runtime.remember
+import org.koin.compose.module.rememberKoinModules
+import org.koin.compose.scope.rememberKoinScope
+import org.koin.core.annotation.KoinExperimentalAPI
+import org.koin.core.module.Module
+import org.koin.core.qualifier.TypeQualifier
+import org.koin.core.scope.Scope
+import org.koin.ext.getFullName
+import org.koin.java.KoinJavaComponent
+
+
+inline fun koinScope(): Scope {
+
+ val scopeId = T::class.getFullName() + "@" + T::class.hashCode()
+ val qualifier = TypeQualifier(T::class)
+
+ return KoinJavaComponent.getKoin().getOrCreateScope(scopeId, qualifier)
+}
+
+@OptIn(KoinExperimentalAPI::class)
+@Composable
+inline fun rememberViewModel(
+ crossinline modules: @DisallowComposableCalls () -> List
+): T {
+ rememberKoinModules(
+ modules = modules
+ )
+
+ val scope = rememberKoinScope(scope = koinScope())
+
+ return remember { scope.get() }
+}
\ No newline at end of file
diff --git a/core/domain/.gitignore b/core/domain/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/core/domain/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/core/domain/build.gradle.kts b/core/domain/build.gradle.kts
new file mode 100644
index 0000000..29b9440
--- /dev/null
+++ b/core/domain/build.gradle.kts
@@ -0,0 +1,3 @@
+plugins {
+ id("core-module")
+}
\ No newline at end of file
diff --git a/core/domain/consumer-rules.pro b/core/domain/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/core/domain/proguard-rules.pro b/core/domain/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/core/domain/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/core/domain/src/main/AndroidManifest.xml b/core/domain/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..1d26c87
--- /dev/null
+++ b/core/domain/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/core/domain/src/main/kotlin/com/mobiledevpro/domain/model/Chat.kt b/core/domain/src/main/kotlin/com/mobiledevpro/domain/model/Chat.kt
new file mode 100644
index 0000000..e26a429
--- /dev/null
+++ b/core/domain/src/main/kotlin/com/mobiledevpro/domain/model/Chat.kt
@@ -0,0 +1,44 @@
+package com.mobiledevpro.domain.model
+
+import android.net.Uri
+
+/**
+ * Chat
+ *
+ * Created on May 06, 2023.
+ *
+ */
+data class Chat(
+ val user: UserProfile,
+ val peopleList: List,
+ val unreadMsgCount : Int = 0
+)
+
+val fakeUser = UserProfile(
+ name = "Joe Black",
+ nickname = "@nickname",
+ status = true,
+ photo = Uri.parse("https://media.istockphoto.com/id/1090878494/photo/close-up-portrait-of-young-smiling-handsome-man-in-blue-polo-shirt-isolated-on-gray-background.jpg?b=1&s=170667a&w=0&k=20&c=c3TaqVe9-0EcHl7mjO-9YChSvGBDhvzUai6obs1Ibz4=")
+)
+
+val fakeChatList = arrayListOf(
+ Chat(
+ user = fakeUser,
+ peopleList = fakePeopleProfileList.take(5).sortedByDescending { !it.status },
+ unreadMsgCount = 100
+ ),
+ Chat(
+ user = fakeUser,
+ peopleList = fakePeopleProfileList.takeLast(3).sortedByDescending { !it.status },
+ unreadMsgCount = 0
+ ),
+
+ Chat(
+ user = fakeUser,
+ peopleList = listOf(fakePeopleProfileList[6]),
+ unreadMsgCount = 3
+ )
+)
+
+fun Chat.name() : String =
+ this.peopleList.toChatName()
diff --git a/core/domain/src/main/kotlin/com/mobiledevpro/domain/model/PeopleProfile.kt b/core/domain/src/main/kotlin/com/mobiledevpro/domain/model/PeopleProfile.kt
new file mode 100644
index 0000000..375400c
--- /dev/null
+++ b/core/domain/src/main/kotlin/com/mobiledevpro/domain/model/PeopleProfile.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.domain.model
+
+import android.net.Uri
+
+/**
+ * Profile
+ *
+ * Created on Feb 04, 2023.
+ *
+ */
+
+data class PeopleProfile(
+ val id: Int,
+ val name: String,
+ val status: Boolean,
+ val photo: Uri? = null
+) {
+ fun listKey(): String = "${id}_${name.replace("\\s+".toRegex(), "")}"
+}
+
+
+val fakePeopleProfileList = arrayListOf(
+ PeopleProfile(
+ id = 0,
+ name = "Michaela Runnings",
+ status = true,
+ Uri.parse("https://images.unsplash.com/photo-1485290334039-a3c69043e517?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=80")
+ ),
+ PeopleProfile(
+ id = 1,
+ name = "John Pestridge",
+ status = false,
+ Uri.parse("https://images.unsplash.com/photo-1542178243-bc20204b769f?ixid=MXwxMjA3fDB8MHxzZWFyY2h8MTB8fHBvcnRyYWl0fGVufDB8MnwwfA%3D%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60")
+ ),
+ PeopleProfile(
+ id = 2,
+ name = "Manilla Andrews",
+ status = true,
+ Uri.parse("https://images.unsplash.com/photo-1543123820-ac4a5f77da38?ixid=MXwxMjA3fDB8MHxzZWFyY2h8NDh8fHBvcnRyYWl0fGVufDB8MnwwfA%3D%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60")
+ ),
+ PeopleProfile(
+ id = 3,
+ name = "Dan Spicer",
+ status = false,
+ Uri.parse("https://images.unsplash.com/photo-1595152772835-219674b2a8a6?ixid=MXwxMjA3fDB8MHxzZWFyY2h8NDd8fHBvcnRyYWl0fGVufDB8MnwwfA%3D%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60")
+ ),
+ PeopleProfile(
+ id = 4,
+ name = "Keanu Dester",
+ status = false,
+ Uri.parse("https://images.unsplash.com/photo-1597528380214-aa94bde3fc32?ixid=MXwxMjA3fDB8MHxzZWFyY2h8NTZ8fHBvcnRyYWl0fGVufDB8MnwwfA%3D%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60")
+ ),
+ PeopleProfile(
+ id = 5,
+ name = "Anichu Patel",
+ status = false,
+ Uri.parse("https://images.unsplash.com/photo-1598641795816-a84ac9eac40c?ixid=MXwxMjA3fDB8MHxzZWFyY2h8NjJ8fHBvcnRyYWl0fGVufDB8MnwwfA%3D%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60")
+ ),
+ PeopleProfile(
+ id = 6,
+ name = "Kienla Onso",
+ status = true,
+ Uri.parse("https://images.unsplash.com/photo-1566895733044-d2bdda8b6234?ixid=MXwxMjA3fDB8MHxzZWFyY2h8ODh8fHBvcnRyYWl0fGVufDB8MnwwfA%3D%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60")
+ ),
+ PeopleProfile(
+ id = 7,
+ name = "Andra Matthews",
+ status = false,
+ Uri.parse("https://images.unsplash.com/photo-1530577197743-7adf14294584?ixid=MXwxMjA3fDB8MHxzZWFyY2h8NTV8fHBvcnRyYWl0fGVufDB8MnwwfA%3D%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60")
+ ),
+ PeopleProfile(
+ id = 8,
+ name = "Georgia S.", status = false,
+ Uri.parse("https://images.unsplash.com/photo-1547212371-eb5e6a4b590c?ixid=MXwxMjA3fDB8MHxzZWFyY2h8MTA3fHxwb3J0cmFpdHxlbnwwfDJ8MHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60")
+ ),
+ PeopleProfile(
+ id = 8,
+ name = "Matt Dengo",
+ status = false,
+ Uri.parse("https://images.unsplash.com/photo-1578176603894-57973e38890f?ixid=MXwxMjA3fDB8MHxzZWFyY2h8MTE0fHxwb3J0cmFpdHxlbnwwfDJ8MHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60")
+ ),
+ PeopleProfile(
+ id = 9,
+ name = "Marsha T.",
+ status = true,
+ Uri.parse("https://images.unsplash.com/photo-1605087880595-8cc6db61f3c6?ixid=MXwxMjA3fDB8MHxzZWFyY2h8MTI0fHxwb3J0cmFpdHxlbnwwfDJ8MHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60")
+ ),
+ PeopleProfile(
+ id = 10,
+ name = "Invshu Patel",
+ status = true,
+ Uri.parse("https://images.unsplash.com/photo-1561820009-8bef03ebf8e5?ixid=MXwxMjA3fDB8MHxzZWFyY2h8MTM3fHxwb3J0cmFpdHxlbnwwfDJ8MHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60")
+ )
+)
+
+fun List.toChatName(): String =
+ mapTo(ArrayList()) { profile -> profile.name }.let { names ->
+ val stringBuilder = StringBuilder()
+ names.onEachIndexed { index, s ->
+ if (index > 0)
+ stringBuilder.append(", ")
+ stringBuilder.append(s)
+ }
+ stringBuilder.toString()
+ }
\ No newline at end of file
diff --git a/core/domain/src/main/kotlin/com/mobiledevpro/domain/model/UserProfile.kt b/core/domain/src/main/kotlin/com/mobiledevpro/domain/model/UserProfile.kt
new file mode 100644
index 0000000..0116ae4
--- /dev/null
+++ b/core/domain/src/main/kotlin/com/mobiledevpro/domain/model/UserProfile.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.domain.model
+
+import android.net.Uri
+
+/**
+ * App User Profile
+ *
+ * Created on Feb 04, 2023.
+ *
+ */
+
+data class UserProfile(
+ val name : String,
+ val nickname: String,
+ val status: Boolean = false,
+ val photo : Uri = Uri.EMPTY
+)
diff --git a/core/navigation/.gitignore b/core/navigation/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/core/navigation/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/core/navigation/build.gradle.kts b/core/navigation/build.gradle.kts
new file mode 100644
index 0000000..0068d5c
--- /dev/null
+++ b/core/navigation/build.gradle.kts
@@ -0,0 +1,25 @@
+@Suppress("DSL_SCOPE_VIOLATION")
+plugins {
+ id("core-compose-module")
+}
+
+
+dependencies {
+ //Compose
+ implementation(platform(libs.compose.bom))
+ implementation(libs.bundles.compose)
+ debugImplementation(libs.bundles.compose.debug)
+
+ api(libs.navigation)
+
+ api(projects.core.ui)
+ api(projects.core.di)
+ implementation(projects.core.domain)
+
+ implementation(projects.feature.home)
+ implementation(projects.feature.onboarding)
+ implementation(projects.feature.subscription)
+ implementation(projects.feature.chatList)
+ implementation(projects.feature.people)
+ implementation(projects.feature.userProfile)
+}
\ No newline at end of file
diff --git a/core/navigation/consumer-rules.pro b/core/navigation/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/core/navigation/proguard-rules.pro b/core/navigation/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/core/navigation/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/core/navigation/src/main/AndroidManifest.xml b/core/navigation/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..568741e
--- /dev/null
+++ b/core/navigation/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/core/navigation/src/main/kotlin/com/mobiledevpro/navigation/HomeBottomNavigation.kt b/core/navigation/src/main/kotlin/com/mobiledevpro/navigation/HomeBottomNavigation.kt
new file mode 100644
index 0000000..dabeb35
--- /dev/null
+++ b/core/navigation/src/main/kotlin/com/mobiledevpro/navigation/HomeBottomNavigation.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.navigation
+
+import android.util.Log
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.slideInHorizontally
+import androidx.compose.animation.slideOutHorizontally
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Warning
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.navigation.NavDestination
+import androidx.navigation.NavDestination.Companion.hierarchy
+import com.mobiledevpro.ui.component.AppBottomBar
+import com.mobiledevpro.ui.component.AppBottomBarItem
+
+
+@Composable
+fun HomeBottomNavigation(
+ screens: List,
+ onNavigateTo: (Screen) -> Unit,
+ currentDestination: NavDestination?
+) {
+
+ Log.d("navigation", "HomeBottomNavigation")
+
+ AnimatedVisibility(
+ visible = true,
+ enter = slideInHorizontally(initialOffsetX = { it }),
+ exit = slideOutHorizontally(targetOffsetX = { it }),
+ ) {
+
+
+ AppBottomBar {
+
+ screens.forEach { screen ->
+ Log.d("navigation", "HomeBottomNavigation: hierarchy = $currentDestination")
+
+ val selected: Boolean =
+ currentDestination?.hierarchy?.any { it.route == screen.route } ?: false
+
+ AppBottomBarItem(
+ selected = selected,
+ onClick = { onNavigateTo(screen) },
+ icon = {
+ Icon(
+ imageVector = screen.icon ?: Icons.Default.Warning,
+ contentDescription = null
+ )
+ },
+ label = { Text(text = screen.title ?: "") }
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/navigation/src/main/kotlin/com/mobiledevpro/navigation/Route.kt b/core/navigation/src/main/kotlin/com/mobiledevpro/navigation/Route.kt
new file mode 100644
index 0000000..7af7c6b
--- /dev/null
+++ b/core/navigation/src/main/kotlin/com/mobiledevpro/navigation/Route.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.navigation
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.rounded.Home
+import androidx.compose.material.icons.rounded.Person
+import androidx.compose.material.icons.rounded.Settings
+import androidx.compose.ui.graphics.vector.ImageVector
+import com.mobiledevpro.people.profile.view.args.PeopleProfileArgs
+
+const val navigationRouteOnBoarding = "on_boarding"
+const val navigationRouteOnBoardingFirst = "on_boarding_first"
+const val navigationRouteOnBoardingSecond = "on_boarding_second"
+const val navigationRouteOnBoardingThird = "on_boarding_third"
+
+const val navigationRouteHome = "home"
+const val navigationRouteChatList = "chat_list"
+const val navigationRouteProfile = "profile"
+
+const val navigationRoutePeople = "people"
+const val navigationRoutePeopleList = "people_list"
+const val navigationRoutePeopleProfile =
+ "people_profile/{${PeopleProfileArgs.PEOPLE_PROFILE_ID_ARG}}"
+
+const val navigationRouteSubscription = "subscription"
+
+sealed class Screen(
+ val route: String,
+ var routePath: String? = null,
+ var clearBackStack: Boolean = false,
+ val restoreState: Boolean = true,
+ val title: String? = null,
+ val icon: ImageVector? = null
+) {
+
+ fun withClearBackStack() = apply { clearBackStack = true }
+
+ fun routeWith(path: String) = apply {
+ routePath = path
+ }
+
+ object OnBoarding : Screen(navigationRouteOnBoarding)
+ object OnBoardingFirst : Screen(navigationRouteOnBoardingFirst)
+ object OnBoardingSecond : Screen(navigationRouteOnBoardingSecond)
+ object OnBoardingThird : Screen(navigationRouteOnBoardingThird)
+
+ object Home : Screen(navigationRouteHome)
+
+ // 3 tabs of Bottom navigation
+ object ChatList :
+ Screen(route = navigationRouteChatList, title = "Chats", icon = Icons.Rounded.Home)
+
+ object People : Screen(
+ route = navigationRoutePeople,
+ restoreState = false,
+ title = "People",
+ icon = Icons.Rounded.Person,
+ )
+
+ object Profile :
+ Screen(route = navigationRouteProfile, title = "Profile", icon = Icons.Rounded.Settings)
+
+ object Subscription : Screen(navigationRouteSubscription)
+
+ object PeopleList : Screen(navigationRoutePeopleList)
+ object PeopleProfile : Screen(navigationRoutePeopleProfile)
+
+}
\ No newline at end of file
diff --git a/core/navigation/src/main/kotlin/com/mobiledevpro/navigation/ScreenNavigation.kt b/core/navigation/src/main/kotlin/com/mobiledevpro/navigation/ScreenNavigation.kt
new file mode 100644
index 0000000..4214a75
--- /dev/null
+++ b/core/navigation/src/main/kotlin/com/mobiledevpro/navigation/ScreenNavigation.kt
@@ -0,0 +1,259 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.navigation
+
+import android.util.Log
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.NavType
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.currentBackStackEntryAsState
+import androidx.navigation.compose.rememberNavController
+import androidx.navigation.navArgument
+import com.mobiledevpro.chatlist.di.featureChatListModule
+import com.mobiledevpro.chatlist.view.ChatListScreen
+import com.mobiledevpro.chatlist.view.ChatListViewModel
+import com.mobiledevpro.di.rememberViewModel
+import com.mobiledevpro.home.view.HomeScreen
+import com.mobiledevpro.home.view.HomeViewModel
+import com.mobiledevpro.navigation.ext.navigateTo
+import com.mobiledevpro.navigation.graph.HomeNavGraph
+import com.mobiledevpro.navigation.graph.OnBoardingNavGraph
+import com.mobiledevpro.navigation.graph.PeopleNavGraph
+import com.mobiledevpro.onboarding.view.OnBoardingFirstScreen
+import com.mobiledevpro.onboarding.view.OnBoardingScreen
+import com.mobiledevpro.onboarding.view.OnBoardingSecondScreen
+import com.mobiledevpro.onboarding.view.OnBoardingThirdScreen
+import com.mobiledevpro.people.profile.view.PeopleProfileScreen
+import com.mobiledevpro.people.profile.view.PeopleProfileViewModel
+import com.mobiledevpro.people.profile.view.args.PeopleProfileArgs
+import com.mobiledevpro.people.view.PeopleScreen
+import com.mobiledevpro.peoplelist.di.featurePeopleListModule
+import com.mobiledevpro.peoplelist.view.PeopleListScreen
+import com.mobiledevpro.peoplelist.view.PeopleListViewModel
+import com.mobiledevpro.subscription.SubscriptionScreen
+import com.mobiledevpro.user.profile.di.featureUserProfileModule
+import com.mobiledevpro.user.profile.view.ProfileScreen
+import com.mobiledevpro.user.profile.view.vm.ProfileViewModel
+
+
+fun NavGraphBuilder.homeNavGraph(onNavigateToRoot: (Screen) -> Unit) {
+ composable(
+ route = Screen.Home.route
+ ) {
+ Log.d("navigation", "------homeNavGraph:START------------")
+
+ //NavController for nested graph
+ //It will not work for root graph
+ val navController = rememberNavController()
+ val navBackStackEntry by navController.currentBackStackEntryAsState()
+
+ val bottomBar: @Composable () -> Unit = {
+ Log.d("navigation", "homeNavGraph:bottomBar")
+ HomeBottomNavigation(
+ screens = listOf(
+ Screen.ChatList,
+ Screen.People,
+ Screen.Profile
+ ), onNavigateTo = navController::navigateTo,
+ currentDestination = navBackStackEntry?.destination
+ )
+ }
+
+ val nestedNavGraph: @Composable () -> Unit = {
+ Log.d("navigation", "homeNavGraph:nestedNavGraph")
+ HomeNavGraph(
+ navController = navController,
+ onNavigateToRoot = onNavigateToRoot
+ )
+ }
+
+ val viewModel: HomeViewModel = viewModel()
+
+ HomeScreen(
+ bottomBar = bottomBar,
+ nestedNavGraph = nestedNavGraph
+ )
+
+ Log.d("navigation", "------homeNavGraph:END------------")
+ }
+
+}
+
+fun NavGraphBuilder.onBoardingNavGraph(onNavigateToRoot: (Screen) -> Unit) {
+ composable(
+ route = Screen.OnBoarding.route
+ ) {
+
+ val navController = rememberNavController()
+
+ val nestedNavGraph: @Composable () -> Unit = {
+ OnBoardingNavGraph(
+ navController = navController
+ )
+ }
+
+ OnBoardingScreen(
+ nestedNavGraph,
+ onNext = {
+ when (navController.currentDestination?.route) {
+ Screen.OnBoardingFirst.route -> navController.navigateTo(Screen.OnBoardingSecond)
+ Screen.OnBoardingSecond.route -> navController.navigateTo(Screen.OnBoardingThird)
+ Screen.OnBoardingThird.route -> Screen.Home.withClearBackStack()
+ .also(onNavigateToRoot)
+
+ else -> {}
+ }
+ }
+ )
+ }
+}
+
+fun NavGraphBuilder.peopleNavGraph() {
+ composable(
+ route = Screen.People.route
+ ) {
+ val navController = rememberNavController()
+
+ val nestedNavGraph: @Composable () -> Unit = {
+ PeopleNavGraph(
+ navController = navController
+ )
+ }
+
+ PeopleScreen(nestedNavGraph)
+ }
+}
+
+fun NavGraphBuilder.onBoardingFirstScreen() {
+ composable(
+ route = Screen.OnBoardingFirst.route
+ ) {
+ OnBoardingFirstScreen()
+ }
+}
+
+fun NavGraphBuilder.onBoardingSecondScreen() {
+ composable(
+ route = Screen.OnBoardingSecond.route
+ ) {
+ OnBoardingSecondScreen()
+ }
+}
+
+fun NavGraphBuilder.onBoardingThirdScreen() {
+ composable(
+ route = Screen.OnBoardingThird.route
+ ) {
+ OnBoardingThirdScreen()
+ }
+}
+
+fun NavGraphBuilder.subscriptionScreen(onNavigateBack: () -> Unit) {
+ composable(
+ route = Screen.Subscription.route
+ ) {
+ SubscriptionScreen(onNavigateBack)
+ }
+}
+
+fun NavGraphBuilder.chatListScreen() {
+ composable(
+ route = Screen.ChatList.route
+ ) {
+
+ val viewModel = rememberViewModel(
+ modules = { listOf(featureChatListModule) }
+ )
+
+ ChatListScreen(
+ state = viewModel.uiState,
+ onClick = { chat ->
+ //TODO: open chat screen
+ }
+ )
+ }
+}
+
+fun NavGraphBuilder.peopleListScreen(onNavigateTo: (Screen) -> Unit) {
+ composable(
+ route = Screen.PeopleList.route
+ ) {
+
+ val viewModel = rememberViewModel(
+ modules = { listOf(featurePeopleListModule) }
+ )
+
+ PeopleListScreen(
+ viewModel.uiState,
+ onNavigateToProfile = { profileId: Int ->
+ Screen.PeopleProfile.routeWith(profileId.toString())
+ .also(onNavigateTo)
+ }
+ )
+ }
+}
+
+fun NavGraphBuilder.peopleProfileScreen(
+ onNavigateBack: () -> Unit,
+ onNavigateTo: (Screen) -> Unit
+) {
+ composable(
+ route = Screen.PeopleProfile.route,
+ arguments = listOf(
+ navArgument(PeopleProfileArgs.PEOPLE_PROFILE_ID_ARG) { type = NavType.IntType }
+ )
+ ) {
+
+ val viewModel: PeopleProfileViewModel = viewModel()
+ val peopleProfile = remember { viewModel.getProfile() }
+
+ peopleProfile ?: return@composable
+
+ PeopleProfileScreen(
+ peopleProfile,
+ onBackPressed = onNavigateBack,
+ onOpenChatWith = {}
+ )
+ }
+}
+
+
+fun NavGraphBuilder.profileScreen(onNavigateTo: (Screen) -> Unit) {
+ composable(
+ route = Screen.Profile.route
+ ) {
+
+ val viewModel = rememberViewModel(
+ modules = {
+ listOf(featureUserProfileModule)
+ }
+ )
+
+ ProfileScreen(
+ state = viewModel.uiState,
+ onNavigateToSubscription = {
+ onNavigateTo(Screen.Subscription)
+ }
+ )
+ }
+}
+
diff --git a/core/navigation/src/main/kotlin/com/mobiledevpro/navigation/ext/NavControllerExt.kt b/core/navigation/src/main/kotlin/com/mobiledevpro/navigation/ext/NavControllerExt.kt
new file mode 100644
index 0000000..bfd8c0e
--- /dev/null
+++ b/core/navigation/src/main/kotlin/com/mobiledevpro/navigation/ext/NavControllerExt.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.navigation.ext
+
+import android.util.Log
+import androidx.navigation.NavController
+import androidx.navigation.NavGraph.Companion.findStartDestination
+import com.mobiledevpro.navigation.Screen
+
+
+fun NavController.navigateTo(
+ screen: Screen
+) {
+
+ val currentRoute: String? = this.currentBackStackEntry?.destination?.route
+
+ val route = screen.routePath?.let { routePath ->
+ screen.route.replaceAfter("/", routePath)
+ } ?: screen.route
+
+ Log.d("navigation", "navigateTo: ${screen.route}")
+
+ navigate(route) {
+
+ Log.d("navigation", "findStartDestination: ${graph.findStartDestination()}")
+
+ // Pop up to the start destination of the graph to
+ // avoid building up a large stack of destinations
+ // on the back stack as users select items
+ popUpTo(graph.findStartDestination().id) {
+ saveState = true
+ }
+ // Avoid multiple copies of the same destination when
+ // reselecting the same item
+ launchSingleTop = true
+ // Restore state when reselecting a previously selected item
+ restoreState = screen.restoreState
+
+ //Clearing back stack up to certain screen if required
+ if (screen.clearBackStack && !currentRoute.isNullOrEmpty())
+ popUpTo(currentRoute) {
+ inclusive = true
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/navigation/src/main/kotlin/com/mobiledevpro/navigation/graph/HomeNavGraph.kt b/core/navigation/src/main/kotlin/com/mobiledevpro/navigation/graph/HomeNavGraph.kt
new file mode 100644
index 0000000..bfa2da7
--- /dev/null
+++ b/core/navigation/src/main/kotlin/com/mobiledevpro/navigation/graph/HomeNavGraph.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.navigation.graph
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.NavHost
+import com.mobiledevpro.navigation.Screen
+import com.mobiledevpro.navigation.chatListScreen
+import com.mobiledevpro.navigation.peopleNavGraph
+import com.mobiledevpro.navigation.profileScreen
+
+/**
+ * Nested navigation graph for Home screen
+ *
+ * Created on Jan 24, 2023.
+ *
+ */
+@Composable
+fun HomeNavGraph(
+ modifier: Modifier = Modifier,
+ navController: NavHostController,
+ onNavigateToRoot: (Screen) -> Unit
+) {
+ NavHost(
+ navController = navController,
+ startDestination = Screen.ChatList.route,
+ modifier = modifier,
+ ) {
+
+ chatListScreen()
+ peopleNavGraph()
+ profileScreen(onNavigateTo = onNavigateToRoot)
+
+ }
+}
\ No newline at end of file
diff --git a/core/navigation/src/main/kotlin/com/mobiledevpro/navigation/graph/OnBoardingNavGraph.kt b/core/navigation/src/main/kotlin/com/mobiledevpro/navigation/graph/OnBoardingNavGraph.kt
new file mode 100644
index 0000000..886168a
--- /dev/null
+++ b/core/navigation/src/main/kotlin/com/mobiledevpro/navigation/graph/OnBoardingNavGraph.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.navigation.graph
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.NavHost
+import com.mobiledevpro.navigation.Screen
+import com.mobiledevpro.navigation.onBoardingFirstScreen
+import com.mobiledevpro.navigation.onBoardingSecondScreen
+import com.mobiledevpro.navigation.onBoardingThirdScreen
+
+/**
+ * Nested navigation graph for OnBoarding screen
+ *
+ * Created on Jan 24, 2023.
+ *
+ */
+@Composable
+fun OnBoardingNavGraph(
+ modifier: Modifier = Modifier,
+ navController: NavHostController
+) {
+ NavHost(
+ navController = navController,
+ startDestination = Screen.OnBoardingFirst.route,
+ modifier = modifier,
+ ) {
+
+ onBoardingFirstScreen()
+ onBoardingSecondScreen()
+ onBoardingThirdScreen()
+ }
+}
\ No newline at end of file
diff --git a/core/navigation/src/main/kotlin/com/mobiledevpro/navigation/graph/PeopleNavGraph.kt b/core/navigation/src/main/kotlin/com/mobiledevpro/navigation/graph/PeopleNavGraph.kt
new file mode 100644
index 0000000..a37937f
--- /dev/null
+++ b/core/navigation/src/main/kotlin/com/mobiledevpro/navigation/graph/PeopleNavGraph.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.navigation.graph
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.NavHost
+import com.mobiledevpro.navigation.Screen
+import com.mobiledevpro.navigation.ext.navigateTo
+import com.mobiledevpro.navigation.peopleListScreen
+import com.mobiledevpro.navigation.peopleProfileScreen
+
+/**
+ * Nested navigation graph for People screen
+ *
+ * Created on Feb 04, 2023.
+ *
+ */
+
+@Composable
+fun PeopleNavGraph(
+ modifier: Modifier = Modifier,
+ navController: NavHostController
+) {
+ NavHost(
+ navController = navController,
+ startDestination = Screen.PeopleList.route,
+ modifier = modifier,
+ ) {
+
+ peopleListScreen(onNavigateTo = navController::navigateTo)
+ peopleProfileScreen(
+ onNavigateTo = navController::navigateTo,
+ onNavigateBack = navController::navigateUp
+ )
+ }
+}
\ No newline at end of file
diff --git a/core/navigation/src/main/kotlin/com/mobiledevpro/navigation/graph/RootNavGraph.kt b/core/navigation/src/main/kotlin/com/mobiledevpro/navigation/graph/RootNavGraph.kt
new file mode 100644
index 0000000..d08b18a
--- /dev/null
+++ b/core/navigation/src/main/kotlin/com/mobiledevpro/navigation/graph/RootNavGraph.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.navigation.graph
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.NavHost
+import com.mobiledevpro.navigation.Screen
+import com.mobiledevpro.navigation.ext.navigateTo
+import com.mobiledevpro.navigation.homeNavGraph
+import com.mobiledevpro.navigation.onBoardingNavGraph
+import com.mobiledevpro.navigation.subscriptionScreen
+
+/**
+ * Top-level navigation host in the app
+ *
+ * Created on Jan 07, 2023.
+ *
+ */
+
+@Composable
+fun RootNavGraph(
+ navController: NavHostController,
+ modifier: Modifier = Modifier,
+ startDestination: Screen
+) {
+ NavHost(
+ navController = navController,
+ route = "root_host",
+ startDestination = startDestination.route,
+ modifier = modifier,
+ ) {
+
+ val navigateBack: () -> Unit = {
+ navController.navigateUp()
+ }
+
+ //Nested Navigation Graphs
+ onBoardingNavGraph(onNavigateToRoot = navController::navigateTo)
+ homeNavGraph(onNavigateToRoot = navController::navigateTo)
+
+ //Root screens
+ subscriptionScreen(onNavigateBack = navigateBack)
+ }
+}
\ No newline at end of file
diff --git a/core/ui/.gitignore b/core/ui/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/core/ui/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts
new file mode 100644
index 0000000..c9d2107
--- /dev/null
+++ b/core/ui/build.gradle.kts
@@ -0,0 +1,8 @@
+plugins {
+ id("core-compose-module")
+}
+dependencies {
+ api(libs.bundles.lifecycle)
+ api(libs.coil)
+ api(libs.lottie)
+}
diff --git a/core/ui/consumer-rules.pro b/core/ui/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/core/ui/proguard-rules.pro b/core/ui/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/core/ui/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/core/ui/src/main/AndroidManifest.xml b/core/ui/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..568741e
--- /dev/null
+++ b/core/ui/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/core/ui/src/main/kotlin/com/mobiledevpro/ui/component/AppBottomBar.kt b/core/ui/src/main/kotlin/com/mobiledevpro/ui/component/AppBottomBar.kt
new file mode 100644
index 0000000..5233b9e
--- /dev/null
+++ b/core/ui/src/main/kotlin/com/mobiledevpro/ui/component/AppBottomBar.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.ui.component
+
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.NavigationBar
+import androidx.compose.material3.NavigationBarItem
+import androidx.compose.material3.NavigationBarItemDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+/**
+ * Common navigation bar for bottom menu
+ *
+ * Created on Jan 12, 2023.
+ *
+ */
+
+@Composable
+fun AppBottomBar(
+ modifier: Modifier = Modifier,
+ content: @Composable RowScope.() -> Unit
+) {
+
+ NavigationBar(
+ modifier = modifier,
+ contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
+ tonalElevation = 4.dp,
+ content = content
+ )
+}
+
+@Composable
+fun RowScope.AppBottomBarItem(
+ selected: Boolean,
+ onClick: () -> Unit,
+ icon: @Composable () -> Unit,
+ modifier: Modifier = Modifier,
+ selectedIcon: @Composable () -> Unit = icon,
+ enabled: Boolean = true,
+ label: @Composable (() -> Unit)? = null,
+ alwaysShowLabel: Boolean = true
+) {
+ NavigationBarItem(
+ selected = selected,
+ onClick = onClick,
+ icon = if (selected) selectedIcon else icon,
+ modifier = modifier,
+ enabled = enabled,
+ label = label,
+ alwaysShowLabel = alwaysShowLabel,
+ colors = NavigationBarItemDefaults.colors(
+ selectedIconColor = MaterialTheme.colorScheme.onPrimaryContainer,
+ unselectedIconColor = MaterialTheme.colorScheme.onSurfaceVariant,
+ selectedTextColor = MaterialTheme.colorScheme.onPrimaryContainer,
+ unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant,
+ indicatorColor = MaterialTheme.colorScheme.primaryContainer
+ )
+ )
+}
\ No newline at end of file
diff --git a/core/ui/src/main/kotlin/com/mobiledevpro/ui/component/AppTopBar.kt b/core/ui/src/main/kotlin/com/mobiledevpro/ui/component/AppTopBar.kt
new file mode 100644
index 0000000..2de01b6
--- /dev/null
+++ b/core/ui/src/main/kotlin/com/mobiledevpro/ui/component/AppTopBar.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.ui.component
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.unit.dp
+import com.mobiledevpro.ui.theme.topAppBarColor
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun AppTopBar(title: String, icon: ImageVector, iconClickAction: () -> Unit = {}) {
+ TopAppBar(
+ navigationIcon = {
+ Icon(
+ imageVector = icon,
+ contentDescription = title,
+ modifier = Modifier
+ .padding(horizontal = 16.dp)
+ .clickable {
+ iconClickAction.invoke()
+ }
+ )
+ },
+ title = { Text(title) },
+ colors = TopAppBarDefaults.topAppBarColors(
+ containerColor = MaterialTheme.colorScheme.topAppBarColor
+ )
+
+ )
+}
\ No newline at end of file
diff --git a/core/ui/src/main/kotlin/com/mobiledevpro/ui/component/CardItem.kt b/core/ui/src/main/kotlin/com/mobiledevpro/ui/component/CardItem.kt
new file mode 100644
index 0000000..6d524c2
--- /dev/null
+++ b/core/ui/src/main/kotlin/com/mobiledevpro/ui/component/CardItem.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2023 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.ui.component
+
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+/**
+ * Default card for list items
+ *
+ * Created on May 06, 2023.
+ *
+ */
+
+@Composable
+fun CardItem(modifier: Modifier, content: @Composable ColumnScope.() -> Unit) {
+ Card(
+ modifier = modifier
+ .padding(start = 16.dp, end = 16.dp, top = 8.dp, bottom = 8.dp)
+ .fillMaxWidth()
+ .wrapContentHeight(align = Alignment.Top),
+ elevation = CardDefaults.cardElevation(2.dp),
+ colors = CardDefaults.cardColors(
+ containerColor = MaterialTheme.colorScheme.surface
+ ),
+ content = content
+ )
+}
diff --git a/core/ui/src/main/kotlin/com/mobiledevpro/ui/component/LabeledDarkModeSwitch.kt b/core/ui/src/main/kotlin/com/mobiledevpro/ui/component/LabeledDarkModeSwitch.kt
new file mode 100644
index 0000000..878fad7
--- /dev/null
+++ b/core/ui/src/main/kotlin/com/mobiledevpro/ui/component/LabeledDarkModeSwitch.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2023 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.ui.component
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Switch
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.mobiledevpro.ui.theme.AppTheme
+
+/**
+ * Radio Button
+ *
+ * Created on Apr 02, 2023.
+ *
+ */
+
+@Composable
+fun LabeledDarkModeSwitch(
+ label: String,
+ checked: Boolean,
+ onCheckedChanged: (Boolean) -> Unit
+) {
+
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(
+ horizontal = 16.dp,
+ vertical = 10.dp
+ )
+ ) {
+
+ Text(text = label, style = MaterialTheme.typography.bodyLarge)
+
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ Text("☀️")
+ Switch(
+ checked = checked,
+ onCheckedChange = onCheckedChanged
+ )
+ Text("🌘")
+ }
+ }
+
+
+}
+
+@Composable
+@Preview
+fun LabeledSwitchPreview() {
+ AppTheme(darkTheme = true) {
+ LabeledDarkModeSwitch(
+ "Dark mode",
+ checked = false,
+ onCheckedChanged = {}
+ )
+ }
+}
\ No newline at end of file
diff --git a/core/ui/src/main/kotlin/com/mobiledevpro/ui/component/ProfileContent.kt b/core/ui/src/main/kotlin/com/mobiledevpro/ui/component/ProfileContent.kt
new file mode 100644
index 0000000..5a2bd8c
--- /dev/null
+++ b/core/ui/src/main/kotlin/com/mobiledevpro/ui/component/ProfileContent.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.ui.component
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+
+/**
+ * For People list
+ *
+ * Created on Feb 05, 2023.
+ *
+ */
+@Composable
+fun ProfileContent(
+ userName: String,
+ subName: String? = null,
+ isOnline: Boolean,
+ alignment: Alignment.Horizontal,
+ modifier: Modifier = Modifier
+) {
+ Column(
+ modifier = modifier,
+ horizontalAlignment = alignment
+ ) {
+ CompositionLocalProvider(
+ LocalContentColor provides LocalContentColor.current.copy(alpha = if (isOnline) 1f else 0.4f)
+ ) {
+
+ Text(
+ text = userName,
+ style = MaterialTheme.typography.bodyLarge
+ )
+ }
+
+ CompositionLocalProvider(LocalContentColor provides LocalContentColor.current.copy(alpha = 0.4F)) {
+ Text(
+ text = subName ?: if (isOnline) "Active now" else "Offline",
+ style = MaterialTheme.typography.bodyMedium
+ )
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/core/ui/src/main/kotlin/com/mobiledevpro/ui/component/ProfilePicture.kt b/core/ui/src/main/kotlin/com/mobiledevpro/ui/component/ProfilePicture.kt
new file mode 100644
index 0000000..5fb0f1b
--- /dev/null
+++ b/core/ui/src/main/kotlin/com/mobiledevpro/ui/component/ProfilePicture.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.ui.component
+
+import android.net.Uri
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.unit.dp
+import coil.compose.rememberAsyncImagePainter
+import coil.request.ImageRequest
+import coil.size.Size
+import coil.transform.CircleCropTransformation
+import com.mobiledevpro.ui.theme.lightGreen
+import com.mobiledevpro.ui.theme.red
+
+/**
+ * For Profile screen
+ *
+ * Created on Feb 05, 2023.
+ *
+ */
+
+@Composable
+fun ProfilePicture(photoUri: Uri, onlineStatus: Boolean, size : ProfilePictureSize, modifier: Modifier = Modifier) {
+ val pictureSizeDp = when(size) {
+ ProfilePictureSize.SMALL -> 36.dp
+ ProfilePictureSize.MEDIUM -> 72.dp
+ ProfilePictureSize.LARGE -> 144.dp
+ }
+
+ Card(
+ shape = CircleShape,
+ border = BorderStroke(
+ width = 2.dp,
+ color = if (onlineStatus) MaterialTheme.colorScheme.lightGreen else MaterialTheme.colorScheme.red
+ ),
+ modifier = modifier,
+ elevation = CardDefaults.cardElevation(4.dp)
+ ) {
+
+ Image(
+ painter = rememberAsyncImagePainter(
+ model = ImageRequest.Builder(LocalContext.current)
+ .data(photoUri)
+ .size(Size.ORIGINAL) // Set the target size to load the image at.
+ .transformations(CircleCropTransformation())
+ .build()
+ ),
+ contentDescription = "Profile image",
+ modifier = Modifier.size(pictureSizeDp),
+ contentScale = ContentScale.Crop
+ )
+ }
+
+}
+
+enum class ProfilePictureSize {
+ SMALL,
+ MEDIUM,
+ LARGE
+}
\ No newline at end of file
diff --git a/core/ui/src/main/kotlin/com/mobiledevpro/ui/component/ScreenBackground.kt b/core/ui/src/main/kotlin/com/mobiledevpro/ui/component/ScreenBackground.kt
new file mode 100644
index 0000000..07f17ed
--- /dev/null
+++ b/core/ui/src/main/kotlin/com/mobiledevpro/ui/component/ScreenBackground.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.ui.component
+
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.LocalAbsoluteTonalElevation
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+
+/**
+ * Common background for all screens
+ *
+ * Created on Jan 10, 2023.
+ *
+ */
+
+@Composable
+fun ScreenBackground(
+ modifier: Modifier = Modifier,
+ content: @Composable () -> Unit
+) {
+ val color = MaterialTheme.colorScheme.background
+
+ Surface(
+ color = if (color == Color.Unspecified) Color.Transparent else color,
+ modifier = modifier.fillMaxSize(),
+ ) {
+ CompositionLocalProvider(
+ LocalAbsoluteTonalElevation provides 0.dp
+ ) {
+ content()
+ }
+ }
+}
diff --git a/core/ui/src/main/kotlin/com/mobiledevpro/ui/component/SettingsButton.kt b/core/ui/src/main/kotlin/com/mobiledevpro/ui/component/SettingsButton.kt
new file mode 100644
index 0000000..bed9647
--- /dev/null
+++ b/core/ui/src/main/kotlin/com/mobiledevpro/ui/component/SettingsButton.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2023 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.ui.component
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.rounded.ExitToApp
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.mobiledevpro.ui.theme.AppTheme
+
+/**
+ * Clickable item for settings list
+ *
+ * Created on Apr 02, 2023.
+ *
+ */
+
+@Composable
+fun SettingsButton(
+ label: String,
+ icon: ImageVector,
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier
+) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(
+ horizontal = 16.dp,
+ vertical = 10.dp
+ )
+ .clickable {
+ onClick()
+ }
+ ) {
+ Text(text = label, style = MaterialTheme.typography.bodyLarge)
+
+ IconButton(
+ onClick = onClick
+ ) {
+ Icon(imageVector = icon, contentDescription = "")
+ }
+ }
+
+}
+
+@Composable
+@Preview
+fun SettingButtonPreview() {
+ AppTheme(darkTheme = true) {
+ SettingsButton(
+ "Logout",
+ icon = Icons.Rounded.ExitToApp,
+ onClick = {}
+ )
+ }
+}
\ No newline at end of file
diff --git a/core/ui/src/main/kotlin/com/mobiledevpro/ui/ext/InsetsExt.kt b/core/ui/src/main/kotlin/com/mobiledevpro/ui/ext/InsetsExt.kt
new file mode 100644
index 0000000..e82f104
--- /dev/null
+++ b/core/ui/src/main/kotlin/com/mobiledevpro/ui/ext/InsetsExt.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2023 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.ui.ext
+
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.navigationBars
+import androidx.compose.foundation.layout.statusBars
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalDensity
+
+@Composable
+fun WindowInsets.Companion.statusBarHeight(): Int = statusBars.getTop(LocalDensity.current)
+
+@Composable
+fun WindowInsets.Companion.navigationBarHeight(): Int =
+ navigationBars.getBottom(LocalDensity.current)
\ No newline at end of file
diff --git a/core/ui/src/main/kotlin/com/mobiledevpro/ui/ext/UnitExt.kt b/core/ui/src/main/kotlin/com/mobiledevpro/ui/ext/UnitExt.kt
new file mode 100644
index 0000000..1e24e85
--- /dev/null
+++ b/core/ui/src/main/kotlin/com/mobiledevpro/ui/ext/UnitExt.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2023 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.ui.ext
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Dp
+
+@Composable
+fun Int.dp(): Dp = with(LocalDensity.current) { this@dp.toDp() }
diff --git a/core/ui/src/main/kotlin/com/mobiledevpro/ui/state/UIState.kt b/core/ui/src/main/kotlin/com/mobiledevpro/ui/state/UIState.kt
new file mode 100644
index 0000000..ebd7532
--- /dev/null
+++ b/core/ui/src/main/kotlin/com/mobiledevpro/ui/state/UIState.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2023 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.ui.state
+
+
+interface UIState
\ No newline at end of file
diff --git a/core/ui/src/main/kotlin/com/mobiledevpro/ui/theme/Color.kt b/core/ui/src/main/kotlin/com/mobiledevpro/ui/theme/Color.kt
new file mode 100644
index 0000000..243fc0a
--- /dev/null
+++ b/core/ui/src/main/kotlin/com/mobiledevpro/ui/theme/Color.kt
@@ -0,0 +1,81 @@
+package com.mobiledevpro.ui.theme
+import androidx.compose.material3.ColorScheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.Color
+
+val md_theme_light_primary = Color(0xFF006495)
+val md_theme_light_onPrimary = Color(0xFFFFFFFF)
+val md_theme_light_primaryContainer = Color(0xFFCBE6FF)
+val md_theme_light_onPrimaryContainer = Color(0xFF001E30)
+val md_theme_light_secondary = Color(0xFF006781)
+val md_theme_light_onSecondary = Color(0xFFFFFFFF)
+val md_theme_light_secondaryContainer = Color(0xFFBAEAFF)
+val md_theme_light_onSecondaryContainer = Color(0xFF001F29)
+val md_theme_light_tertiary = Color(0xFF006496)
+val md_theme_light_onTertiary = Color(0xFFFFFFFF)
+val md_theme_light_tertiaryContainer = Color(0xFFCCE5FF)
+val md_theme_light_onTertiaryContainer = Color(0xFF001E31)
+val md_theme_light_error = Color(0xFFBA1A1A)
+val md_theme_light_errorContainer = Color(0xFFFFDAD6)
+val md_theme_light_onError = Color(0xFFFFFFFF)
+val md_theme_light_onErrorContainer = Color(0xFF410002)
+val md_theme_light_background = Color(0xFFF8FDFF)
+val md_theme_light_onBackground = Color(0xFF001F25)
+val md_theme_light_surface = Color(0xFFF8FDFF)
+val md_theme_light_onSurface = Color(0xFF001F25)
+val md_theme_light_surfaceVariant = Color(0xFFDEE3EA)
+val md_theme_light_onSurfaceVariant = Color(0xFF41474D)
+val md_theme_light_outline = Color(0xFF72787E)
+val md_theme_light_inverseOnSurface = Color(0xFFD6F6FF)
+val md_theme_light_inverseSurface = Color(0xFF00363F)
+val md_theme_light_inversePrimary = Color(0xFF8FCDFF)
+val md_theme_light_shadow = Color(0xFF000000)
+val md_theme_light_surfaceTint = Color(0xFF006495)
+val md_theme_light_outlineVariant = Color(0xFFC1C7CE)
+val md_theme_light_scrim = Color(0xFF000000)
+
+val md_theme_dark_primary = Color(0xFF8FCDFF)
+val md_theme_dark_onPrimary = Color(0xFF003450)
+val md_theme_dark_primaryContainer = Color(0xFF004B71)
+val md_theme_dark_onPrimaryContainer = Color(0xFFCBE6FF)
+val md_theme_dark_secondary = Color(0xFF5FD4FD)
+val md_theme_dark_onSecondary = Color(0xFF003544)
+val md_theme_dark_secondaryContainer = Color(0xFF004D62)
+val md_theme_dark_onSecondaryContainer = Color(0xFFBAEAFF)
+val md_theme_dark_tertiary = Color(0xFF91CDFF)
+val md_theme_dark_onTertiary = Color(0xFF003350)
+val md_theme_dark_tertiaryContainer = Color(0xFF004B72)
+val md_theme_dark_onTertiaryContainer = Color(0xFFCCE5FF)
+val md_theme_dark_error = Color(0xFFFFB4AB)
+val md_theme_dark_errorContainer = Color(0xFF93000A)
+val md_theme_dark_onError = Color(0xFF690005)
+val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
+val md_theme_dark_background = Color(0xFF001F25)
+val md_theme_dark_onBackground = Color(0xFFA6EEFF)
+val md_theme_dark_surface = Color(0xFF001F25)
+val md_theme_dark_onSurface = Color(0xFFA6EEFF)
+val md_theme_dark_surfaceVariant = Color(0xFF41474D)
+val md_theme_dark_onSurfaceVariant = Color(0xFFC1C7CE)
+val md_theme_dark_outline = Color(0xFF8B9198)
+val md_theme_dark_inverseOnSurface = Color(0xFF001F25)
+val md_theme_dark_inverseSurface = Color(0xFFA6EEFF)
+val md_theme_dark_inversePrimary = Color(0xFF006495)
+val md_theme_dark_shadow = Color(0xFF000000)
+val md_theme_dark_surfaceTint = Color(0xFF8FCDFF)
+val md_theme_dark_outlineVariant = Color(0xFF41474D)
+val md_theme_dark_scrim = Color(0xFF000000)
+
+val seed = Color(0xFF06283D)
+
+
+@get:Composable
+val ColorScheme.topAppBarColor: Color
+ get() = seed
+
+@get:Composable
+val ColorScheme.lightGreen: Color
+ get() = Color(0x9932CD32)
+
+@get:Composable
+val ColorScheme.red: Color
+ get() = Color(0x99F44336)
\ No newline at end of file
diff --git a/core/ui/src/main/kotlin/com/mobiledevpro/ui/theme/Icon.kt b/core/ui/src/main/kotlin/com/mobiledevpro/ui/theme/Icon.kt
new file mode 100644
index 0000000..ac9c6c3
--- /dev/null
+++ b/core/ui/src/main/kotlin/com/mobiledevpro/ui/theme/Icon.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.ui.theme
+
diff --git a/core/ui/src/main/kotlin/com/mobiledevpro/ui/theme/Theme.kt b/core/ui/src/main/kotlin/com/mobiledevpro/ui/theme/Theme.kt
new file mode 100644
index 0000000..b5ebd2c
--- /dev/null
+++ b/core/ui/src/main/kotlin/com/mobiledevpro/ui/theme/Theme.kt
@@ -0,0 +1,124 @@
+package com.mobiledevpro.ui.theme
+
+import android.app.Activity
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.darkColorScheme
+import androidx.compose.material3.lightColorScheme
+import androidx.compose.material3.surfaceColorAtElevation
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.unit.dp
+import androidx.core.view.WindowCompat
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+private val LightColorScheme = lightColorScheme(
+ primary = md_theme_light_primary,
+ onPrimary = md_theme_light_onPrimary,
+ primaryContainer = md_theme_light_primaryContainer,
+ onPrimaryContainer = md_theme_light_onPrimaryContainer,
+ secondary = md_theme_light_secondary,
+ onSecondary = md_theme_light_onSecondary,
+ secondaryContainer = md_theme_light_secondaryContainer,
+ onSecondaryContainer = md_theme_light_onSecondaryContainer,
+ tertiary = md_theme_light_tertiary,
+ onTertiary = md_theme_light_onTertiary,
+ tertiaryContainer = md_theme_light_tertiaryContainer,
+ onTertiaryContainer = md_theme_light_onTertiaryContainer,
+ error = md_theme_light_error,
+ errorContainer = md_theme_light_errorContainer,
+ onError = md_theme_light_onError,
+ onErrorContainer = md_theme_light_onErrorContainer,
+ background = md_theme_light_background,
+ onBackground = md_theme_light_onBackground,
+ surface = md_theme_light_surface,
+ onSurface = md_theme_light_onSurface,
+ surfaceVariant = md_theme_light_surfaceVariant,
+ onSurfaceVariant = md_theme_light_onSurfaceVariant,
+ outline = md_theme_light_outline,
+ inverseOnSurface = md_theme_light_inverseOnSurface,
+ inverseSurface = md_theme_light_inverseSurface,
+ inversePrimary = md_theme_light_inversePrimary,
+ surfaceTint = md_theme_light_surfaceTint,
+ outlineVariant = md_theme_light_outlineVariant,
+ scrim = md_theme_light_scrim
+)
+
+
+private val DarkColorScheme = darkColorScheme(
+ primary = md_theme_dark_primary,
+ onPrimary = md_theme_dark_onPrimary,
+ primaryContainer = md_theme_dark_primaryContainer,
+ onPrimaryContainer = md_theme_dark_onPrimaryContainer,
+ secondary = md_theme_dark_secondary,
+ onSecondary = md_theme_dark_onSecondary,
+ secondaryContainer = md_theme_dark_secondaryContainer,
+ onSecondaryContainer = md_theme_dark_onSecondaryContainer,
+ tertiary = md_theme_dark_tertiary,
+ onTertiary = md_theme_dark_onTertiary,
+ tertiaryContainer = md_theme_dark_tertiaryContainer,
+ onTertiaryContainer = md_theme_dark_onTertiaryContainer,
+ error = md_theme_dark_error,
+ errorContainer = md_theme_dark_errorContainer,
+ onError = md_theme_dark_onError,
+ onErrorContainer = md_theme_dark_onErrorContainer,
+ background = md_theme_dark_background,
+ onBackground = md_theme_dark_onBackground,
+ surface = md_theme_dark_surface,
+ onSurface = md_theme_dark_onSurface,
+ surfaceVariant = md_theme_dark_surfaceVariant,
+ onSurfaceVariant = md_theme_dark_onSurfaceVariant,
+ outline = md_theme_dark_outline,
+ inverseOnSurface = md_theme_dark_inverseOnSurface,
+ inverseSurface = md_theme_dark_inverseSurface,
+ inversePrimary = md_theme_dark_inversePrimary,
+ surfaceTint = md_theme_dark_surfaceTint,
+ outlineVariant = md_theme_dark_outlineVariant,
+ scrim = md_theme_dark_scrim,
+)
+
+@Composable
+fun AppTheme(
+ darkTheme: Boolean = true,
+ content: @Composable () -> Unit
+) {
+ val colorScheme = when {
+ darkTheme -> DarkColorScheme
+ else -> LightColorScheme
+ }
+ val view = LocalView.current
+ if (!view.isInEditMode) {
+ SideEffect {
+ val window = (view.context as Activity).window
+ window.statusBarColor = Color.Transparent.toArgb()
+ window.navigationBarColor = colorScheme.surfaceColorAtElevation(4.dp).toArgb()
+ WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme
+ WindowCompat.getInsetsController(window, view).isAppearanceLightNavigationBars = !darkTheme
+ }
+ }
+
+ MaterialTheme(
+ colorScheme = colorScheme,
+ typography = Typography
+ ) {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(color = MaterialTheme.colorScheme.background)
+ ) {
+ content()
+ }
+ }
+}
+
+//TODO: it's temporary implementation. Dark mode value should be saved into preferences.
+val _darkModeState = MutableStateFlow(true)
+val darkModeState: StateFlow = _darkModeState.asStateFlow()
\ No newline at end of file
diff --git a/core/ui/src/main/kotlin/com/mobiledevpro/ui/theme/Type.kt b/core/ui/src/main/kotlin/com/mobiledevpro/ui/theme/Type.kt
new file mode 100644
index 0000000..a66e6a9
--- /dev/null
+++ b/core/ui/src/main/kotlin/com/mobiledevpro/ui/theme/Type.kt
@@ -0,0 +1,34 @@
+package com.mobiledevpro.ui.theme
+
+import androidx.compose.material3.Typography
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.sp
+
+// Set of Material typography styles to start with
+val Typography = Typography(
+ bodyLarge = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 16.sp,
+ lineHeight = 24.sp,
+ letterSpacing = 0.5.sp
+ )
+ /* Other default text styles to override
+ titleLarge = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 22.sp,
+ lineHeight = 28.sp,
+ letterSpacing = 0.sp
+ ),
+ labelSmall = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Medium,
+ fontSize = 11.sp,
+ lineHeight = 16.sp,
+ letterSpacing = 0.5.sp
+ )
+ */
+)
\ No newline at end of file
diff --git a/core/ui/src/main/kotlin/com/mobiledevpro/ui/vm/BaseViewModel.kt b/core/ui/src/main/kotlin/com/mobiledevpro/ui/vm/BaseViewModel.kt
new file mode 100644
index 0000000..acad53c
--- /dev/null
+++ b/core/ui/src/main/kotlin/com/mobiledevpro/ui/vm/BaseViewModel.kt
@@ -0,0 +1,15 @@
+package com.mobiledevpro.ui.vm
+
+import androidx.lifecycle.ViewModel
+import com.mobiledevpro.ui.state.UIState
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+abstract class BaseViewModel : ViewModel() {
+
+ protected val _uiState: MutableStateFlow = MutableStateFlow(initUIState())
+ val uiState: StateFlow = _uiState.asStateFlow()
+
+ abstract fun initUIState(): State
+}
\ No newline at end of file
diff --git a/core/ui/src/main/res/drawable/ic_arrow_back_white_24dp.xml b/core/ui/src/main/res/drawable/ic_arrow_back_white_24dp.xml
new file mode 100644
index 0000000..6c3197a
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_arrow_back_white_24dp.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/core/ui/src/main/res/raw/ic_crowd.json b/core/ui/src/main/res/raw/ic_crowd.json
new file mode 100644
index 0000000..3aa4e87
--- /dev/null
+++ b/core/ui/src/main/res/raw/ic_crowd.json
@@ -0,0 +1 @@
+{"v":"5.5.8","fr":29.9700012207031,"ip":0,"op":31.0000012626559,"w":200,"h":200,"nm":"CROWN","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 8","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[100,100,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[17,17,100],"ix":6}},"ao":0,"ip":0,"op":31.0000012626559,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"CROWN Outlines 2","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,29.273,0],"ix":2},"a":{"a":0,"k":[540,540,0],"ix":1},"s":{"a":0,"k":[82.758,82.758,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[7.654,0],[0,0],[0,7.315],[0,0],[0,0],[0,0]],"o":[[0,0],[-7.654,0],[0,0],[0,0],[0,0],[-0.272,7.053]],"v":[[191.761,19.983],[-192.033,19.983],[-205.701,6.922],[-205.701,-19.982],[205.701,-19.982],[205.701,6.922]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.6090786803,0.002458892383,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[540,739.357],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-22.234,0],[0,0],[0,-113.765],[0,0],[0,0],[0,0],[0,22.258],[22.234,0],[0,-22.259],[-1.371,-3.847],[0,0],[0,0],[0,14.015],[22.235,0],[0,-22.258],[-10.98,-7.145],[0,0],[0,0],[0,4.397],[22.234,0],[0,-22.259]],"o":[[0,0],[0,0],[0,0],[0,-113.765],[0,0],[22.234,0],[0,-22.259],[-22.235,0],[0,4.397],[0,0],[0,0],[10.703,-7.145],[0,-22.258],[-22.235,0],[0,14.015],[0,0],[0,0],[1.098,-3.847],[0,-22.259],[-21.96,0.275],[0,22.258]],"v":[[-261.735,-6.32],[-260.088,-6.32],[-206.561,224.508],[206.562,224.508],[260.089,-6.32],[261.737,-6.32],[302.087,-46.715],[261.737,-87.11],[221.384,-46.715],[223.305,-34.349],[126.407,22.534],[22.099,-150.588],[40.214,-184.113],[-0.136,-224.508],[-40.489,-184.113],[-22.371,-150.588],[-126.681,22.534],[-223.58,-34.349],[-221.658,-46.715],[-262.009,-87.11],[-302.087,-46.715]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.6090786803,0.002458892383,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[540,474.424],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":31.0000012626559,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 1","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":28,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[-372,-51,0],"to":[140.667,3.333,0],"ti":[-140.667,-3.333,0]},{"t":16.0000006516934,"s":[472,-31,0]}],"ix":2},"a":{"a":0,"k":[-90,-13,0],"ix":1},"s":{"a":0,"k":[100,159.775,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[148,562],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-90,-13],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":31.0000012626559,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"CROWN Outlines","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,29.273,0],"ix":2},"a":{"a":0,"k":[540,540,0],"ix":1},"s":{"a":0,"k":[82.758,82.758,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[7.654,0],[0,0],[0,7.315],[0,0],[0,0],[0,0]],"o":[[0,0],[-7.654,0],[0,0],[0,0],[0,0],[-0.272,7.053]],"v":[[191.761,19.983],[-192.033,19.983],[-205.701,6.922],[-205.701,-19.982],[205.701,-19.982],[205.701,6.922]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.6090786803,0.002458892383,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[540,739.357],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-22.234,0],[0,0],[0,-113.765],[0,0],[0,0],[0,0],[0,22.258],[22.234,0],[0,-22.259],[-1.371,-3.847],[0,0],[0,0],[0,14.015],[22.235,0],[0,-22.258],[-10.98,-7.145],[0,0],[0,0],[0,4.397],[22.234,0],[0,-22.259]],"o":[[0,0],[0,0],[0,0],[0,-113.765],[0,0],[22.234,0],[0,-22.259],[-22.235,0],[0,4.397],[0,0],[0,0],[10.703,-7.145],[0,-22.258],[-22.235,0],[0,14.015],[0,0],[0,0],[1.098,-3.847],[0,-22.259],[-21.96,0.275],[0,22.258]],"v":[[-261.735,-6.32],[-260.088,-6.32],[-206.561,224.508],[206.562,224.508],[260.089,-6.32],[261.737,-6.32],[302.087,-46.715],[261.737,-87.11],[221.384,-46.715],[223.305,-34.349],[126.407,22.534],[22.099,-150.588],[40.214,-184.113],[-0.136,-224.508],[-40.489,-184.113],[-22.371,-150.588],[-126.681,22.534],[-223.58,-34.349],[-221.658,-46.715],[-262.009,-87.11],[-302.087,-46.715]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.6090786803,0.002458892383,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[540,474.424],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":31.0000012626559,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/core/util/.gitignore b/core/util/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/core/util/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/core/util/build.gradle.kts b/core/util/build.gradle.kts
new file mode 100644
index 0000000..29b9440
--- /dev/null
+++ b/core/util/build.gradle.kts
@@ -0,0 +1,3 @@
+plugins {
+ id("core-module")
+}
\ No newline at end of file
diff --git a/core/util/consumer-rules.pro b/core/util/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/core/util/proguard-rules.pro b/core/util/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/core/util/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/core/util/src/main/AndroidManifest.xml b/core/util/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..568741e
--- /dev/null
+++ b/core/util/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/core/util/src/main/kotlin/com/mobiledevpro/util/Constant.kt b/core/util/src/main/kotlin/com/mobiledevpro/util/Constant.kt
new file mode 100644
index 0000000..b5358e9
--- /dev/null
+++ b/core/util/src/main/kotlin/com/mobiledevpro/util/Constant.kt
@@ -0,0 +1,22 @@
+package com.mobiledevpro.util/*
+ * Copyright 2023 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+
+object Constant {
+ const val LOG_TAG_DEBUG = "app.debug"
+}
\ No newline at end of file
diff --git a/core/util/src/main/kotlin/com/mobiledevpro/util/TimeUtil.kt b/core/util/src/main/kotlin/com/mobiledevpro/util/TimeUtil.kt
new file mode 100644
index 0000000..f45085e
--- /dev/null
+++ b/core/util/src/main/kotlin/com/mobiledevpro/util/TimeUtil.kt
@@ -0,0 +1,13 @@
+package com.mobiledevpro.util
+
+import java.time.LocalDateTime
+import java.time.format.DateTimeFormatter
+
+object TimeUtil {
+
+ fun currentTimeStamp(): String = DateTimeFormatter.ofPattern(TIMESTAMP_PATTERN).let { format ->
+ LocalDateTime.now().format(format)
+ }
+}
+
+private const val TIMESTAMP_PATTERN = "yyyyMMdd_HHmmssSSS"
\ No newline at end of file
diff --git a/doc/modularization.png b/doc/modularization.png
new file mode 100644
index 0000000..2fd9aa8
Binary files /dev/null and b/doc/modularization.png differ
diff --git a/feature/chat_list/.gitignore b/feature/chat_list/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/feature/chat_list/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/feature/chat_list/build.gradle.kts b/feature/chat_list/build.gradle.kts
new file mode 100644
index 0000000..d5d59fc
--- /dev/null
+++ b/feature/chat_list/build.gradle.kts
@@ -0,0 +1,3 @@
+plugins {
+ id("feature-module")
+}
\ No newline at end of file
diff --git a/feature/chat_list/consumer-rules.pro b/feature/chat_list/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/feature/chat_list/proguard-rules.pro b/feature/chat_list/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/feature/chat_list/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/feature/chat_list/src/main/AndroidManifest.xml b/feature/chat_list/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..1d26c87
--- /dev/null
+++ b/feature/chat_list/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/feature/chat_list/src/main/kotlin/com/mobiledevpro/chatlist/di/Module.kt b/feature/chat_list/src/main/kotlin/com/mobiledevpro/chatlist/di/Module.kt
new file mode 100644
index 0000000..ae76a72
--- /dev/null
+++ b/feature/chat_list/src/main/kotlin/com/mobiledevpro/chatlist/di/Module.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2023 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.chatlist.di
+
+import com.mobiledevpro.chatlist.domain.usecase.GetChatListUseCase
+import com.mobiledevpro.chatlist.view.ChatListViewModel
+import org.koin.androidx.viewmodel.dsl.viewModelOf
+import org.koin.core.module.dsl.scopedOf
+import org.koin.dsl.module
+
+/**
+ * User Profile screen module
+ *
+ * Created on Jul 22, 2023.
+ *
+ */
+
+val featureChatListModule = module {
+
+ scope {
+ viewModelOf(::ChatListViewModel)
+ scopedOf(::GetChatListUseCase)
+ }
+}
\ No newline at end of file
diff --git a/feature/chat_list/src/main/kotlin/com/mobiledevpro/chatlist/domain/usecase/GetChatListUseCase.kt b/feature/chat_list/src/main/kotlin/com/mobiledevpro/chatlist/domain/usecase/GetChatListUseCase.kt
new file mode 100644
index 0000000..72af1d6
--- /dev/null
+++ b/feature/chat_list/src/main/kotlin/com/mobiledevpro/chatlist/domain/usecase/GetChatListUseCase.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2023 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.chatlist.domain.usecase
+
+import com.mobiledevpro.coroutines.BaseCoroutinesFLowUseCase
+import com.mobiledevpro.coroutines.None
+import com.mobiledevpro.domain.model.Chat
+import com.mobiledevpro.domain.model.fakeChatList
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+
+class GetChatListUseCase(
+
+) : BaseCoroutinesFLowUseCase, None>(Dispatchers.IO) {
+
+ override fun buildUseCaseFlow(params: None?): Flow> =
+ flowOf(fakeChatList)
+
+}
\ No newline at end of file
diff --git a/feature/chat_list/src/main/kotlin/com/mobiledevpro/chatlist/view/ChatListScreen.kt b/feature/chat_list/src/main/kotlin/com/mobiledevpro/chatlist/view/ChatListScreen.kt
new file mode 100644
index 0000000..04f6af4
--- /dev/null
+++ b/feature/chat_list/src/main/kotlin/com/mobiledevpro/chatlist/view/ChatListScreen.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.chatlist.view
+
+import android.widget.Toast
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.itemsIndexed
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.mobiledevpro.chatlist.view.component.ChatCard
+import com.mobiledevpro.domain.model.Chat
+import com.mobiledevpro.domain.model.fakeChatList
+import com.mobiledevpro.ui.component.ScreenBackground
+import com.mobiledevpro.ui.ext.dp
+import com.mobiledevpro.ui.ext.statusBarHeight
+import com.mobiledevpro.ui.theme.AppTheme
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+
+@Composable
+fun ChatListScreen(
+ state: StateFlow,
+ onClick: (Chat) -> Unit
+) {
+
+ val uiState by state.collectAsStateWithLifecycle()
+ val context = LocalContext.current
+
+ ScreenBackground(
+ modifier = Modifier
+ .fillMaxSize()
+ ) {
+
+ when (uiState) {
+ is ChatListUIState.Loading -> Loading()
+ is ChatListUIState.Empty -> NoChatFound()
+ is ChatListUIState.Success ->
+ ChatList(
+ chatList = (uiState as ChatListUIState.Success).profileList,
+ onClick = onClick
+ )
+
+ is ChatListUIState.Fail -> {
+ NoChatFound()
+ LaunchedEffect(Unit) {
+ Toast.makeText(
+ context,
+ (uiState as ChatListUIState.Fail).throwable.localizedMessage,
+ Toast.LENGTH_LONG
+ ).show()
+ }
+ }
+
+ }
+
+
+ }
+}
+
+@Composable
+private fun ChatList(chatList: List, onClick: (Chat) -> Unit) {
+
+ //Cause content is drawn edge-to-edge, let's set the top-padding for the first element in the list
+ val statusBarHeight: Dp = WindowInsets.statusBarHeight().dp()
+
+ LazyColumn {
+ itemsIndexed(chatList) { index, chat ->
+ ChatCard(
+ modifier = Modifier.then(
+ if (index == 0)
+ Modifier.padding(top = statusBarHeight)
+ else
+ Modifier
+ ),
+ chat = chat,
+ onClick = { onClick(chat) }
+ )
+ }
+ }
+}
+
+@Composable
+private fun NoChatFound() {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(16.dp)
+ ) {
+ Text(
+ text = "No chat found",
+ modifier = Modifier
+ .padding(16.dp)
+ .align(Alignment.Center),
+ style = MaterialTheme.typography.bodyLarge
+ )
+ }
+}
+
+@Composable
+private fun Loading() {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(16.dp)
+ ) {
+ Text(
+ text = "Loading...", modifier = Modifier
+ .padding(16.dp)
+ .align(Alignment.Center),
+ style = MaterialTheme.typography.bodyLarge
+ )
+ }
+}
+
+@Composable
+@Preview
+fun ChatListScreenPreview() {
+ AppTheme {
+ ChatListScreen(
+ state = MutableStateFlow(ChatListUIState.Success(fakeChatList)),
+ onClick = {}
+ )
+ }
+}
\ No newline at end of file
diff --git a/feature/chat_list/src/main/kotlin/com/mobiledevpro/chatlist/view/ChatListUIState.kt b/feature/chat_list/src/main/kotlin/com/mobiledevpro/chatlist/view/ChatListUIState.kt
new file mode 100644
index 0000000..a94f7bd
--- /dev/null
+++ b/feature/chat_list/src/main/kotlin/com/mobiledevpro/chatlist/view/ChatListUIState.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.chatlist.view
+
+import com.mobiledevpro.domain.model.Chat
+import com.mobiledevpro.ui.state.UIState
+
+/**
+ * UI state for [com.mobiledevpro.chatlist.view.ChatListScreen]
+ *
+ * Created on Feb 04, 2023.
+ *
+ */
+sealed interface ChatListUIState : UIState {
+
+ data object Empty : ChatListUIState
+
+ data object Loading : ChatListUIState
+
+ class Success(val profileList : List) : ChatListUIState
+
+ class Fail(val throwable: Throwable) : ChatListUIState
+}
\ No newline at end of file
diff --git a/feature/chat_list/src/main/kotlin/com/mobiledevpro/chatlist/view/ChatListViewModel.kt b/feature/chat_list/src/main/kotlin/com/mobiledevpro/chatlist/view/ChatListViewModel.kt
new file mode 100644
index 0000000..a73c80c
--- /dev/null
+++ b/feature/chat_list/src/main/kotlin/com/mobiledevpro/chatlist/view/ChatListViewModel.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.chatlist.view
+
+import android.util.Log
+import androidx.lifecycle.viewModelScope
+import com.mobiledevpro.chatlist.domain.usecase.GetChatListUseCase
+import com.mobiledevpro.ui.vm.BaseViewModel
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+
+class ChatListViewModel(
+ private val getChatListUseCase: GetChatListUseCase
+) : BaseViewModel() {
+
+ override fun initUIState(): ChatListUIState = ChatListUIState.Empty
+
+ init {
+ Log.d("UI", "ChatListViewModel init")
+ observeChatList()
+ }
+
+ private fun observeChatList() {
+ getChatListUseCase.execute()
+ .onEach { result ->
+ result.onSuccess { list ->
+ ChatListUIState.Success(list)
+ .also { _uiState.value = it }
+ }.onFailure {
+ //TODO: show an error
+ }
+ }.launchIn(viewModelScope)
+ }
+}
\ No newline at end of file
diff --git a/feature/chat_list/src/main/kotlin/com/mobiledevpro/chatlist/view/component/ChatCard.kt b/feature/chat_list/src/main/kotlin/com/mobiledevpro/chatlist/view/component/ChatCard.kt
new file mode 100644
index 0000000..0c50af9
--- /dev/null
+++ b/feature/chat_list/src/main/kotlin/com/mobiledevpro/chatlist/view/component/ChatCard.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.chatlist.view.component
+
+import android.net.Uri
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Badge
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.mobiledevpro.domain.model.Chat
+import com.mobiledevpro.domain.model.PeopleProfile
+import com.mobiledevpro.domain.model.fakePeopleProfileList
+import com.mobiledevpro.domain.model.fakeUser
+import com.mobiledevpro.domain.model.name
+import com.mobiledevpro.ui.component.CardItem
+import com.mobiledevpro.ui.component.ProfilePicture
+import com.mobiledevpro.ui.component.ProfilePictureSize
+import com.mobiledevpro.ui.theme.AppTheme
+
+/**
+ * Fo chat list screen
+ *
+ * Created on May 06, 2023.
+ *
+ */
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+internal fun ChatCard(
+ modifier: Modifier = Modifier,
+ chat: Chat,
+ onClick: () -> Unit
+) {
+
+ CardItem(
+ modifier = modifier
+ .clickable { onClick.invoke() }
+ ) {
+
+ Box {
+ Row(
+ modifier = Modifier
+ .padding(horizontal = 16.dp, vertical = 24.dp)
+ .fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+
+ ChatPicture(
+ profileList = chat.peopleList
+ )
+
+ Text(
+ text = chat.name(),
+ style = MaterialTheme.typography.bodyMedium,
+ maxLines = 2,
+ overflow = TextOverflow.Ellipsis,
+ modifier = Modifier
+ .padding(horizontal = 8.dp)
+ )
+
+ }
+
+ if (chat.unreadMsgCount > 0)
+ Badge(
+ containerColor = MaterialTheme.colorScheme.tertiary,
+ modifier = Modifier
+ .align(Alignment.BottomEnd)
+ .padding(8.dp)
+ ) {
+ Text(
+ text = if (chat.unreadMsgCount > 99) "99+" else chat.unreadMsgCount.toString(),
+ style = MaterialTheme.typography.labelSmall
+ )
+ }
+ }
+
+ }
+}
+
+@Composable
+fun ChatPicture(profileList: List, modifier: Modifier = Modifier) {
+
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Box(modifier = modifier) {
+ profileList.take(VISIBLE_PROFILES_COUNT).forEachIndexed { index, profile ->
+
+ ProfilePicture(
+ profile.photo ?: Uri.EMPTY,
+ profile.status,
+ size = ProfilePictureSize.SMALL,
+ modifier = Modifier.padding(start = 16.dp * index)
+ )
+
+ }
+ }
+ if (profileList.size > VISIBLE_PROFILES_COUNT)
+ Text(
+ text = "+${profileList.size - 3}",
+ style = MaterialTheme.typography.bodySmall,
+ modifier = Modifier.padding(4.dp)
+ )
+ }
+}
+
+@Composable
+@Preview
+fun ChatCardPreview() {
+ val peopleList = fakePeopleProfileList.take(2).sortedByDescending { !it.status }
+ val chat = Chat(fakeUser, peopleList)
+ AppTheme {
+ ChatCard(
+ chat = chat,
+ onClick = {})
+ }
+}
+
+const val VISIBLE_PROFILES_COUNT = 3
\ No newline at end of file
diff --git a/feature/home/.gitignore b/feature/home/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/feature/home/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/feature/home/build.gradle.kts b/feature/home/build.gradle.kts
new file mode 100644
index 0000000..4c12a42
--- /dev/null
+++ b/feature/home/build.gradle.kts
@@ -0,0 +1,3 @@
+plugins {
+ id("feature-module")
+}
\ No newline at end of file
diff --git a/feature/home/consumer-rules.pro b/feature/home/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/feature/home/proguard-rules.pro b/feature/home/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/feature/home/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/feature/home/src/main/AndroidManifest.xml b/feature/home/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..568741e
--- /dev/null
+++ b/feature/home/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/feature/home/src/main/kotlin/com/mobiledevpro/home/view/HomeScreen.kt b/feature/home/src/main/kotlin/com/mobiledevpro/home/view/HomeScreen.kt
new file mode 100644
index 0000000..022b94f
--- /dev/null
+++ b/feature/home/src/main/kotlin/com/mobiledevpro/home/view/HomeScreen.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.home.view
+
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Scaffold
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.mobiledevpro.ui.component.ScreenBackground
+
+@Composable
+fun HomeScreen(
+ nestedNavGraph: @Composable () -> Unit,
+ bottomBar: @Composable () -> Unit
+) {
+
+ Scaffold(
+ bottomBar = bottomBar,
+ contentWindowInsets = WindowInsets(left = 0, top = 0, right = 0, bottom = 0)
+ ) { paddingValues ->
+ ScreenBackground(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(paddingValues)
+ ) {
+ nestedNavGraph.invoke()
+ }
+ }
+}
\ No newline at end of file
diff --git a/feature/home/src/main/kotlin/com/mobiledevpro/home/view/HomeViewModel.kt b/feature/home/src/main/kotlin/com/mobiledevpro/home/view/HomeViewModel.kt
new file mode 100644
index 0000000..8f73f7c
--- /dev/null
+++ b/feature/home/src/main/kotlin/com/mobiledevpro/home/view/HomeViewModel.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.home.view
+
+import androidx.lifecycle.ViewModel
+
+/**
+ * Home screen
+ *
+ * Created on Jan 24, 2023.
+ *
+ */
+class HomeViewModel : ViewModel() {
+}
\ No newline at end of file
diff --git a/feature/onboarding/.gitignore b/feature/onboarding/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/feature/onboarding/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/feature/onboarding/build.gradle.kts b/feature/onboarding/build.gradle.kts
new file mode 100644
index 0000000..988f39f
--- /dev/null
+++ b/feature/onboarding/build.gradle.kts
@@ -0,0 +1,3 @@
+plugins {
+ id("feature-module")
+}
diff --git a/feature/onboarding/consumer-rules.pro b/feature/onboarding/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/feature/onboarding/proguard-rules.pro b/feature/onboarding/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/feature/onboarding/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/feature/onboarding/src/main/AndroidManifest.xml b/feature/onboarding/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..568741e
--- /dev/null
+++ b/feature/onboarding/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/feature/onboarding/src/main/kotlin/com/mobiledevpro/onboarding/view/OnBoardingFirstScreen.kt b/feature/onboarding/src/main/kotlin/com/mobiledevpro/onboarding/view/OnBoardingFirstScreen.kt
new file mode 100644
index 0000000..a9d3313
--- /dev/null
+++ b/feature/onboarding/src/main/kotlin/com/mobiledevpro/onboarding/view/OnBoardingFirstScreen.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.onboarding.view
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.mobiledevpro.ui.theme.AppTheme
+
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun OnBoardingFirstScreen() {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(color = Color(0x80FFCC99))
+ ) {
+ Text(
+ text = "1st OnBoarding",
+ textAlign = TextAlign.Center,
+ modifier = Modifier
+ .padding(16.dp)
+ .align(Alignment.Center)
+ .fillMaxWidth(),
+ style = MaterialTheme.typography.bodyLarge
+
+ )
+ }
+}
+
+@Preview
+@Composable
+fun OnBoardingFirstPreview() {
+ AppTheme {
+ OnBoardingFirstScreen()
+ }
+}
\ No newline at end of file
diff --git a/feature/onboarding/src/main/kotlin/com/mobiledevpro/onboarding/view/OnBoardingScreen.kt b/feature/onboarding/src/main/kotlin/com/mobiledevpro/onboarding/view/OnBoardingScreen.kt
new file mode 100644
index 0000000..eb9b5c2
--- /dev/null
+++ b/feature/onboarding/src/main/kotlin/com/mobiledevpro/onboarding/view/OnBoardingScreen.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.onboarding.view
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Button
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.mobiledevpro.ui.component.ScreenBackground
+
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun OnBoardingScreen(
+ nestedNavGraph: @Composable () -> Unit,
+ onNext: () -> Unit
+) {
+ Scaffold { paddingValues ->
+ ScreenBackground(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(paddingValues)
+ ) {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(16.dp)
+ ) {
+
+ nestedNavGraph.invoke()
+
+ Button(
+ onClick = onNext,
+ modifier = Modifier
+ .align(Alignment.BottomCenter)
+ .defaultMinSize(minWidth = 144.dp, minHeight = 48.dp)
+
+ ) {
+ Text(text = "Next")
+ }
+
+ }
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/feature/onboarding/src/main/kotlin/com/mobiledevpro/onboarding/view/OnBoardingSecondScreen.kt b/feature/onboarding/src/main/kotlin/com/mobiledevpro/onboarding/view/OnBoardingSecondScreen.kt
new file mode 100644
index 0000000..ae8ac3f
--- /dev/null
+++ b/feature/onboarding/src/main/kotlin/com/mobiledevpro/onboarding/view/OnBoardingSecondScreen.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.onboarding.view
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.mobiledevpro.ui.theme.AppTheme
+
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun OnBoardingSecondScreen() {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(16.dp)
+ .background(color = Color(0x80FFFF99))
+ ) {
+ Text(
+ text = "2nd OnBoarding",
+ textAlign = TextAlign.Center,
+ modifier = Modifier
+ .padding(16.dp)
+ .align(Alignment.Center)
+ .fillMaxWidth(),
+ style = MaterialTheme.typography.bodyLarge
+
+ )
+ }
+}
+
+@Preview
+@Composable
+fun OnBoardingSecondPreview() {
+ AppTheme {
+ OnBoardingSecondScreen()
+ }
+}
\ No newline at end of file
diff --git a/feature/onboarding/src/main/kotlin/com/mobiledevpro/onboarding/view/OnBoardingThirdScreen.kt b/feature/onboarding/src/main/kotlin/com/mobiledevpro/onboarding/view/OnBoardingThirdScreen.kt
new file mode 100644
index 0000000..25bca61
--- /dev/null
+++ b/feature/onboarding/src/main/kotlin/com/mobiledevpro/onboarding/view/OnBoardingThirdScreen.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.onboarding.view
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.mobiledevpro.ui.theme.AppTheme
+
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun OnBoardingThirdScreen() {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(16.dp)
+ .background(color = Color(0x80FF9999))
+ ) {
+ Text(
+ text = "3rd OnBoarding",
+ textAlign = TextAlign.Center,
+ modifier = Modifier
+ .padding(16.dp)
+ .align(Alignment.Center)
+ .fillMaxWidth(),
+ style = MaterialTheme.typography.bodyLarge
+
+ )
+ }
+}
+
+@Preview
+@Composable
+fun OnBoardingThirdPreview() {
+ AppTheme {
+ OnBoardingThirdScreen()
+ }
+}
\ No newline at end of file
diff --git a/feature/people/.gitignore b/feature/people/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/feature/people/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/feature/people/build.gradle.kts b/feature/people/build.gradle.kts
new file mode 100644
index 0000000..276865a
--- /dev/null
+++ b/feature/people/build.gradle.kts
@@ -0,0 +1,8 @@
+plugins {
+ id("feature-module")
+}
+
+dependencies {
+ api(projects.feature.peopleList.dependencyProject)
+ api(projects.feature.peopleProfile.dependencyProject)
+}
\ No newline at end of file
diff --git a/feature/people/consumer-rules.pro b/feature/people/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/feature/people/proguard-rules.pro b/feature/people/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/feature/people/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/feature/people/src/main/AndroidManifest.xml b/feature/people/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..1d26c87
--- /dev/null
+++ b/feature/people/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/feature/people/src/main/kotlin/com/mobiledevpro/people/view/PeopleScreen.kt b/feature/people/src/main/kotlin/com/mobiledevpro/people/view/PeopleScreen.kt
new file mode 100644
index 0000000..109c7cd
--- /dev/null
+++ b/feature/people/src/main/kotlin/com/mobiledevpro/people/view/PeopleScreen.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.people.view
+
+import android.util.Log
+import androidx.compose.runtime.Composable
+
+/**
+ * Host for People list and People profile
+ *
+ * Created on Feb 04, 2023.
+ *
+ */
+@Composable
+fun PeopleScreen(
+ nestedNavGraph: @Composable () -> Unit
+) {
+
+ Log.d("navigation", "PeopleScreen: ")
+
+ nestedNavGraph.invoke()
+}
\ No newline at end of file
diff --git a/feature/people_list/.gitignore b/feature/people_list/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/feature/people_list/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/feature/people_list/build.gradle.kts b/feature/people_list/build.gradle.kts
new file mode 100644
index 0000000..8bc006b
--- /dev/null
+++ b/feature/people_list/build.gradle.kts
@@ -0,0 +1,4 @@
+plugins {
+ id("feature-module")
+ id("testing-module")
+}
diff --git a/feature/people_list/consumer-rules.pro b/feature/people_list/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/feature/people_list/proguard-rules.pro b/feature/people_list/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/feature/people_list/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/feature/people_list/src/main/AndroidManifest.xml b/feature/people_list/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..568741e
--- /dev/null
+++ b/feature/people_list/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/feature/people_list/src/main/kotlin/com/mobiledevpro/peoplelist/di/Module.kt b/feature/people_list/src/main/kotlin/com/mobiledevpro/peoplelist/di/Module.kt
new file mode 100644
index 0000000..ce47a2c
--- /dev/null
+++ b/feature/people_list/src/main/kotlin/com/mobiledevpro/peoplelist/di/Module.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2023 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.peoplelist.di
+
+import com.mobiledevpro.peoplelist.domain.usecase.GetPeopleListUseCase
+import com.mobiledevpro.peoplelist.view.PeopleListViewModel
+import org.koin.androidx.viewmodel.dsl.viewModelOf
+import org.koin.core.module.dsl.scopedOf
+import org.koin.dsl.module
+
+/**
+ * User Profile screen module
+ *
+ * Created on Jul 22, 2023.
+ *
+ */
+
+val featurePeopleListModule = module {
+
+ scope {
+ viewModelOf(::PeopleListViewModel)
+ scopedOf(::GetPeopleListUseCase)
+ }
+}
\ No newline at end of file
diff --git a/feature/people_list/src/main/kotlin/com/mobiledevpro/peoplelist/domain/usecase/GetPeopleListUseCase.kt b/feature/people_list/src/main/kotlin/com/mobiledevpro/peoplelist/domain/usecase/GetPeopleListUseCase.kt
new file mode 100644
index 0000000..ef46f05
--- /dev/null
+++ b/feature/people_list/src/main/kotlin/com/mobiledevpro/peoplelist/domain/usecase/GetPeopleListUseCase.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2023 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.peoplelist.domain.usecase
+
+import com.mobiledevpro.coroutines.BaseCoroutinesFLowUseCase
+import com.mobiledevpro.coroutines.None
+import com.mobiledevpro.domain.model.PeopleProfile
+import com.mobiledevpro.domain.model.fakePeopleProfileList
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+
+class GetPeopleListUseCase(
+
+) : BaseCoroutinesFLowUseCase, None>(Dispatchers.IO) {
+
+ override fun buildUseCaseFlow(params: None?): Flow> =
+ flowOf(fakePeopleProfileList)
+}
\ No newline at end of file
diff --git a/feature/people_list/src/main/kotlin/com/mobiledevpro/peoplelist/view/PeopleListScreen.kt b/feature/people_list/src/main/kotlin/com/mobiledevpro/peoplelist/view/PeopleListScreen.kt
new file mode 100644
index 0000000..5744d4e
--- /dev/null
+++ b/feature/people_list/src/main/kotlin/com/mobiledevpro/peoplelist/view/PeopleListScreen.kt
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.peoplelist.view
+
+import android.widget.Toast
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.itemsIndexed
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.mobiledevpro.domain.model.PeopleProfile
+import com.mobiledevpro.peoplelist.view.component.ProfileCard
+import com.mobiledevpro.ui.component.ScreenBackground
+import com.mobiledevpro.ui.ext.dp
+import com.mobiledevpro.ui.ext.statusBarHeight
+import com.mobiledevpro.ui.theme.AppTheme
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+
+@Composable
+fun PeopleListScreen(
+ stateFlow: StateFlow,
+ onNavigateToProfile: (profileId: Int) -> Unit
+) {
+
+ val uiState by stateFlow.collectAsStateWithLifecycle()
+ val context = LocalContext.current
+
+ ScreenBackground(
+ modifier = Modifier
+ .fillMaxSize()
+ ) {
+
+ when (uiState) {
+ is PeopleProfileUIState.Loading -> Loading()
+ is PeopleProfileUIState.Empty -> NoPeopleFound()
+ is PeopleProfileUIState.Success ->
+ PeopleList(
+ list = (uiState as PeopleProfileUIState.Success).profileList,
+ onProfileClick = onNavigateToProfile
+ )
+
+ is PeopleProfileUIState.Fail -> {
+ NoPeopleFound()
+ LaunchedEffect(Unit) {
+ Toast.makeText(
+ context,
+ (uiState as PeopleProfileUIState.Fail).throwable.localizedMessage,
+ Toast.LENGTH_LONG
+ ).show()
+ }
+ }
+
+ }
+
+ }
+}
+
+@Composable
+private fun NoPeopleFound() {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(16.dp)
+ ) {
+ Text(
+ text = "No people found",
+ modifier = Modifier
+ .padding(16.dp)
+ .align(Alignment.Center),
+ style = MaterialTheme.typography.bodyLarge
+ )
+ }
+}
+
+@Composable
+private fun Loading() {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(16.dp)
+ ) {
+ Text(
+ text = "Loading...", modifier = Modifier
+ .padding(16.dp)
+ .align(Alignment.Center),
+ style = MaterialTheme.typography.bodyLarge
+ )
+ }
+}
+
+@Composable
+private fun PeopleList(list: List, onProfileClick: (profileId: Int) -> Unit) {
+
+ //Cause content is drawn edge-to-edge, let's set the top-padding for the first element in the list
+ val statusBarHeight: Dp = WindowInsets.statusBarHeight().dp()
+
+ LazyColumn {
+ itemsIndexed(
+ items = list,
+ key = { _, item -> item.listKey() }
+ ) { index, profile ->
+ ProfileCard(
+ modifier = Modifier.then(
+ if (index == 0)
+ Modifier.padding(top = statusBarHeight)
+ else
+ Modifier
+ ),
+
+
+ item = profile,
+ onClick = { onProfileClick(profile.id) })
+ }
+ }
+}
+
+
+@Preview
+@Composable
+fun PeopleListPreview() {
+ AppTheme {
+ PeopleListScreen(
+ stateFlow = MutableStateFlow(PeopleProfileUIState.Empty),
+ onNavigateToProfile = { }
+ )
+ }
+}
\ No newline at end of file
diff --git a/feature/people_list/src/main/kotlin/com/mobiledevpro/peoplelist/view/PeopleListViewModel.kt b/feature/people_list/src/main/kotlin/com/mobiledevpro/peoplelist/view/PeopleListViewModel.kt
new file mode 100644
index 0000000..448c33a
--- /dev/null
+++ b/feature/people_list/src/main/kotlin/com/mobiledevpro/peoplelist/view/PeopleListViewModel.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.peoplelist.view
+
+import androidx.lifecycle.viewModelScope
+import com.mobiledevpro.peoplelist.domain.usecase.GetPeopleListUseCase
+import com.mobiledevpro.ui.vm.BaseViewModel
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+
+class PeopleListViewModel(
+ private val getPeopleListUseCase: GetPeopleListUseCase
+) : BaseViewModel() {
+
+ override fun initUIState(): PeopleProfileUIState = PeopleProfileUIState.Loading
+
+ init {
+ observePeopleList()
+ }
+
+ private fun observePeopleList() {
+
+ getPeopleListUseCase.execute()
+ .onEach { result ->
+ result.onSuccess { list ->
+ PeopleProfileUIState.Success(list)
+ .also { _uiState.value = it }
+ }.onFailure {
+ //TODO: show error
+ }
+ }.launchIn(viewModelScope)
+ }
+
+}
\ No newline at end of file
diff --git a/feature/people_list/src/main/kotlin/com/mobiledevpro/peoplelist/view/PeopleProfileUIState.kt b/feature/people_list/src/main/kotlin/com/mobiledevpro/peoplelist/view/PeopleProfileUIState.kt
new file mode 100644
index 0000000..3011ebb
--- /dev/null
+++ b/feature/people_list/src/main/kotlin/com/mobiledevpro/peoplelist/view/PeopleProfileUIState.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.peoplelist.view
+
+import com.mobiledevpro.domain.model.PeopleProfile
+import com.mobiledevpro.ui.state.UIState
+
+/**
+ * UI state for [com.mobiledevpro.peoplelist.view.PeopleListScreen]
+ *
+ * Created on Feb 04, 2023.
+ *
+ */
+sealed interface PeopleProfileUIState : UIState {
+
+ object Empty : PeopleProfileUIState
+
+ object Loading : PeopleProfileUIState
+
+ class Success(val profileList : List) : PeopleProfileUIState
+
+ class Fail(val throwable: Throwable) : PeopleProfileUIState
+}
\ No newline at end of file
diff --git a/feature/people_list/src/main/kotlin/com/mobiledevpro/peoplelist/view/component/ProfileCard.kt b/feature/people_list/src/main/kotlin/com/mobiledevpro/peoplelist/view/component/ProfileCard.kt
new file mode 100644
index 0000000..a7cdfb3
--- /dev/null
+++ b/feature/people_list/src/main/kotlin/com/mobiledevpro/peoplelist/view/component/ProfileCard.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.peoplelist.view.component
+
+import android.net.Uri
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.mobiledevpro.domain.model.PeopleProfile
+import com.mobiledevpro.ui.component.CardItem
+import com.mobiledevpro.ui.component.ProfileContent
+import com.mobiledevpro.ui.component.ProfilePicture
+import com.mobiledevpro.ui.component.ProfilePictureSize
+
+/**
+ * For People list
+ *
+ * Created on Feb 05, 2023.
+ *
+ */
+@Composable
+internal fun ProfileCard(modifier: Modifier = Modifier, item: PeopleProfile, onClick: () -> Unit) {
+ CardItem(
+ modifier = modifier
+ .clickable { onClick.invoke() }
+ ) {
+
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Start
+ ) {
+ ProfilePicture(
+ item.photo ?: Uri.EMPTY,
+ item.status,
+ size = ProfilePictureSize.MEDIUM,
+ modifier = Modifier.padding(16.dp)
+ )
+ ProfileContent(
+ userName = item.name,
+ subName = null,
+ isOnline = item.status,
+ alignment = Alignment.Start,
+ modifier = Modifier.padding(8.dp)
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/feature/people_list/src/test/kotlin/com/mobiledevpro/peoplelist/PeopleListViewModelTest.kt b/feature/people_list/src/test/kotlin/com/mobiledevpro/peoplelist/PeopleListViewModelTest.kt
new file mode 100644
index 0000000..4a48f2d
--- /dev/null
+++ b/feature/people_list/src/test/kotlin/com/mobiledevpro/peoplelist/PeopleListViewModelTest.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2023 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.peoplelist
+
+import app.cash.turbine.test
+import com.mobiledevpro.peoplelist.domain.usecase.GetPeopleListUseCase
+import com.mobiledevpro.peoplelist.view.PeopleListViewModel
+import com.mobiledevpro.peoplelist.view.PeopleProfileUIState
+import com.mobiledevpro.ui.state.UIState
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+
+
+class PeopleListViewModelTest {
+
+ private lateinit var vm: PeopleListViewModel
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Before
+ fun setUp() {
+ Dispatchers.setMain(StandardTestDispatcher())
+
+ val useCase = GetPeopleListUseCase()
+ vm = PeopleListViewModel(getPeopleListUseCase = useCase)
+ assertTrue(
+ "Initial state is incorrect: ${vm.uiState.value}",
+ (vm.uiState.value as UIState) == PeopleProfileUIState.Loading
+ )
+ }
+
+ @Test
+ fun stateTest() = runTest {
+
+ vm.uiState.test {
+ // assertEquals("State is not Loading", PeopleProfileUIState.Loading, awaitItem())
+
+ assertTrue(
+ "People list is empty",
+ (awaitItem() as PeopleProfileUIState.Success).profileList.isNotEmpty()
+ )
+ }
+ }
+
+ @After
+ fun finish() = Dispatchers.resetMain()
+}
\ No newline at end of file
diff --git a/feature/people_profile/.gitignore b/feature/people_profile/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/feature/people_profile/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/feature/people_profile/build.gradle.kts b/feature/people_profile/build.gradle.kts
new file mode 100644
index 0000000..d5d59fc
--- /dev/null
+++ b/feature/people_profile/build.gradle.kts
@@ -0,0 +1,3 @@
+plugins {
+ id("feature-module")
+}
\ No newline at end of file
diff --git a/feature/people_profile/consumer-rules.pro b/feature/people_profile/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/feature/people_profile/proguard-rules.pro b/feature/people_profile/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/feature/people_profile/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/feature/people_profile/src/main/AndroidManifest.xml b/feature/people_profile/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..568741e
--- /dev/null
+++ b/feature/people_profile/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/feature/people_profile/src/main/kotlin/com/mobiledevpro/people/profile/view/PeopleProfileScreen.kt b/feature/people_profile/src/main/kotlin/com/mobiledevpro/people/profile/view/PeopleProfileScreen.kt
new file mode 100644
index 0000000..e977860
--- /dev/null
+++ b/feature/people_profile/src/main/kotlin/com/mobiledevpro/people/profile/view/PeopleProfileScreen.kt
@@ -0,0 +1,223 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.people.profile.view
+
+import android.net.Uri
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.statusBarsPadding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.surfaceColorAtElevation
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.layout.boundsInParent
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.dp
+import com.mobiledevpro.domain.model.PeopleProfile
+import com.mobiledevpro.domain.model.fakePeopleProfileList
+import com.mobiledevpro.people.profile.R
+import com.mobiledevpro.ui.component.ProfileContent
+import com.mobiledevpro.ui.component.ProfilePicture
+import com.mobiledevpro.ui.component.ProfilePictureSize
+import com.mobiledevpro.ui.component.ScreenBackground
+import com.mobiledevpro.ui.theme.AppTheme
+import com.mobiledevpro.ui.R as RApp
+
+/**
+ * Profile screen for selected person from People list
+ *
+ * Created on Feb 03, 2023.
+ *
+ */
+
+@Composable
+fun PeopleProfileScreen(
+ profile: PeopleProfile,
+ onBackPressed: () -> Unit,
+ onOpenChatWith: (profile: PeopleProfile) -> Unit
+) {
+
+ val backgroundBoxTopOffset = remember { mutableIntStateOf(0) }
+
+ ScreenBackground(
+ modifier = Modifier
+ .fillMaxSize()
+ .statusBarsPadding()
+ ) {
+
+ //Background with rounded top-corners
+ Box(
+ modifier = Modifier
+ .offset { IntOffset(0, backgroundBoxTopOffset.value) }
+ .clip(RoundedCornerShape(topStart = 48.dp, topEnd = 48.dp))
+ .background(color = MaterialTheme.colorScheme.surfaceColorAtElevation(4.dp))
+ )
+
+ Column(
+ modifier = Modifier.fillMaxSize(),
+ verticalArrangement = Arrangement.Top
+ ) {
+ IconButton(
+ onClick = { onBackPressed() },
+ modifier = Modifier.padding(16.dp)
+ ) {
+ Icon(
+ painter = painterResource(id = RApp.drawable.ic_arrow_back_white_24dp),
+ contentDescription = ""
+ )
+ }
+
+ ProfilePicture(
+ photoUri = profile.photo ?: Uri.EMPTY,
+ onlineStatus = profile.status,
+ size = ProfilePictureSize.LARGE,
+ modifier = Modifier
+ .padding(paddingValues = PaddingValues(16.dp, 16.dp, 16.dp, 16.dp))
+ .align(Alignment.CenterHorizontally)
+ .onGloballyPositioned {
+ val rect = it.boundsInParent()
+ backgroundBoxTopOffset.value =
+ rect.topCenter.y.toInt() + (rect.bottomCenter.y - rect.topCenter.y).toInt() / 2
+ }
+ )
+
+ ProfileContent(
+ userName = profile.name,
+ isOnline = profile.status,
+ alignment = Alignment.CenterHorizontally,
+ modifier = Modifier
+ .padding(8.dp)
+ .align(Alignment.CenterHorizontally)
+ )
+
+ ProfileSocialIcons(modifier = Modifier.align(Alignment.CenterHorizontally))
+
+ Row(
+ modifier = Modifier
+ .fillMaxHeight()
+ .align(Alignment.CenterHorizontally),
+ verticalAlignment = Alignment.Bottom
+ ) {
+ Button(
+ onClick = {
+ profile.also(onOpenChatWith)
+ },
+ modifier = Modifier
+ .padding(bottom = 48.dp, top = 16.dp, start = 16.dp, end = 16.dp)
+ .defaultMinSize(minHeight = 48.dp, minWidth = 144.dp),
+ colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.tertiary),
+ ) {
+ Text(text = "Say Hi \uD83D\uDC4B")
+ }
+ }
+ }
+
+ }
+}
+
+@Composable
+fun ProfileSocialIcons(modifier: Modifier) {
+ Row(
+ modifier = modifier
+ ) {
+ IconButton(
+ onClick = {
+
+ }
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_instagram_white_48dp),
+ contentDescription = "",
+ modifier = Modifier.padding(4.dp),
+ )
+ }
+
+ IconButton(
+ onClick = {
+
+ }
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_linkedin_white_48dp),
+ contentDescription = "",
+ modifier = Modifier.padding(4.dp)
+ )
+ }
+
+ IconButton(
+ onClick = {
+
+ }
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_youtube_white_48dp),
+ contentDescription = "",
+ modifier = Modifier.padding(4.dp)
+ )
+ }
+
+ IconButton(
+ onClick = {
+
+ }
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_twitter_white_48dp),
+ contentDescription = "",
+ modifier = Modifier.padding(4.dp)
+ )
+ }
+ }
+}
+
+
+@Preview
+@Composable
+fun PeopleProfilePreview() {
+ AppTheme(darkTheme = true) {
+ fakePeopleProfileList.find { it.id == 2 }?.let {
+ PeopleProfileScreen(
+ it,
+ onBackPressed = {},
+ onOpenChatWith = {}
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/feature/people_profile/src/main/kotlin/com/mobiledevpro/people/profile/view/PeopleProfileViewModel.kt b/feature/people_profile/src/main/kotlin/com/mobiledevpro/people/profile/view/PeopleProfileViewModel.kt
new file mode 100644
index 0000000..dbbcf06
--- /dev/null
+++ b/feature/people_profile/src/main/kotlin/com/mobiledevpro/people/profile/view/PeopleProfileViewModel.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.people.profile.view
+
+import android.util.Log
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.ViewModel
+import com.mobiledevpro.domain.model.PeopleProfile
+import com.mobiledevpro.domain.model.fakePeopleProfileList
+import com.mobiledevpro.people.profile.view.args.PeopleProfileArgs
+
+/**
+ * Profile screen for selected person from People list
+ *
+ * Created on Feb 04, 2023.
+ *
+ */
+class PeopleProfileViewModel(
+ savedStateHandle: SavedStateHandle
+) : ViewModel() {
+
+ private val profileId : Int = PeopleProfileArgs(savedStateHandle).peopleProfileId
+
+
+
+ init {
+ Log.d("navigation", "PeopleProfileViewModel: args = $profileId")
+ }
+
+ fun getProfile() : PeopleProfile? = fakePeopleProfileList.find { it.id == profileId }
+}
\ No newline at end of file
diff --git a/feature/people_profile/src/main/kotlin/com/mobiledevpro/people/profile/view/args/PeopleProfileArgs.kt b/feature/people_profile/src/main/kotlin/com/mobiledevpro/people/profile/view/args/PeopleProfileArgs.kt
new file mode 100644
index 0000000..74068b8
--- /dev/null
+++ b/feature/people_profile/src/main/kotlin/com/mobiledevpro/people/profile/view/args/PeopleProfileArgs.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.people.profile.view.args
+
+import androidx.lifecycle.SavedStateHandle
+
+/**
+ *
+ * Created on Feb 04, 2023.
+ *
+ */
+class PeopleProfileArgs(val peopleProfileId: Int) {
+ constructor(savedStateHandle: SavedStateHandle) :
+ this(checkNotNull(savedStateHandle[PEOPLE_PROFILE_ID_ARG]) as Int)
+
+ companion object {
+ const val PEOPLE_PROFILE_ID_ARG = "peopleProfileId"
+ }
+}
\ No newline at end of file
diff --git a/feature/people_profile/src/main/res/drawable/ic_instagram_white_48dp.xml b/feature/people_profile/src/main/res/drawable/ic_instagram_white_48dp.xml
new file mode 100644
index 0000000..9c6addd
--- /dev/null
+++ b/feature/people_profile/src/main/res/drawable/ic_instagram_white_48dp.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/feature/people_profile/src/main/res/drawable/ic_linkedin_white_48dp.xml b/feature/people_profile/src/main/res/drawable/ic_linkedin_white_48dp.xml
new file mode 100644
index 0000000..c3c09cf
--- /dev/null
+++ b/feature/people_profile/src/main/res/drawable/ic_linkedin_white_48dp.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
diff --git a/feature/people_profile/src/main/res/drawable/ic_twitter_white_48dp.xml b/feature/people_profile/src/main/res/drawable/ic_twitter_white_48dp.xml
new file mode 100644
index 0000000..80fd439
--- /dev/null
+++ b/feature/people_profile/src/main/res/drawable/ic_twitter_white_48dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/feature/people_profile/src/main/res/drawable/ic_youtube_white_48dp.xml b/feature/people_profile/src/main/res/drawable/ic_youtube_white_48dp.xml
new file mode 100644
index 0000000..b4e7a9c
--- /dev/null
+++ b/feature/people_profile/src/main/res/drawable/ic_youtube_white_48dp.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/feature/subscription/.gitignore b/feature/subscription/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/feature/subscription/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/feature/subscription/build.gradle.kts b/feature/subscription/build.gradle.kts
new file mode 100644
index 0000000..d5d59fc
--- /dev/null
+++ b/feature/subscription/build.gradle.kts
@@ -0,0 +1,3 @@
+plugins {
+ id("feature-module")
+}
\ No newline at end of file
diff --git a/feature/subscription/consumer-rules.pro b/feature/subscription/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/feature/subscription/proguard-rules.pro b/feature/subscription/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/feature/subscription/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/feature/subscription/src/main/AndroidManifest.xml b/feature/subscription/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..568741e
--- /dev/null
+++ b/feature/subscription/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/feature/subscription/src/main/kotlin/com/mobiledevpro/subscription/SubscriptionScreen.kt b/feature/subscription/src/main/kotlin/com/mobiledevpro/subscription/SubscriptionScreen.kt
new file mode 100644
index 0000000..1838323
--- /dev/null
+++ b/feature/subscription/src/main/kotlin/com/mobiledevpro/subscription/SubscriptionScreen.kt
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.subscription
+
+import android.widget.Toast
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.systemBars
+import androidx.compose.material3.Button
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.mobiledevpro.ui.component.ScreenBackground
+import com.mobiledevpro.ui.theme.AppTheme
+
+@Composable
+fun SubscriptionScreen(onNavigateBack: () -> Unit) {
+ val context = LocalContext.current
+
+ val showToast: (message: String) -> Unit = { message ->
+ Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
+ }
+
+ Scaffold(
+ contentWindowInsets = WindowInsets.systemBars
+ ) { paddingValues ->
+ ScreenBackground(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(paddingValues)
+ ) {
+
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(16.dp)
+ ) {
+
+ Text(
+ text = "Be like a Pro",
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier
+ .align(Alignment.TopCenter)
+ .padding(16.dp)
+ )
+
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .align(Alignment.Center),
+ verticalArrangement = Arrangement.Center
+ ) {
+ Text(
+ text = "Subscribe on",
+ textAlign = TextAlign.Center,
+ modifier = Modifier
+ .padding(16.dp)
+ .align(Alignment.CenterHorizontally)
+ .fillMaxWidth(),
+ style = MaterialTheme.typography.bodyLarge
+
+ )
+
+ SubsButton(
+ text = "1 Month - 0.9\$",
+ modifier = Modifier.align(Alignment.CenterHorizontally)
+ ) {
+ showToast("Subscribing to 1 month...")
+ onNavigateBack()
+ }
+
+ SubsButton(
+ text = "6 Months - 3.99\$",
+ modifier = Modifier.align(Alignment.CenterHorizontally)
+ ) {
+ showToast("Subscribing to 6 months...")
+ onNavigateBack()
+ }
+
+ SubsButton(
+ text = "1 Year - 6.99\$",
+ modifier = Modifier.align(Alignment.CenterHorizontally)
+ ) {
+ showToast("Subscribing to 1 year...")
+ onNavigateBack()
+ }
+
+ }
+ }
+
+ /*
+
+ Column(
+ modifier = Modifier
+ .padding(16.dp)
+ .verticalScroll(rememberScrollState())
+ .height(IntrinsicSize.Max)
+ ) {
+
+ Header()
+
+ //If you remove Items, Footer will be centered at the screen as you could see on attached screenshots
+ Item(text = "Item 1")
+ Item(text = "Item 2")
+ Item(text = "Item 3")
+
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ //.background(color = Color.Blue)
+ ) {
+ Footer(modifier = Modifier.align(Alignment.Center))
+ }
+ }
+
+ */
+
+ }
+ }
+
+
+}
+/*
+@Composable
+fun Header() {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(100.dp)
+ .background(color = Color.Yellow)
+ )
+}
+
+@Composable
+fun Item(text: String) {
+ Box(
+ modifier = Modifier
+ .padding(vertical = 8.dp)
+ .fillMaxWidth()
+ .height(50.dp)
+ .background(color = Color.White)
+
+ ) {
+ Text(
+ text = text,
+ modifier = Modifier.align(Alignment.Center),
+ style = MaterialTheme.typography.bodyLarge
+ )
+ }
+}
+
+@Composable
+fun Footer(modifier: Modifier) {
+ Box(
+ modifier = modifier
+ .padding(vertical = 8.dp)
+ .fillMaxWidth()
+ .height(100.dp)
+ .background(color = Color.Gray)
+
+ ) {
+ Text(
+ text = "Optional block",
+ modifier = Modifier.align(Alignment.Center),
+ style = MaterialTheme.typography.bodyLarge
+ )
+ }
+}
+
+ */
+
+@Composable
+fun SubsButton(
+ text: String,
+ modifier: Modifier,
+ onClick: () -> Unit
+) {
+ Button(
+ onClick = onClick,
+ modifier = modifier
+ .padding(8.dp)
+ .defaultMinSize(minWidth = 192.dp, minHeight = 48.dp)
+
+
+ ) {
+ Text(text = text)
+ }
+}
+
+@Preview
+@Composable
+fun SubscriptionScreenPreview() {
+ AppTheme {
+ SubscriptionScreen {}
+ }
+}
\ No newline at end of file
diff --git a/feature/user_profile/.gitignore b/feature/user_profile/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/feature/user_profile/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/feature/user_profile/build.gradle.kts b/feature/user_profile/build.gradle.kts
new file mode 100644
index 0000000..d5d59fc
--- /dev/null
+++ b/feature/user_profile/build.gradle.kts
@@ -0,0 +1,3 @@
+plugins {
+ id("feature-module")
+}
\ No newline at end of file
diff --git a/feature/user_profile/consumer-rules.pro b/feature/user_profile/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/feature/user_profile/proguard-rules.pro b/feature/user_profile/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/feature/user_profile/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/feature/user_profile/src/main/AndroidManifest.xml b/feature/user_profile/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..568741e
--- /dev/null
+++ b/feature/user_profile/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/feature/user_profile/src/main/kotlin/com/mobiledevpro/user/profile/di/Module.kt b/feature/user_profile/src/main/kotlin/com/mobiledevpro/user/profile/di/Module.kt
new file mode 100644
index 0000000..bd3b149
--- /dev/null
+++ b/feature/user_profile/src/main/kotlin/com/mobiledevpro/user/profile/di/Module.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2023 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.user.profile.di
+
+import com.mobiledevpro.user.profile.domain.usecase.GetUserProfileUseCase
+import com.mobiledevpro.user.profile.view.vm.ProfileViewModel
+import org.koin.androidx.viewmodel.dsl.viewModelOf
+import org.koin.core.module.dsl.scopedOf
+import org.koin.dsl.module
+
+/**
+ * User Profile screen module
+ *
+ * Created on Jul 22, 2023.
+ *
+ */
+
+val featureUserProfileModule = module {
+
+ scope {
+ viewModelOf(::ProfileViewModel)
+ scopedOf(::GetUserProfileUseCase)
+ }
+}
\ No newline at end of file
diff --git a/feature/user_profile/src/main/kotlin/com/mobiledevpro/user/profile/domain/usecase/GetUserProfileUseCase.kt b/feature/user_profile/src/main/kotlin/com/mobiledevpro/user/profile/domain/usecase/GetUserProfileUseCase.kt
new file mode 100644
index 0000000..d920a4d
--- /dev/null
+++ b/feature/user_profile/src/main/kotlin/com/mobiledevpro/user/profile/domain/usecase/GetUserProfileUseCase.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2023 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.user.profile.domain.usecase
+
+import com.mobiledevpro.coroutines.BaseCoroutinesFLowUseCase
+import com.mobiledevpro.coroutines.None
+import com.mobiledevpro.domain.model.UserProfile
+import com.mobiledevpro.domain.model.fakeUser
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+
+class GetUserProfileUseCase(
+
+) : BaseCoroutinesFLowUseCase(Dispatchers.IO) {
+
+ override fun buildUseCaseFlow(params: None?): Flow =
+ flowOf(fakeUser)
+}
\ No newline at end of file
diff --git a/feature/user_profile/src/main/kotlin/com/mobiledevpro/user/profile/view/ProfileScreen.kt b/feature/user_profile/src/main/kotlin/com/mobiledevpro/user/profile/view/ProfileScreen.kt
new file mode 100644
index 0000000..d0ab13d
--- /dev/null
+++ b/feature/user_profile/src/main/kotlin/com/mobiledevpro/user/profile/view/ProfileScreen.kt
@@ -0,0 +1,217 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.user.profile.view
+
+import android.net.Uri
+import android.util.Log
+import android.widget.Toast
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.statusBarsPadding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.rounded.ExitToApp
+import androidx.compose.material3.Divider
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.surfaceColorAtElevation
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.layout.boundsInParent
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.airbnb.lottie.compose.LottieAnimation
+import com.airbnb.lottie.compose.LottieCompositionSpec
+import com.airbnb.lottie.compose.rememberLottieComposition
+import com.mobiledevpro.domain.model.UserProfile
+import com.mobiledevpro.domain.model.fakeUser
+import com.mobiledevpro.ui.component.LabeledDarkModeSwitch
+import com.mobiledevpro.ui.component.ProfileContent
+import com.mobiledevpro.ui.component.ProfilePicture
+import com.mobiledevpro.ui.component.ProfilePictureSize
+import com.mobiledevpro.ui.component.ScreenBackground
+import com.mobiledevpro.ui.component.SettingsButton
+import com.mobiledevpro.ui.theme.AppTheme
+import com.mobiledevpro.ui.theme._darkModeState
+import com.mobiledevpro.ui.theme.darkModeState
+import com.mobiledevpro.user.profile.view.state.UserProfileUIState
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.update
+import com.mobiledevpro.ui.R as RUi
+
+@Composable
+fun ProfileScreen(
+ state: StateFlow,
+ onNavigateToSubscription: () -> Unit
+) {
+ Log.d("navigation", "ProfileScreen:")
+
+ val uiState by state.collectAsStateWithLifecycle()
+ val context = LocalContext.current
+
+ val backgroundBoxTopOffset = remember { mutableStateOf(0) }
+ val darkModeOn = remember { mutableStateOf(darkModeState.value) }
+
+ val user : UserProfile = when(uiState) {
+ is UserProfileUIState.Success -> (uiState as UserProfileUIState.Success).userProfile
+ is UserProfileUIState.Fail -> {
+ LaunchedEffect(Unit) {
+ Toast.makeText(
+ context,
+ (uiState as UserProfileUIState.Fail).throwable.localizedMessage,
+ Toast.LENGTH_LONG
+ ).show()
+ }
+
+ UserProfile("", "", false, Uri.EMPTY)
+ }
+
+ else -> UserProfile("", "", false, Uri.EMPTY)
+ }
+
+ ScreenBackground(
+ modifier = Modifier
+ .fillMaxSize()
+ .statusBarsPadding()
+ ) {
+
+
+ //Background with rounded top-corners
+ Box(
+ modifier = Modifier
+ .offset { IntOffset(0, backgroundBoxTopOffset.value) }
+ .clip(RoundedCornerShape(topStart = 48.dp, topEnd = 48.dp))
+ .background(color = MaterialTheme.colorScheme.surfaceColorAtElevation(4.dp))
+ )
+
+ Box(modifier = Modifier.fillMaxSize()) {
+
+ BeLikeAProButton(modifier = Modifier
+ .align(Alignment.TopEnd)
+ .padding(8.dp)
+ .size(56.dp)
+ .clickable { onNavigateToSubscription() })
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(start = 16.dp, end = 16.dp, bottom = 32.dp),
+ verticalArrangement = Arrangement.Top
+ ) {
+
+ ProfilePicture(
+ photoUri = user.photo,
+ onlineStatus = true,
+ size = ProfilePictureSize.LARGE,
+ modifier = Modifier
+ .padding(paddingValues = PaddingValues(16.dp, 16.dp, 16.dp, 16.dp))
+ .align(Alignment.CenterHorizontally)
+ .onGloballyPositioned {
+ val rect = it.boundsInParent()
+ backgroundBoxTopOffset.value =
+ rect.topCenter.y.toInt() + (rect.bottomCenter.y - rect.topCenter.y).toInt() / 2
+ }
+ )
+
+ ProfileContent(
+ userName = user.name,
+ subName = user.nickname,
+ isOnline = user.status,
+ alignment = Alignment.CenterHorizontally,
+ modifier = Modifier
+ .padding(8.dp)
+ .align(Alignment.CenterHorizontally)
+ )
+
+
+
+
+
+ Column(
+ modifier = Modifier
+ .fillMaxHeight()
+ .align(Alignment.CenterHorizontally),
+ verticalArrangement = Arrangement.Bottom
+ ) {
+
+ LabeledDarkModeSwitch(
+ label = "Dark mode",
+ checked = darkModeOn.value,
+ onCheckedChanged = { isDark ->
+ Log.d("main", "ProfileScreen: dark $isDark")
+ darkModeOn.value = isDark
+ _darkModeState.update {
+ isDark
+ }
+ })
+
+ Divider()
+
+ SettingsButton(
+ label = "Log Out",
+ icon = Icons.Rounded.ExitToApp,
+ onClick = {
+
+ }
+ )
+ }
+ }
+
+
+ }
+ }
+}
+
+@Composable
+fun BeLikeAProButton(modifier: Modifier) {
+ //Here is how to use Lottie animation with Compose https://github.com/airbnb/lottie/blob/master/android-compose.md
+
+ val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(RUi.raw.ic_crowd))
+
+ LottieAnimation(composition = composition, modifier = modifier)
+}
+
+@Preview
+@Composable
+fun ProfileScreenPreview() {
+ AppTheme(darkTheme = true) {
+ ProfileScreen(
+ state = MutableStateFlow(UserProfileUIState.Success(fakeUser)),
+ onNavigateToSubscription = {}
+ )
+ }
+}
\ No newline at end of file
diff --git a/feature/user_profile/src/main/kotlin/com/mobiledevpro/user/profile/view/state/UserProfileUIState.kt b/feature/user_profile/src/main/kotlin/com/mobiledevpro/user/profile/view/state/UserProfileUIState.kt
new file mode 100644
index 0000000..5b1a1b2
--- /dev/null
+++ b/feature/user_profile/src/main/kotlin/com/mobiledevpro/user/profile/view/state/UserProfileUIState.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2023 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.user.profile.view.state
+
+import com.mobiledevpro.domain.model.UserProfile
+import com.mobiledevpro.ui.state.UIState
+
+/**
+ * UI state for [com.mobiledevpro.user.profile.view.ProfileScreen]
+ *
+ * Created on May 09, 2023.
+ *
+ */
+sealed interface UserProfileUIState : UIState {
+
+ object Empty : UserProfileUIState
+
+ class Success(val userProfile: UserProfile) : UserProfileUIState
+
+ class Fail(val throwable: Throwable) : UserProfileUIState
+}
\ No newline at end of file
diff --git a/feature/user_profile/src/main/kotlin/com/mobiledevpro/user/profile/view/vm/ProfileViewModel.kt b/feature/user_profile/src/main/kotlin/com/mobiledevpro/user/profile/view/vm/ProfileViewModel.kt
new file mode 100644
index 0000000..09fbf1b
--- /dev/null
+++ b/feature/user_profile/src/main/kotlin/com/mobiledevpro/user/profile/view/vm/ProfileViewModel.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2022 | Dmitri Chernysh | https://mobile-dev.pro
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.mobiledevpro.user.profile.view.vm
+
+import android.util.Log
+import androidx.lifecycle.viewModelScope
+import com.mobiledevpro.ui.vm.BaseViewModel
+import com.mobiledevpro.user.profile.domain.usecase.GetUserProfileUseCase
+import com.mobiledevpro.user.profile.view.state.UserProfileUIState
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+class ProfileViewModel(
+ private val getUserProfileUseCase: GetUserProfileUseCase
+) : BaseViewModel() {
+
+ override fun initUIState(): UserProfileUIState = UserProfileUIState.Empty
+
+ init {
+ Log.d("UI", "ProfileViewModel init")
+ observeUserProfile()
+ }
+
+ private fun observeUserProfile() {
+ getUserProfileUseCase.execute()
+ .onEach { result ->
+ result.onSuccess { profile ->
+ UserProfileUIState.Success(profile)
+ .also { _uiState.value = it }
+ }.onFailure {
+ //TODO: show the error
+ }
+
+ }.launchIn(viewModelScope)
+ }
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..e71fb95
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,33 @@
+# Gradle properties
+# Configuration on demand attempts to configure only projects that are relevant for requested tasks
+org.gradle.configureondemand=true
+org.gradle.jvmargs=-Xmx4g -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx4g" -XX:+UseParallelGC
+org.gradle.parallel=true
+# With Configuration cache,
+# Gradle can skip the configuration phase entirely
+# when nothing that affects the build configuration has changed
+org.gradle.unsafe.configuration-cache=false
+# Should be enabled for better performance
+org.gradle.daemon=true
+# The cache stores previous build results, and greatly reduces the need to rebuild things when they have already been built locally.
+org.gradle.caching=true
+# Show more logs
+org.gradle.console=verbose
+# Application properties
+android.enableJetifier=false
+android.useAndroidX=true
+kapt.incremental.apt=true
+kotlin.code.style=official
+# Abdroid Build features default values
+android.defaults.buildfeatures.aidl=false
+android.defaults.buildfeatures.buildconfig=false
+android.defaults.buildfeatures.databinding=false
+android.defaults.buildfeatures.renderscript=false
+android.defaults.buildfeatures.resvalues=false
+android.defaults.buildfeatures.shaders=false
+android.defaults.buildfeatures.viewbinding=false
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
+android.nonFinalResIds=false
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
new file mode 100644
index 0000000..6a837b0
--- /dev/null
+++ b/gradle/libs.versions.toml
@@ -0,0 +1,177 @@
+[versions]
+# App
+app-version-code = "1"
+app-version-name = "0.1.0-alpha"
+sdk-min = "29"
+sdk-target = "34"
+sdk-compile = "34"
+
+# Plugins
+android-gradle = "8.1.4"
+kotlin = "1.9.22"
+kotlin-coroutines = "1.7.3"
+kotlin-serialization = "1.4.1"
+google-services = "4.4.1"
+plugin-crashlytics = "2.9.9"
+plugin-performance = "1.4.2"
+
+# Libs
+android-core-ktx = "1.12.0"
+activity = "1.8.2"
+navigation = "2.7.7"
+retrofit = "2.9.0"
+room = "2.4.3"
+lifecycle = "2.7.0"
+compose-bom = "2024.02.00"
+compose-compiler = "1.5.8"
+coil = "2.5.0"
+material3 = "1.2.0"
+koin = "3.5.3"
+data-store = "1.0.0"
+koin-compose = "3.5.3"
+coroutines-android = "1.7.3"
+workmanager = "2.9.0"
+inapp-update = "2.1.0"
+
+# Firebase
+firebase-bom = "32.7.2"
+
+# test libraries
+test-junit = "4.13.2"
+test-mockk = "1.13.9"
+test-coroutines-flow = "1.0.0"
+
+# android test libraries
+test-android-runner = "1.5.2"
+junit = "1.1.5"
+espresso-core = "3.5.1"
+
+# 3rd party libs
+lottie = "6.1.0"
+desugaring = "2.0.4"
+appcompat = "1.6.1"
+material = "1.11.0"
+
+[plugins]
+
+android-application = { id = "com.android.application", version.ref = "android-gradle" }
+android-library = { id = "com.android.library", version.ref = "android-gradle" }
+kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
+kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" }
+kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
+google-services = { id = "com.google.gms.google-services", version.ref = "google-services" }
+crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "plugin-crashlytics" }
+performance-monitor = { id = "com.google.firebase.firebase-perf", version.ref = "plugin-performance" }
+
+[libraries]
+
+androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "android-core-ktx" }
+activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activity" }
+activity-ktx = { group = "androidx.activity", name = "activity-ktx", version.ref = "activity" }
+lyfecycle-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycle" }
+lyfecycle-runtime = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" }
+lyfecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycle" }
+navigation = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation" }
+coil = { group = "io.coil-kt", name = "coil-compose", version.ref = "coil" }
+kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlin-coroutines" }
+kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
+gradle = { module = "com.android.tools.build:gradle", version.ref = "android-gradle" }
+gradle-api = { module = "com.android.tools.build:gradle-api", version.ref = "android-gradle" }
+data-store = { module = "androidx.datastore:datastore-preferences", version.ref = "data-store" }
+coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines-android" }
+workmanager = { module = "androidx.work:work-runtime-ktx", version.ref = "workmanager" }
+desugaring = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugaring" }
+inapp-update = { module = "com.google.android.play:app-update", version.ref = "inapp-update" }
+inapp-update-ext = { module = "com.google.android.play:app-update-ktx", version.ref = "inapp-update" }
+
+# Compose
+compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
+compose-ui = { group = "androidx.compose.ui", name = "ui" }
+compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
+compose-ui-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
+compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
+compose-ui-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
+compose-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" }
+compose-ui-util = { group = "androidx.compose.ui", name = "ui-util" }
+#compose-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout-compose", version.ref = "constraintlayout" }
+
+# test libraries
+test-junit = { module = "junit:junit", version.ref = "test-junit" }
+test-kotlin = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
+test-kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlin-coroutines" }
+test-mockk = { module = "io.mockk:mockk", version.ref = "test-mockk" }
+test-coroutines-flow = { module = "app.cash.turbine:turbine", version.ref = "test-coroutines-flow" }
+
+# Android test libraries
+test-android-compose = { group = "androidx.compose.ui", name = "ui-test-junit4" }
+test-android-runner = { module = "androidx.test:runner", version.ref = "test-android-runner" }
+junit = { group = "androidx.test.ext", name = "junit", version.ref = "junit" }
+espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" }
+
+#3rd party libs
+lottie = { module = "com.airbnb.android:lottie-compose", version.ref = "lottie" }
+
+# Koin for dependency injection
+koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
+koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" }
+koin-compose = { module = "io.insert-koin:koin-androidx-compose", version.ref = "koin-compose" }
+koin-compose-navigation = { module = "io.insert-koin:koin-androidx-compose-navigation", version.ref = "koin-compose" }
+koin-workmanager = { module = "io.insert-koin:koin-androidx-workmanager", version.ref = "koin" }
+koin-test = { module = "io.insert-koin:koin-test", version.ref = "koin" }
+koin-test-junit = { module = "io.insert-koin:koin-test-junit4", version.ref = "koin" }
+
+# Firebase
+firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebase-bom" }
+firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics" }
+firebase-analytics = { module = "com.google.firebase:firebase-analytics" }
+firebase-performance = { module = "com.google.firebase:firebase-perf" }
+firebase-messaging = { module = "com.google.firebase:firebase-messaging" }
+androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
+material = { group = "com.google.android.material", name = "material", version.ref = "material" }
+
+
+[bundles]
+compose = [
+ "compose-ui",
+ "compose-ui-graphics",
+ "compose-ui-preview",
+ "compose-material3",
+ "activity-compose",
+ "compose-ui-util"
+]
+
+compose-debug = [
+ "compose-ui-tooling",
+ "compose-ui-manifest"
+]
+
+lifecycle = [
+ "lyfecycle-compose",
+ "lyfecycle-runtime",
+ "lyfecycle-runtime-compose"
+]
+
+test-common = [
+ "test-junit",
+ "test-kotlin",
+ "test-kotlin-coroutines",
+ "test-mockk",
+ "test-coroutines-flow"
+]
+
+test-android = [
+ "test-android-compose",
+ "test-android-runner"
+]
+
+koin = [
+ "koin-core",
+ "koin-android",
+ "koin-compose",
+ "koin-compose-navigation"
+]
+
+test-koin = [
+ "koin-test",
+ "koin-test-junit"
+]
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..e708b1c
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..c12cd15
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Feb 24 16:13:44 EET 2023
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..4f906e0
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..107acd3
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/maestro/people-profile-flow.yaml b/maestro/people-profile-flow.yaml
new file mode 100644
index 0000000..838873d
--- /dev/null
+++ b/maestro/people-profile-flow.yaml
@@ -0,0 +1,28 @@
+appId: com.mobiledevpro.apptemplate.compose
+---
+- launchApp:
+ appId: "com.mobiledevpro.apptemplate.compose"
+ clearState: true
+- waitForAnimationToEnd:
+ timeout: 1500
+#- extendedWaitUntil:
+# visible: "Next"
+# timeout: 1500
+#- tapOn: "Next"
+#- tapOn: "Next"
+#- tapOn: "Next"
+- extendedWaitUntil:
+ visible: "People"
+ timeout: 5000
+- tapOn: "People"
+- extendedWaitUntil:
+ visible: "Manilla Andrews"
+ timeout: 5000
+- tapOn: "Manilla Andrews"
+- "waitForAnimationToEnd"
+- "back"
+
+# How to run this flow:
+# 1. Install Maestro: Terminal -> curl -Ls "https://get.maestro.mobile.dev" | bash
+# 2. Install the app on emulator (doesn't work with physical device)
+# 3. Run the flow: Terminal -> maestro test -c maestro/people-profile-flow.yaml
\ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000..a00fe69
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,34 @@
+pluginManagement {
+ includeBuild("build-logic")
+ repositories {
+ gradlePluginPortal()
+ google()
+ mavenCentral()
+ }
+}
+
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+rootProject.name = "App-Template-Compose"
+
+include(":app")
+include(":core:ui")
+include(":core:navigation")
+include(":core:domain")
+include(":core:coroutines")
+
+include(":feature:home")
+include(":feature:onboarding")
+include(":feature:subscription")
+include(":feature:chat_list")
+include(":feature:people_list")
+include(":feature:user_profile")
+include(":feature:people_profile")
+include(":feature:people")
+include(":core:di")
+include(":core:util")