├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── themes.xml │ │ │ │ └── colors.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── xml │ │ │ │ ├── backup_rules.xml │ │ │ │ └── data_extraction_rules.xml │ │ │ ├── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ └── drawable │ │ │ │ └── ic_launcher_background.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── iamkamrul │ │ │ │ └── modularization │ │ │ │ ├── JetpackApplication.kt │ │ │ │ ├── di │ │ │ │ └── RepositoryModule.kt │ │ │ │ ├── navigation │ │ │ │ └── AppNavigation.kt │ │ │ │ └── MainActivity.kt │ │ └── AndroidManifest.xml │ ├── release │ │ └── java │ │ │ └── com │ │ │ └── iamkamrul │ │ │ └── modularization │ │ │ └── BaseUrlModule.kt │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── iamkamrul │ │ │ └── modularization │ │ │ └── ExampleUnitTest.kt │ ├── debug │ │ └── java │ │ │ └── com │ │ │ └── iamkamrul │ │ │ └── modularization │ │ │ └── BaseUrlModule.kt │ └── androidTest │ │ └── java │ │ └── com │ │ └── iamkamrul │ │ └── modularization │ │ └── ExampleInstrumentedTest.kt ├── proguard-rules.pro └── build.gradle.kts ├── common ├── .gitignore ├── consumer-rules.pro ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── iamkamrul │ │ │ └── common │ │ │ └── utils │ │ │ └── NavRoute.kt │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── iamkamrul │ │ │ └── common │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── com │ │ └── iamkamrul │ │ └── common │ │ └── ExampleInstrumentedTest.kt ├── build.gradle.kts └── proguard-rules.pro ├── core ├── data │ ├── .gitignore │ ├── consumer-rules.pro │ ├── src │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── iamkamrul │ │ │ │ └── data │ │ │ │ ├── apiservice │ │ │ │ └── ApiService.kt │ │ │ │ ├── module │ │ │ │ └── ApiServiceModule.kt │ │ │ │ ├── utils │ │ │ │ ├── BaseMapper.kt │ │ │ │ └── NetworkBoundResource.kt │ │ │ │ ├── mapper │ │ │ │ ├── ProfileMapper.kt │ │ │ │ └── RepoListItemMapper.kt │ │ │ │ └── repoimpl │ │ │ │ └── GithubRepoImpl.kt │ │ └── test │ │ │ └── java │ │ │ └── com │ │ │ └── iamkamrul │ │ │ └── data │ │ │ └── ExampleUnitTest.kt │ ├── build.gradle.kts │ └── proguard-rules.pro ├── di │ ├── .gitignore │ ├── consumer-rules.pro │ ├── src │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── iamkamrul │ │ │ │ └── di │ │ │ │ ├── qualifier │ │ │ │ └── AppBaseUrl.kt │ │ │ │ └── module │ │ │ │ ├── RetrofitModule.kt │ │ │ │ └── OkHttpModule.kt │ │ └── test │ │ │ └── java │ │ │ └── com │ │ │ └── iamkamrul │ │ │ └── di │ │ │ └── ExampleUnitTest.kt │ ├── build.gradle.kts │ └── proguard-rules.pro ├── domain │ ├── .gitignore │ ├── consumer-rules.pro │ ├── src │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── iamkamrul │ │ │ │ └── domain │ │ │ │ ├── utils │ │ │ │ ├── Result.kt │ │ │ │ └── BaseUseCase.kt │ │ │ │ ├── repository │ │ │ │ └── GithubRepository.kt │ │ │ │ └── usecase │ │ │ │ ├── ProfileUseCase.kt │ │ │ │ └── RepoListUseCase.kt │ │ └── test │ │ │ └── java │ │ │ └── com │ │ │ └── iamkamrul │ │ │ └── domain │ │ │ └── ExampleUnitTest.kt │ ├── build.gradle.kts │ └── proguard-rules.pro ├── ui │ ├── .gitignore │ ├── consumer-rules.pro │ ├── src │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── iamkamrul │ │ │ │ └── ui │ │ │ │ └── component │ │ │ │ └── ErrorMessageCompose.kt │ │ ├── test │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── iamkamrul │ │ │ │ └── ui │ │ │ │ └── ExampleUnitTest.kt │ │ └── androidTest │ │ │ └── java │ │ │ └── com │ │ │ └── iamkamrul │ │ │ └── ui │ │ │ └── ExampleInstrumentedTest.kt │ ├── proguard-rules.pro │ └── build.gradle.kts └── designsystem │ ├── .gitignore │ ├── consumer-rules.pro │ ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── res │ │ │ └── font │ │ │ │ ├── roboto_bold.ttf │ │ │ │ ├── roboto_black.ttf │ │ │ │ ├── roboto_italic.ttf │ │ │ │ ├── roboto_light.ttf │ │ │ │ ├── roboto_medium.ttf │ │ │ │ ├── roboto_regular.ttf │ │ │ │ ├── roboto_bold_italic.ttf │ │ │ │ └── roboto_extra_light.ttf │ │ └── java │ │ │ └── com │ │ │ └── iamkamrul │ │ │ └── designsystem │ │ │ ├── theme │ │ │ ├── Shape.kt │ │ │ ├── AppColorScheme.kt │ │ │ ├── Color.kt │ │ │ ├── Type.kt │ │ │ └── Theme.kt │ │ │ └── component │ │ │ └── ScaffoldTopAppbar.kt │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── iamkamrul │ │ │ └── designsystem │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── com │ │ └── iamkamrul │ │ └── designsystem │ │ └── ExampleInstrumentedTest.kt │ ├── proguard-rules.pro │ └── build.gradle.kts ├── features ├── profile │ ├── .gitignore │ ├── consumer-rules.pro │ ├── src │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── iamkamrul │ │ │ │ └── profile │ │ │ │ ├── ProfileNavigation.kt │ │ │ │ ├── ProfileViewModel.kt │ │ │ │ └── ProfileScreen.kt │ │ └── androidTest │ │ │ └── java │ │ │ └── com │ │ │ └── iamkamrul │ │ │ └── profile │ │ │ └── ExampleInstrumentedTest.kt │ ├── build.gradle.kts │ └── proguard-rules.pro └── repolist │ ├── .gitignore │ ├── consumer-rules.pro │ ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── iamkamrul │ │ │ └── repolist │ │ │ ├── RepoListNavigation.kt │ │ │ ├── RepoListViewModel.kt │ │ │ └── RepoListScreen.kt │ └── androidTest │ │ └── java │ │ └── com │ │ └── iamkamrul │ │ └── repolist │ │ └── ExampleInstrumentedTest.kt │ ├── build.gradle.kts │ └── proguard-rules.pro ├── model ├── entity │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── iamkamrul │ │ └── entity │ │ ├── ProfileEntity.kt │ │ └── RepoItemEntity.kt └── apiresponse │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ └── main │ └── java │ └── com │ └── iamkamrul │ └── apiresponse │ ├── ProfileApiResponse.kt │ └── RepoItemApiResponse.kt ├── .idea ├── .name ├── vcs.xml ├── kotlinc.xml ├── misc.xml └── inspectionProfiles │ └── Project_Default.xml ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libs.versions.toml ├── README.md ├── settings.gradle.kts ├── gradle.properties ├── .gitignore ├── gradlew.bat └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /common/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /common/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/data/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/data/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/di/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/di/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/domain/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/ui/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/ui/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/domain/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /features/profile/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /model/entity/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/designsystem/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/designsystem/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /features/profile/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /features/repolist/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /features/repolist/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /model/apiresponse/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | JetPackComposeModularization -------------------------------------------------------------------------------- /core/domain/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | JetPackComposeModularization 3 | -------------------------------------------------------------------------------- /common/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamrul3288/JetPackCompose-Modularization-MVVM-Clean-Architecture/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /core/data/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /core/di/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /core/ui/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /features/profile/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /core/designsystem/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /features/repolist/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /model/entity/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed 2 | plugins { 3 | alias(libs.plugins.iamkamrul.jvm.library) 4 | } 5 | true -------------------------------------------------------------------------------- /model/apiresponse/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed 2 | plugins { 3 | alias(libs.plugins.iamkamrul.jvm.library) 4 | } 5 | true -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamrul3288/JetPackCompose-Modularization-MVVM-Clean-Architecture/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamrul3288/JetPackCompose-Modularization-MVVM-Clean-Architecture/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamrul3288/JetPackCompose-Modularization-MVVM-Clean-Architecture/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamrul3288/JetPackCompose-Modularization-MVVM-Clean-Architecture/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamrul3288/JetPackCompose-Modularization-MVVM-Clean-Architecture/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamrul3288/JetPackCompose-Modularization-MVVM-Clean-Architecture/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamrul3288/JetPackCompose-Modularization-MVVM-Clean-Architecture/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /core/designsystem/src/main/res/font/roboto_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamrul3288/JetPackCompose-Modularization-MVVM-Clean-Architecture/HEAD/core/designsystem/src/main/res/font/roboto_bold.ttf -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamrul3288/JetPackCompose-Modularization-MVVM-Clean-Architecture/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamrul3288/JetPackCompose-Modularization-MVVM-Clean-Architecture/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamrul3288/JetPackCompose-Modularization-MVVM-Clean-Architecture/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /core/designsystem/src/main/res/font/roboto_black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamrul3288/JetPackCompose-Modularization-MVVM-Clean-Architecture/HEAD/core/designsystem/src/main/res/font/roboto_black.ttf -------------------------------------------------------------------------------- /core/designsystem/src/main/res/font/roboto_italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamrul3288/JetPackCompose-Modularization-MVVM-Clean-Architecture/HEAD/core/designsystem/src/main/res/font/roboto_italic.ttf -------------------------------------------------------------------------------- /core/designsystem/src/main/res/font/roboto_light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamrul3288/JetPackCompose-Modularization-MVVM-Clean-Architecture/HEAD/core/designsystem/src/main/res/font/roboto_light.ttf -------------------------------------------------------------------------------- /core/designsystem/src/main/res/font/roboto_medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamrul3288/JetPackCompose-Modularization-MVVM-Clean-Architecture/HEAD/core/designsystem/src/main/res/font/roboto_medium.ttf -------------------------------------------------------------------------------- /core/designsystem/src/main/res/font/roboto_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamrul3288/JetPackCompose-Modularization-MVVM-Clean-Architecture/HEAD/core/designsystem/src/main/res/font/roboto_regular.ttf -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /core/designsystem/src/main/res/font/roboto_bold_italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamrul3288/JetPackCompose-Modularization-MVVM-Clean-Architecture/HEAD/core/designsystem/src/main/res/font/roboto_bold_italic.ttf -------------------------------------------------------------------------------- /core/designsystem/src/main/res/font/roboto_extra_light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kamrul3288/JetPackCompose-Modularization-MVVM-Clean-Architecture/HEAD/core/designsystem/src/main/res/font/roboto_extra_light.ttf -------------------------------------------------------------------------------- /common/src/main/java/com/iamkamrul/common/utils/NavRoute.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.common.utils 2 | 3 | object NavRoute { 4 | const val repoListScreen = "repoListScreen" 5 | const val profileScreen = "profileScreen" 6 | } -------------------------------------------------------------------------------- /core/di/src/main/java/com/iamkamrul/di/qualifier/AppBaseUrl.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.di.qualifier 2 | 3 | import javax.inject.Qualifier 4 | 5 | @Qualifier 6 | @Retention(AnnotationRetention.RUNTIME) 7 | annotation class AppBaseUrl -------------------------------------------------------------------------------- /common/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed 2 | plugins { 3 | alias(libs.plugins.iamkamrul.android.library) 4 | } 5 | android { 6 | namespace = "com.iamkamrul.common" 7 | } 8 | dependencies { 9 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Sep 23 16:27:12 BDT 2024 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /core/domain/src/main/java/com/iamkamrul/domain/utils/Result.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.domain.utils 2 | 3 | 4 | sealed interface Result { 5 | data object Loading:Result 6 | data class Success(val data: T) : Result 7 | data class Error(val message: String,val code:Int) : Result 8 | } 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JetPackCompose-Modularization-MVVM-Clean-Architecture 2 | A clean architecture approach (multi-module) using Jetpack compose, Kotlin, Navigation, MVVM, Hilt, Kotlin Coroutines, Flow and Retrofit. 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /features/profile/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed 2 | plugins { 3 | alias(libs.plugins.iamkamrul.android.feature.compose) 4 | } 5 | android { 6 | namespace = "com.iamkamrul.profile" 7 | } 8 | 9 | dependencies{ 10 | implementation(libs.image.coil.compose) 11 | } -------------------------------------------------------------------------------- /features/repolist/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed 2 | plugins { 3 | alias(libs.plugins.iamkamrul.android.feature.compose) 4 | } 5 | 6 | android { 7 | namespace = "com.iamkamrul.repolist" 8 | } 9 | dependencies{ 10 | implementation(libs.image.coil.compose) 11 | } -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /core/data/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @Suppress("DSL_SCOPE_VIOLATION") 2 | plugins { 3 | alias(libs.plugins.iamkamrul.android.library) 4 | alias(libs.plugins.iamkamrul.android.hilt) 5 | } 6 | android { 7 | namespace = "com.iamkamrul.data" 8 | 9 | } 10 | 11 | dependencies { 12 | implementation(projects.core.domain) 13 | implementation(projects.core.di) 14 | api(projects.model.apiresponse) 15 | } -------------------------------------------------------------------------------- /core/domain/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @Suppress("DSL_SCOPE_VIOLATION") 2 | plugins { 3 | alias(libs.plugins.iamkamrul.android.library) 4 | alias(libs.plugins.iamkamrul.android.hilt) 5 | } 6 | 7 | android { 8 | namespace = "com.iamkamrul.domain" 9 | } 10 | 11 | dependencies { 12 | api(projects.model.entity) 13 | implementation(libs.androidx.corektx) 14 | implementation(libs.kotlinx.coroutines.android) 15 | } -------------------------------------------------------------------------------- /core/domain/src/main/java/com/iamkamrul/domain/utils/BaseUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.domain.utils 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | 5 | interface UseCase 6 | 7 | interface ApiUseCaseParams : UseCase { 8 | suspend fun execute(params: Params): Flow> 9 | } 10 | 11 | interface ApiUseCaseNonParams : UseCase { 12 | suspend fun execute(): Flow> 13 | } 14 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /core/designsystem/src/main/java/com/iamkamrul/designsystem/theme/Shape.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.designsystem.theme 2 | 3 | import androidx.compose.foundation.shape.RoundedCornerShape 4 | import androidx.compose.material3.Shapes 5 | import androidx.compose.ui.unit.dp 6 | 7 | val Shapes = Shapes( 8 | small = RoundedCornerShape(4.dp), 9 | medium = RoundedCornerShape(4.dp), 10 | large = RoundedCornerShape(0.dp) 11 | ) -------------------------------------------------------------------------------- /core/di/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed 2 | plugins { 3 | alias(libs.plugins.iamkamrul.android.library) 4 | alias(libs.plugins.iamkamrul.android.hilt) 5 | alias(libs.plugins.iamkamrul.android.retrofit) 6 | } 7 | android { 8 | namespace = "com.iamkamrul.di" 9 | } 10 | dependencies { 11 | api(libs.log.timber) 12 | api(libs.bundles.network) 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /core/di/src/test/java/com/iamkamrul/di/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.di 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /core/ui/src/test/java/com/iamkamrul/ui/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.ui 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /common/src/test/java/com/iamkamrul/common/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.common 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /core/data/src/test/java/com/iamkamrul/data/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.data 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/iamkamrul/modularization/JetpackApplication.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.modularization 2 | 3 | import android.app.Application 4 | import dagger.hilt.android.HiltAndroidApp 5 | import timber.log.Timber 6 | 7 | @HiltAndroidApp 8 | class JetpackApplication : Application(){ 9 | override fun onCreate() { 10 | super.onCreate() 11 | if (BuildConfig.DEBUG){ 12 | Timber.plant(Timber.DebugTree()) 13 | } 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /app/src/release/java/com/iamkamrul/modularization/BaseUrlModule.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.modularization 2 | 3 | import com.iamkamrul.di.qualifier.AppBaseUrl 4 | import dagger.Module 5 | import dagger.Provides 6 | import dagger.hilt.InstallIn 7 | import dagger.hilt.components.SingletonComponent 8 | 9 | @Module 10 | @InstallIn(SingletonComponent::class) 11 | class BaseUrlModule{ 12 | @Provides 13 | @AppBaseUrl 14 | fun provideBaseUrl():String = "" 15 | } 16 | 17 | -------------------------------------------------------------------------------- /core/domain/src/test/java/com/iamkamrul/domain/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.domain 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/test/java/com/iamkamrul/modularization/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.modularization 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /core/designsystem/src/test/java/com/iamkamrul/designsystem/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.designsystem 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/debug/java/com/iamkamrul/modularization/BaseUrlModule.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.modularization 2 | 3 | import com.iamkamrul.di.qualifier.AppBaseUrl 4 | import dagger.Module 5 | import dagger.Provides 6 | import dagger.hilt.InstallIn 7 | import dagger.hilt.components.SingletonComponent 8 | 9 | @Module 10 | @InstallIn(SingletonComponent::class) 11 | class BaseUrlModule{ 12 | @Provides 13 | @AppBaseUrl 14 | fun provideBaseUrl():String = "https://api.github.com/" 15 | } 16 | 17 | -------------------------------------------------------------------------------- /features/repolist/src/main/java/com/iamkamrul/repolist/RepoListNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.repolist 2 | 3 | import androidx.navigation.NavGraphBuilder 4 | import androidx.navigation.compose.composable 5 | 6 | const val repoListScreenRoute = "repoListScreenRoute" 7 | 8 | fun NavGraphBuilder.repoListScreen( 9 | onRepoItemClick:()->Unit 10 | ){ 11 | composable(route = repoListScreenRoute){ 12 | RepoListRoute( 13 | onRepoItemClick = onRepoItemClick 14 | ) 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/iamkamrul/modularization/di/RepositoryModule.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.modularization.di 2 | 3 | import com.iamkamrul.data.repoimpl.GithubRepoImpl 4 | import com.iamkamrul.domain.repository.GithubRepository 5 | import dagger.Binds 6 | import dagger.Module 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.components.SingletonComponent 9 | 10 | @Module 11 | @InstallIn(SingletonComponent::class) 12 | interface RepositoryModule{ 13 | 14 | @Binds 15 | fun bindGithubRepository(githubRepoImpl: GithubRepoImpl): GithubRepository 16 | 17 | } -------------------------------------------------------------------------------- /core/domain/src/main/java/com/iamkamrul/domain/repository/GithubRepository.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.domain.repository 2 | 3 | import com.iamkamrul.domain.usecase.ProfileUseCase 4 | import com.iamkamrul.domain.usecase.RepoListUseCase 5 | import com.iamkamrul.entity.ProfileEntity 6 | import com.iamkamrul.entity.RepoItemEntity 7 | import kotlinx.coroutines.flow.Flow 8 | import com.iamkamrul.domain.utils.Result 9 | 10 | 11 | interface GithubRepository { 12 | suspend fun fetchRepoList(params: RepoListUseCase.Params): Flow>> 13 | suspend fun fetchProfile(params: ProfileUseCase.Params):Flow> 14 | } -------------------------------------------------------------------------------- /features/profile/src/main/java/com/iamkamrul/profile/ProfileNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.profile 2 | 3 | import androidx.navigation.NavController 4 | import androidx.navigation.NavGraphBuilder 5 | import androidx.navigation.compose.composable 6 | 7 | const val profileScreenRoute = "profileScreenRoute" 8 | 9 | fun NavController.navigateToProfileScreen(){ 10 | navigate(profileScreenRoute) 11 | } 12 | 13 | fun NavGraphBuilder.profileScreen( 14 | onBackBtnClick:()->Unit 15 | ){ 16 | composable(route = profileScreenRoute){ 17 | ProfileScreenRoute( 18 | onBackBtnClick = onBackBtnClick 19 | ) 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /model/entity/src/main/java/com/iamkamrul/entity/ProfileEntity.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.entity 2 | 3 | data class ProfileEntity( 4 | val userAvatar: String, 5 | val userFullName: String, 6 | val userName: String, 7 | val about: String, 8 | val repoCount: Int, 9 | val followerCount: Int, 10 | val followingCount: Int, 11 | ) { 12 | constructor() : this( 13 | userAvatar = "https://www.google.com", 14 | userFullName = "Kamrul Hasan", 15 | userName = "kamrul3288", 16 | about = "Android Developer", 17 | repoCount = 24, 18 | followerCount = 24, 19 | followingCount = 24 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /core/data/src/main/java/com/iamkamrul/data/apiservice/ApiService.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.data.apiservice 2 | 3 | import com.iamkamrul.apiresponse.ProfileApiResponse 4 | import com.iamkamrul.apiresponse.RepoItemApiResponse 5 | import retrofit2.Response 6 | import retrofit2.http.GET 7 | import retrofit2.http.Path 8 | 9 | interface ApiService { 10 | @GET("/users/{username}/repos") 11 | suspend fun fetchRepoList( 12 | @Path("username")username:String 13 | ): Response> 14 | 15 | @GET("/users/{username}") 16 | suspend fun fetchProfile( 17 | @Path("username")username:String 18 | ):Response 19 | } -------------------------------------------------------------------------------- /core/data/src/main/java/com/iamkamrul/data/module/ApiServiceModule.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.data.module 2 | 3 | 4 | import com.iamkamrul.data.apiservice.ApiService 5 | import com.iamkamrul.di.qualifier.AppBaseUrl 6 | import dagger.Module 7 | import dagger.Provides 8 | import dagger.hilt.InstallIn 9 | import dagger.hilt.components.SingletonComponent 10 | import retrofit2.Retrofit 11 | import javax.inject.Singleton 12 | 13 | @Module 14 | @InstallIn(SingletonComponent::class) 15 | object ApiServiceModule { 16 | @Provides 17 | @Singleton 18 | fun provideApiService(@AppBaseUrl retrofit: Retrofit): ApiService { 19 | return retrofit.create(ApiService::class.java) 20 | } 21 | } -------------------------------------------------------------------------------- /core/data/src/main/java/com/iamkamrul/data/utils/BaseMapper.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.data.utils 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import kotlinx.coroutines.flow.map 5 | import com.iamkamrul.domain.utils.Result 6 | 7 | interface Mapper{ 8 | fun mapFromApiResponse(type:R):E 9 | } 10 | 11 | fun mapFromApiResponse(result: Flow>, mapper: Mapper): Flow> { 12 | return result.map { 13 | when(it){ 14 | is Result.Success-> Result.Success(mapper.mapFromApiResponse(it.data)) 15 | is Result.Error->Result.Error(it.message,it.code) 16 | is Result.Loading -> Result.Loading 17 | } 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /core/domain/src/main/java/com/iamkamrul/domain/usecase/ProfileUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.domain.usecase 2 | 3 | import com.iamkamrul.domain.repository.GithubRepository 4 | import com.iamkamrul.domain.utils.ApiUseCaseParams 5 | import com.iamkamrul.domain.utils.Result 6 | import com.iamkamrul.entity.ProfileEntity 7 | import kotlinx.coroutines.flow.Flow 8 | import javax.inject.Inject 9 | 10 | class ProfileUseCase @Inject constructor( 11 | private val repository: GithubRepository 12 | ):ApiUseCaseParams{ 13 | data class Params(val userName:String) 14 | override suspend fun execute(params: Params): Flow> { 15 | return repository.fetchProfile(params) 16 | } 17 | } -------------------------------------------------------------------------------- /core/domain/src/main/java/com/iamkamrul/domain/usecase/RepoListUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.domain.usecase 2 | 3 | import com.iamkamrul.domain.repository.GithubRepository 4 | import com.iamkamrul.domain.utils.ApiUseCaseParams 5 | import com.iamkamrul.domain.utils.Result 6 | import com.iamkamrul.entity.RepoItemEntity 7 | import kotlinx.coroutines.flow.Flow 8 | import javax.inject.Inject 9 | 10 | class RepoListUseCase @Inject constructor( 11 | private val repository: GithubRepository 12 | ):ApiUseCaseParams>{ 13 | override suspend fun execute(params: Params): Flow>> { 14 | return repository.fetchRepoList(params) 15 | } 16 | data class Params(val userName:String) 17 | } -------------------------------------------------------------------------------- /model/entity/src/main/java/com/iamkamrul/entity/RepoItemEntity.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.entity 2 | 3 | data class RepoItemEntity( 4 | val userAvatarUrl:String, 5 | val userName:String, 6 | val repoName:String, 7 | val repoFullName:String, 8 | val repoDescription:String, 9 | val language:String, 10 | val forksCount:Int, 11 | val stargazers_count:Int, 12 | ){ 13 | constructor():this( 14 | userAvatarUrl = "https://www.google.com/", 15 | userName = "Kamrul Hasan", 16 | repoName = "Kamrul3288", 17 | repoFullName = "Kamrul3288/JetpackCompose", 18 | repoDescription = "It's an awsome repository", 19 | language = "Kotlin", 20 | forksCount = 100, 21 | stargazers_count = 786 22 | ) 23 | } -------------------------------------------------------------------------------- /core/ui/src/androidTest/java/com/iamkamrul/ui/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.ui 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.iamkamrul.ui.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /common/src/androidTest/java/com/iamkamrul/common/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.common 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.iamkamrul.common.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /common/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /core/di/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /core/ui/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /features/profile/src/androidTest/java/com/iamkamrul/profile/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.profile 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.iamkamrul.profile.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/iamkamrul/modularization/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.modularization 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.iamkamrul.modularization", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /core/data/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /core/domain/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /features/repolist/src/androidTest/java/com/iamkamrul/repolist/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.repolist 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.iamkamrul.repolist.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /core/designsystem/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /features/profile/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /features/repolist/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /core/designsystem/src/androidTest/java/com/iamkamrul/designsystem/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.designsystem 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.iamkamrul.designsystem.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /core/designsystem/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed 2 | plugins { 3 | alias(libs.plugins.iamkamrul.android.library) 4 | alias(libs.plugins.iamkamrul.android.library.compose) 5 | } 6 | 7 | android { 8 | namespace = "com.iamkamrul.designsystem" 9 | } 10 | 11 | dependencies { 12 | api(libs.androidx.corektx) 13 | api(libs.androidx.compose.foundation) 14 | api(libs.androidx.compose.material.iconsExtended) 15 | api(libs.androidx.compose.material3) 16 | api(libs.androidx.compose.ui.tooling.preview) 17 | api(libs.androidx.compose.ui) 18 | api(libs.androidx.compose.ui.graphics) 19 | debugApi(libs.androidx.compose.ui.tooling) 20 | 21 | testImplementation(libs.test.junit) 22 | androidTestImplementation(libs.test.extjunit) 23 | androidTestImplementation(libs.test.espresso) 24 | } -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | includeBuild("build-logic") 3 | repositories { 4 | google() 5 | mavenCentral() 6 | gradlePluginPortal() 7 | } 8 | } 9 | dependencyResolutionManagement { 10 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 11 | repositories { 12 | google() 13 | mavenCentral() 14 | maven("https://www.jitpack.io") 15 | } 16 | } 17 | 18 | rootProject.name = "JetPackComposeModularization" 19 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 20 | include (":app") 21 | include(":core:di") 22 | include(":core:domain") 23 | include(":core:data") 24 | include(":model:entity") 25 | include(":model:apiresponse") 26 | 27 | include(":common") 28 | include(":core:designsystem") 29 | include(":core:ui") 30 | 31 | include(":features:repolist") 32 | include(":features:profile") 33 | 34 | -------------------------------------------------------------------------------- /core/ui/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed 2 | plugins { 3 | alias(libs.plugins.iamkamrul.android.library) 4 | alias(libs.plugins.iamkamrul.android.library.compose) 5 | } 6 | 7 | android { 8 | namespace = "com.iamkamrul.ui" 9 | } 10 | 11 | dependencies { 12 | implementation(projects.core.designsystem) 13 | 14 | 15 | api(libs.androidx.corektx) 16 | api(libs.androidx.compose.foundation) 17 | api(libs.androidx.compose.material.iconsExtended) 18 | api(libs.androidx.compose.material3) 19 | api(libs.androidx.compose.ui.tooling.preview) 20 | api(libs.androidx.compose.ui) 21 | api(libs.androidx.compose.ui.graphics) 22 | debugApi(libs.androidx.compose.ui.tooling) 23 | 24 | testImplementation(libs.test.junit) 25 | androidTestImplementation(libs.test.extjunit) 26 | androidTestImplementation(libs.test.espresso) 27 | } -------------------------------------------------------------------------------- /core/data/src/main/java/com/iamkamrul/data/mapper/ProfileMapper.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.data.mapper 2 | 3 | import com.iamkamrul.apiresponse.ProfileApiResponse 4 | import com.iamkamrul.data.utils.Mapper 5 | import com.iamkamrul.entity.ProfileEntity 6 | import javax.inject.Inject 7 | 8 | class ProfileMapper @Inject constructor():Mapper { 9 | override fun mapFromApiResponse(type: ProfileApiResponse): ProfileEntity { 10 | return ProfileEntity( 11 | userAvatar = type.avatar_url ?:"https://www.pullrequest.com/blog/github-code-review-service/images/github-logo_hub2899c31b6ca7aed8d6a218f0e752fe4_46649_1200x1200_fill_box_center_2.png", 12 | userFullName = type.name ?: "NAME_NOT_FOUND", 13 | userName = type.login ?: "", 14 | about = type.bio ?: "BIO_NOT_FOUND", 15 | repoCount = type.public_repos ?: 0, 16 | followerCount = type.followers ?: 0, 17 | followingCount = type.following ?: 0 18 | ) 19 | } 20 | } -------------------------------------------------------------------------------- /model/apiresponse/src/main/java/com/iamkamrul/apiresponse/ProfileApiResponse.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.apiresponse 2 | 3 | data class ProfileApiResponse( 4 | val avatar_url: String?, 5 | val bio: String?, 6 | val blog: String?, 7 | val company: String?, 8 | val created_at: String?, 9 | val email: String?, 10 | val events_url: String?, 11 | val followers: Int?, 12 | val followers_url: String?, 13 | val following: Int?, 14 | val following_url: String?, 15 | val gists_url: String?, 16 | val gravatar_id: String?, 17 | val hireable: Boolean?, 18 | val html_url: String?, 19 | val id: Int?, 20 | val location: String?, 21 | val login: String?, 22 | val name: String?, 23 | val node_id: String?, 24 | val organizations_url: String?, 25 | val public_gists: Int?, 26 | val public_repos: Int?, 27 | val received_events_url: String?, 28 | val repos_url: String?, 29 | val site_admin: Boolean?, 30 | val starred_url: String?, 31 | val subscriptions_url: String?, 32 | val twitter_username: String?, 33 | val type: String?, 34 | val updated_at: String?, 35 | val url: String? 36 | ) -------------------------------------------------------------------------------- /core/designsystem/src/main/java/com/iamkamrul/designsystem/theme/AppColorScheme.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.designsystem.theme 2 | 3 | import androidx.compose.material3.MaterialTheme 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.runtime.ReadOnlyComposable 6 | import androidx.compose.runtime.compositionLocalOf 7 | import androidx.compose.ui.graphics.Color 8 | 9 | 10 | data class AppColor( 11 | val white: Color = Color.Unspecified, 12 | val black: Color = Color.Unspecified, 13 | val topAppBar: Color = Color.Unspecified, 14 | val secondaryBackground: Color = Color.Unspecified 15 | ) 16 | internal val LocalAppColor = compositionLocalOf { AppColor() } 17 | 18 | val MaterialTheme.color: AppColor 19 | @Composable 20 | @ReadOnlyComposable 21 | get() = LocalAppColor.current 22 | 23 | 24 | 25 | internal val LocalLightColorScheme = AppColor( 26 | white = White, 27 | black = Black, 28 | topAppBar = White, 29 | secondaryBackground = Black95, 30 | ) 31 | 32 | internal val LocalDarkColorScheme = AppColor( 33 | white = Black, 34 | black = White, 35 | topAppBar = Black10, 36 | secondaryBackground = Black 37 | ) 38 | 39 | -------------------------------------------------------------------------------- /core/data/src/main/java/com/iamkamrul/data/mapper/RepoListItemMapper.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.data.mapper 2 | 3 | import com.iamkamrul.apiresponse.RepoItemApiResponse 4 | import com.iamkamrul.data.utils.Mapper 5 | import com.iamkamrul.entity.RepoItemEntity 6 | import javax.inject.Inject 7 | 8 | class RepoListItemMapper @Inject constructor() : Mapper,List>{ 9 | override fun mapFromApiResponse(type: List): List { 10 | return type.map { 11 | RepoItemEntity( 12 | userAvatarUrl = it.owner?.avatar_url ?: "https://www.pullrequest.com/blog/github-code-review-service/images/github-logo_hub2899c31b6ca7aed8d6a218f0e752fe4_46649_1200x1200_fill_box_center_2.png", 13 | userName = it.owner?.login ?: "NO_USER_NAME_FOUND", 14 | repoName = it.name ?: "EMPTY_REPO_NAME", 15 | repoFullName = it.full_name ?: "EMPTY_REPO_NAME", 16 | repoDescription = it.description ?: "No description found", 17 | language = it.language ?: "Not Found", 18 | forksCount = it.forks_count ?: 0, 19 | stargazers_count = it.stargazers_count ?: 0 20 | ) 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/iamkamrul/modularization/navigation/AppNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.modularization.navigation 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.Modifier 5 | import androidx.lifecycle.Lifecycle 6 | import androidx.navigation.NavController 7 | import androidx.navigation.NavHostController 8 | import androidx.navigation.compose.NavHost 9 | import androidx.navigation.compose.rememberNavController 10 | import com.iamkamrul.profile.navigateToProfileScreen 11 | import com.iamkamrul.profile.profileScreen 12 | import com.iamkamrul.repolist.repoListScreen 13 | import com.iamkamrul.repolist.repoListScreenRoute 14 | 15 | @Composable 16 | fun AppNavigation( 17 | modifier: Modifier = Modifier, 18 | navController: NavHostController = rememberNavController(), 19 | startDestination:String = repoListScreenRoute 20 | ){ 21 | NavHost( 22 | navController = navController, 23 | startDestination = startDestination, 24 | modifier = modifier 25 | ) { 26 | repoListScreen(onRepoItemClick = navController::navigateToProfileScreen) 27 | profileScreen(onBackBtnClick = navController::popBackStackOrIgnore) 28 | } 29 | } 30 | 31 | fun NavController.popBackStackOrIgnore() { 32 | if (currentBackStackEntry?.lifecycle?.currentState == Lifecycle.State.RESUMED) { 33 | popBackStack() 34 | } 35 | } -------------------------------------------------------------------------------- /core/di/src/main/java/com/iamkamrul/di/module/RetrofitModule.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.di.module 2 | 3 | import com.google.gson.Gson 4 | import com.google.gson.GsonBuilder 5 | import com.iamkamrul.di.qualifier.AppBaseUrl 6 | import dagger.Module 7 | import dagger.Provides 8 | import dagger.hilt.InstallIn 9 | import dagger.hilt.components.SingletonComponent 10 | import okhttp3.OkHttpClient 11 | import retrofit2.Retrofit 12 | import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory 13 | import retrofit2.converter.gson.GsonConverterFactory 14 | 15 | 16 | @Module 17 | @InstallIn(SingletonComponent::class) 18 | object RetrofitModule { 19 | 20 | @Provides 21 | @AppBaseUrl 22 | fun provideRetrofit( 23 | @AppBaseUrl baseUrl: String, 24 | okHttpClient: OkHttpClient, 25 | factory: GsonConverterFactory 26 | ): Retrofit { 27 | return Retrofit.Builder() 28 | .baseUrl(baseUrl) 29 | .client(okHttpClient) 30 | .addConverterFactory(factory) 31 | .addCallAdapterFactory(RxJava3CallAdapterFactory.create()) 32 | .build() 33 | } 34 | 35 | @Provides 36 | fun provideConverterFactory(gson: Gson): GsonConverterFactory { 37 | return GsonConverterFactory.create(gson) 38 | } 39 | 40 | @Provides 41 | fun provideGson(): Gson { 42 | val gsonBuilder = GsonBuilder() 43 | return gsonBuilder.create() 44 | } 45 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /app/src/main/java/com/iamkamrul/modularization/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.modularization 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | import androidx.activity.enableEdgeToEdge 7 | import androidx.compose.foundation.layout.fillMaxSize 8 | import androidx.compose.material3.MaterialTheme 9 | import androidx.compose.material3.Surface 10 | import androidx.compose.ui.Modifier 11 | import androidx.navigation.compose.rememberNavController 12 | import com.iamkamrul.modularization.navigation.AppNavigation 13 | import com.iamkamrul.designsystem.theme.JetPackComposeModularizationTheme 14 | import dagger.hilt.android.AndroidEntryPoint 15 | 16 | @AndroidEntryPoint 17 | class MainActivity : ComponentActivity() { 18 | override fun onCreate(savedInstanceState: Bundle?) { 19 | enableEdgeToEdge() 20 | super.onCreate(savedInstanceState) 21 | setContent { 22 | JetPackComposeModularizationTheme { 23 | val navController = rememberNavController() 24 | Surface( 25 | modifier = Modifier.fillMaxSize(), 26 | color = MaterialTheme.colorScheme.background 27 | ) { 28 | AppNavigation( 29 | navController = navController 30 | ) 31 | } 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .idea/ 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | .cxx 10 | local.properties 11 | 12 | # Built application files 13 | *.apk 14 | *.aar 15 | *.ap_ 16 | *.aab 17 | 18 | # Files for the ART/Dalvik VM 19 | *.dex 20 | 21 | # Java class files 22 | *.class 23 | 24 | # Generated files 25 | bin/ 26 | gen/ 27 | out/ 28 | # Uncomment the following line in case you need and you don't have the release build type files in your app 29 | # release/ 30 | 31 | # Gradle files 32 | .gradle/ 33 | build/ 34 | 35 | # Proguard folder generated by Eclipse 36 | proguard/ 37 | 38 | # Log Files 39 | *.log 40 | 41 | # Android Studio Navigation editor temp files 42 | .navigation/ 43 | 44 | # Android Studio captures folder 45 | captures/ 46 | 47 | # Keystore files 48 | # Uncomment the following lines if you do not want to check your keystore files in. 49 | #*.jks 50 | #*.keystore 51 | 52 | # External native build folder generated in Android Studio 2.2 and later 53 | .cxx/ 54 | 55 | # Google Services (e.g. APIs or Firebase) 56 | # google-services.json 57 | 58 | # Freeline 59 | freeline.py 60 | freeline/ 61 | freeline_project_description.json 62 | 63 | # fastlane 64 | fastlane/report.xml 65 | fastlane/Preview.html 66 | fastlane/screenshots 67 | fastlane/test_output 68 | fastlane/readme.md 69 | 70 | # Version control 71 | vcs.xml 72 | 73 | # lint 74 | lint/intermediates/ 75 | lint/generated/ 76 | lint/outputs/ 77 | lint/tmp/ 78 | -------------------------------------------------------------------------------- /core/data/src/main/java/com/iamkamrul/data/repoimpl/GithubRepoImpl.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.data.repoimpl 2 | 3 | import com.iamkamrul.data.apiservice.ApiService 4 | import com.iamkamrul.data.mapper.ProfileMapper 5 | import com.iamkamrul.data.mapper.RepoListItemMapper 6 | import com.iamkamrul.data.utils.NetworkBoundResource 7 | import com.iamkamrul.data.utils.mapFromApiResponse 8 | import com.iamkamrul.domain.repository.GithubRepository 9 | import com.iamkamrul.domain.usecase.ProfileUseCase 10 | import com.iamkamrul.domain.usecase.RepoListUseCase 11 | import com.iamkamrul.domain.utils.Result 12 | import com.iamkamrul.entity.ProfileEntity 13 | import com.iamkamrul.entity.RepoItemEntity 14 | import kotlinx.coroutines.flow.Flow 15 | import javax.inject.Inject 16 | 17 | class GithubRepoImpl @Inject constructor( 18 | private val apiService: ApiService, 19 | private val networkBoundResources: NetworkBoundResource, 20 | private val repositoryListItemMapper: RepoListItemMapper, 21 | private val profileMapper: ProfileMapper 22 | ):GithubRepository{ 23 | 24 | override suspend fun fetchRepoList(params: RepoListUseCase.Params): Flow>> { 25 | return mapFromApiResponse( 26 | result = networkBoundResources.downloadData { 27 | apiService.fetchRepoList(params.userName) 28 | },repositoryListItemMapper 29 | ) 30 | } 31 | 32 | override suspend fun fetchProfile(params: ProfileUseCase.Params): Flow> { 33 | return mapFromApiResponse( 34 | result = networkBoundResources.downloadData { 35 | apiService.fetchProfile(params.userName) 36 | },profileMapper 37 | ) 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /core/di/src/main/java/com/iamkamrul/di/module/OkHttpModule.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.di.module 2 | 3 | 4 | import dagger.Module 5 | import dagger.Provides 6 | import dagger.hilt.InstallIn 7 | import dagger.hilt.components.SingletonComponent 8 | import okhttp3.OkHttpClient 9 | import okhttp3.logging.HttpLoggingInterceptor 10 | import timber.log.Timber 11 | import java.util.concurrent.TimeUnit 12 | import javax.inject.Singleton 13 | 14 | @Module 15 | @InstallIn(SingletonComponent::class) 16 | object OkHttpModule { 17 | 18 | @Provides 19 | @Singleton 20 | fun provideLoggerInterceptor(): HttpLoggingInterceptor { 21 | val interceptor = HttpLoggingInterceptor { message -> Timber.e(message) } 22 | interceptor.apply { interceptor.level = HttpLoggingInterceptor.Level.HEADERS } 23 | interceptor.apply { interceptor.level = HttpLoggingInterceptor.Level.BODY } 24 | return interceptor 25 | } 26 | 27 | @Provides 28 | @Singleton 29 | fun provideOkHttpClient( 30 | loggerInterceptor: HttpLoggingInterceptor, 31 | ): OkHttpClient { 32 | val timeOut = 30 33 | val httpClient = OkHttpClient().newBuilder() 34 | .connectTimeout(timeOut.toLong(), TimeUnit.SECONDS) 35 | .readTimeout(timeOut.toLong(), TimeUnit.SECONDS) 36 | .writeTimeout(timeOut.toLong(), TimeUnit.SECONDS) 37 | 38 | httpClient.addInterceptor(loggerInterceptor) 39 | httpClient.addInterceptor { chain -> 40 | val original = chain.request() 41 | val requestBuilder = original.newBuilder() 42 | .addHeader("Accept", "application/json") 43 | val request = requestBuilder.build() 44 | chain.proceed(request) 45 | } 46 | return httpClient.build() 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /core/ui/src/main/java/com/iamkamrul/ui/component/ErrorMessageCompose.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.ui.component 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Arrangement 5 | import androidx.compose.foundation.layout.Column 6 | import androidx.compose.foundation.layout.Spacer 7 | import androidx.compose.foundation.layout.fillMaxSize 8 | import androidx.compose.foundation.layout.fillMaxWidth 9 | import androidx.compose.foundation.layout.height 10 | import androidx.compose.foundation.layout.padding 11 | import androidx.compose.foundation.shape.RoundedCornerShape 12 | import androidx.compose.material3.Button 13 | import androidx.compose.material3.MaterialTheme 14 | import androidx.compose.material3.Text 15 | import androidx.compose.runtime.Composable 16 | import androidx.compose.ui.Alignment 17 | import androidx.compose.ui.Modifier 18 | import androidx.compose.ui.text.style.TextAlign 19 | import androidx.compose.ui.unit.dp 20 | import com.iamkamrul.designsystem.theme.color 21 | 22 | @Composable 23 | fun NetworkErrorMessage( 24 | message:String, 25 | modifier: Modifier = Modifier, 26 | onClickRefresh:()->Unit 27 | ){ 28 | Column( 29 | verticalArrangement = Arrangement.Center, 30 | horizontalAlignment = Alignment.CenterHorizontally, 31 | modifier = modifier 32 | .background(MaterialTheme.color.white) 33 | .fillMaxSize() 34 | .padding(16.dp) 35 | ) { 36 | 37 | Text(text = message, style = MaterialTheme.typography.titleLarge, textAlign = TextAlign.Center) 38 | Spacer(modifier = modifier.height(32.dp)) 39 | Button( 40 | modifier = modifier.fillMaxWidth().height(50.dp), 41 | onClick = onClickRefresh, 42 | shape = RoundedCornerShape(10.dp) 43 | ) { 44 | Text(text = "Refresh") 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /features/profile/src/main/java/com/iamkamrul/profile/ProfileViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.profile 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.iamkamrul.domain.usecase.ProfileUseCase 6 | import com.iamkamrul.domain.utils.Result 7 | import com.iamkamrul.entity.ProfileEntity 8 | import dagger.hilt.android.lifecycle.HiltViewModel 9 | import kotlinx.coroutines.flow.* 10 | import kotlinx.coroutines.launch 11 | import javax.inject.Inject 12 | 13 | @HiltViewModel 14 | class ProfileViewModel @Inject constructor( 15 | private val profileUseCase: ProfileUseCase 16 | ): ViewModel(){ 17 | private val _profileUiState = MutableStateFlow(ProfileUiState.Loading) 18 | val profileUiState get() = _profileUiState.asStateFlow() 19 | 20 | 21 | init { 22 | fetchProfile() 23 | } 24 | 25 | private fun fetchProfile(){ 26 | viewModelScope.launch { 27 | profileUseCase.execute(ProfileUseCase.Params(userName = "kamrul3288")).collect{ response-> 28 | when(response){ 29 | is Result.Error -> _profileUiState.value = ProfileUiState.Error(response.message) 30 | Result.Loading -> _profileUiState.value = ProfileUiState.Loading 31 | is Result.Success -> _profileUiState.value = ProfileUiState.Success(response.data) 32 | } 33 | } 34 | } 35 | } 36 | 37 | fun handleAction(action: ProfileUiAction){ 38 | when(action){ 39 | ProfileUiAction.FetchProfile -> fetchProfile() 40 | } 41 | } 42 | } 43 | 44 | 45 | 46 | sealed interface ProfileUiState{ 47 | data object Loading : ProfileUiState 48 | data class Success(val data:ProfileEntity): ProfileUiState 49 | data class Error(val message:String) : ProfileUiState 50 | } 51 | 52 | sealed interface ProfileUiAction{ 53 | data object FetchProfile:ProfileUiAction 54 | } -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 32 | -------------------------------------------------------------------------------- /features/repolist/src/main/java/com/iamkamrul/repolist/RepoListViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.repolist 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.iamkamrul.domain.usecase.RepoListUseCase 6 | import com.iamkamrul.domain.utils.Result 7 | import com.iamkamrul.entity.RepoItemEntity 8 | import dagger.hilt.android.lifecycle.HiltViewModel 9 | import kotlinx.coroutines.flow.* 10 | import kotlinx.coroutines.launch 11 | import javax.inject.Inject 12 | 13 | @HiltViewModel 14 | class RepoListViewModel @Inject constructor( 15 | private val repoListUseCase: RepoListUseCase 16 | ): ViewModel(){ 17 | private val _repoListUiState = MutableStateFlow(RepoListUiState.Loading) 18 | val repoListUiState get() = _repoListUiState.asStateFlow() 19 | 20 | init { 21 | fetchRepoList() 22 | } 23 | 24 | private fun fetchRepoList(){ 25 | viewModelScope.launch { 26 | repoListUseCase.execute(RepoListUseCase.Params(userName = "kamrul3288")).collect{response-> 27 | when(response){ 28 | is Result.Error -> _repoListUiState.value = RepoListUiState.Error(response.message) 29 | is Result.Loading -> _repoListUiState.value = RepoListUiState.Loading 30 | is Result.Success ->{ 31 | if (response.data.isEmpty()){ 32 | _repoListUiState.value = RepoListUiState.RepoListEmpty 33 | return@collect 34 | } 35 | _repoListUiState.value = RepoListUiState.HasRepoList(response.data) 36 | } 37 | } 38 | } 39 | } 40 | } 41 | 42 | fun handleAction(action: RepoListUiAction){ 43 | when(action){ 44 | RepoListUiAction.FetchRepoList -> fetchRepoList() 45 | } 46 | } 47 | } 48 | 49 | sealed interface RepoListUiState{ 50 | data object Loading:RepoListUiState 51 | data class HasRepoList(val repoList:List):RepoListUiState 52 | data object RepoListEmpty:RepoListUiState 53 | data class Error(val message:String):RepoListUiState 54 | } 55 | 56 | sealed interface RepoListUiAction{ 57 | data object FetchRepoList:RepoListUiAction 58 | } -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.iamkamrul.android.application) 3 | alias(libs.plugins.iamkamrul.android.hilt) 4 | alias(libs.plugins.iamkamrul.android.application.compose) 5 | } 6 | 7 | android { 8 | namespace = "com.iamkamrul.modularization" 9 | defaultConfig { 10 | applicationId = "com.iamkamrul.compose" 11 | versionCode = 1 12 | versionName = "1.0.0" 13 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 14 | vectorDrawables { 15 | useSupportLibrary = true 16 | } 17 | } 18 | 19 | buildTypes { 20 | debug { 21 | isMinifyEnabled = false 22 | isShrinkResources = false 23 | proguardFiles( 24 | getDefaultProguardFile("proguard-android-optimize.txt"), 25 | "proguard-rules.pro" 26 | ) 27 | } 28 | debug { 29 | isMinifyEnabled = false 30 | isShrinkResources = false 31 | proguardFiles( 32 | getDefaultProguardFile("proguard-android-optimize.txt"), 33 | "proguard-rules.pro" 34 | ) 35 | } 36 | } 37 | buildFeatures{ 38 | buildConfig = true 39 | } 40 | packaging { 41 | resources { 42 | excludes += "/META-INF/{AL2.0,LGPL2.1}" 43 | } 44 | } 45 | } 46 | dependencies { 47 | implementation(projects.core.di) 48 | implementation(projects.core.domain) 49 | implementation(projects.core.data) 50 | implementation(projects.common) 51 | implementation(projects.core.ui) 52 | implementation(projects.core.designsystem) 53 | 54 | implementation(projects.features.repolist) 55 | implementation(projects.features.profile) 56 | 57 | implementation(libs.androidx.compose.activity) 58 | implementation(libs.androidx.compose.navigation) 59 | implementation(libs.androidx.compose.hilt.navigation) 60 | implementation(libs.androidx.lifecycle.runtimeCompose) 61 | implementation(libs.androidx.lifecycle.viewModelCompose) 62 | 63 | testImplementation(libs.test.junit) 64 | androidTestImplementation(libs.test.extjunit) 65 | androidTestImplementation(libs.test.espresso) 66 | androidTestImplementation(libs.test.compose.ui.junit) 67 | debugImplementation(libs.androidx.compose.ui.manifest) 68 | } -------------------------------------------------------------------------------- /core/designsystem/src/main/java/com/iamkamrul/designsystem/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.designsystem.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val Orange10 = Color(0xFF6B261E) 6 | val Orange20 = Color(0xFF9D2D20) 7 | val Orange30 = Color(0xFFD3301D) 8 | val Orange40 = Color(0xFFF04935) 9 | val Orange50 = Color(0xFFF35B49) 10 | val Orange60 = Color(0xFFF87C6D) 11 | val Orange70 = Color(0xFFFB9084) 12 | val Orange80 = Color(0xFFFCA197) 13 | val Orange90 = Color(0xFFFDB3AA) 14 | val Orange95 = Color(0xFFFED2CD) 15 | val Orange99 = Color(0xFFFED2CD) 16 | 17 | 18 | val BlueGray10 = Color(0xFF1D192B) 19 | val BlueGray20 = Color(0xFF332D41) 20 | val BlueGray30 = Color(0xFF4A4458) 21 | val BlueGray40 = Color(0xFF625B71) 22 | val BlueGray50 = Color(0xFF7A7289) 23 | val BlueGray60 = Color(0xFF958DA5) 24 | val BlueGray70 = Color(0xFFB0A7C0) 25 | val BlueGray80 = Color(0xFFCCC2DC) 26 | val BlueGray90 = Color(0xFFE8DEF8) 27 | val BlueGray95 = Color(0xFFF6EDFF) 28 | val BlueGray99 = Color(0xFFFFFBFE) 29 | 30 | 31 | val Red10 = Color(0xFF410E0B) 32 | val Red20 = Color(0xFF601410) 33 | val Red30 = Color(0xFF8C1D18) 34 | val Red40 = Color(0xFFB3261E) 35 | val Red50 = Color(0xFFDC362E) 36 | val Red60 = Color(0xFFE46962) 37 | val Red70 = Color(0xFFEC928E) 38 | val Red80 = Color(0xFFF2B8B5) 39 | 40 | val Green10 = Color(0xFF051905) 41 | val Green20 = Color(0xFF0A480A) 42 | val Green30 = Color(0xFF075F07) 43 | val Green40 = Color(0xFF027D02) 44 | val Green50 = Color(0xFF029702) 45 | val Green60 = Color(0xFF02B102) 46 | val Green70 = Color(0xFF01CB01) 47 | val Green80 = Color(0xFF00E500) 48 | val Green90 = Color(0xFF5FF45F) 49 | val Green95 = Color(0xFF96FB96) 50 | val Green99 = Color(0xFFC0FCC0) 51 | 52 | val White = Color(0xFFFFFFFF) 53 | val White10 = Color(0xFFE6E6E6) 54 | val White20 = Color(0xFFCCCCCC) 55 | val White30 = Color(0xFFB3B3B3) 56 | val White40 = Color(0xFF999999) 57 | val White50 = Color(0xFF808080) 58 | val White60 = Color(0xFF666666) 59 | val White70 = Color(0xFF4D4D4D) 60 | val White80 = Color(0xFF333333) 61 | val White90 = Color(0xFF1A1A1A) 62 | val White95 = Color(0xFF0D0D0D) 63 | val White99 = Color(0xFF030303) 64 | 65 | 66 | val Black = Color(0xFF000000) 67 | val Black10 = Color(0xFF1A1A1A) 68 | val Black20 = Color(0xFF333333) 69 | val Black30 = Color(0xFF4D4D4D) 70 | val Black40 = Color(0xFF666666) 71 | val Black50 = Color(0xFF808080) 72 | val Black60 = Color(0xFF999999) 73 | val Black70 = Color(0xFFB3B3B3) 74 | val Black80 = Color(0xFFCCCCCC) 75 | val Black90 = Color(0xFFE6E6E6) 76 | val Black95 = Color(0xFFF2F2F2) 77 | val Black99 = Color(0xFFFCFCFC) 78 | 79 | 80 | val Gray10 = Color(0xFFCFCFCF) 81 | val Gray20 = Color(0xFFD1D1D1) 82 | val Gray30 = Color(0xFFD9D9D9) 83 | val Gray40 = Color(0xFFDEDEDE) 84 | val Gray50 = Color(0xFFE3E3E3) 85 | val Gray60 = Color(0xFFE3E3E3) 86 | val Gray70 = Color(0xFFEDEDED) 87 | val Gray80 = Color(0xFFF2F2F2) 88 | val Gray90 = Color(0xFFF7F7F7) 89 | val Gray95 = Color(0xFFFAFAFA) 90 | val Gray99 = Color(0xFFFCFCFC) 91 | -------------------------------------------------------------------------------- /core/data/src/main/java/com/iamkamrul/data/utils/NetworkBoundResource.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.data.utils 2 | 3 | import com.google.gson.JsonParser 4 | import kotlinx.coroutines.CoroutineDispatcher 5 | import kotlinx.coroutines.Dispatchers 6 | import kotlinx.coroutines.withContext 7 | import okhttp3.ResponseBody 8 | import retrofit2.HttpException 9 | import retrofit2.Response 10 | import java.io.IOException 11 | import java.net.SocketTimeoutException 12 | import com.iamkamrul.domain.utils.Result 13 | import kotlinx.coroutines.flow.Flow 14 | import kotlinx.coroutines.flow.catch 15 | import kotlinx.coroutines.flow.flow 16 | import timber.log.Timber 17 | import javax.inject.Inject 18 | 19 | class NetworkBoundResource @Inject constructor(){ 20 | private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO 21 | 22 | suspend fun downloadData(api : suspend () -> Response): Flow> { 23 | return withContext(ioDispatcher) { 24 | flow { 25 | emit(Result.Loading) 26 | val response:Response = api() 27 | if (response.isSuccessful){ 28 | response.body()?.let { 29 | emit(Result.Success(data = it)) 30 | }?: emit(Result.Error(message = "Unknown error occurred", code = 0)) 31 | }else{ 32 | emit(Result.Error(message = parserErrorBody(response.errorBody()), code = response.code())) 33 | } 34 | 35 | }.catch { error-> 36 | Timber.e(error.localizedMessage) 37 | emit(Result.Error(message = message(error), code = code(error))) 38 | } 39 | } 40 | } 41 | 42 | private fun parserErrorBody(response: ResponseBody?):String{ 43 | return response?.let { 44 | val errorMessage = JsonParser.parseString(it.string()).asJsonObject["message"].asString 45 | errorMessage.ifEmpty { "Whoops! Something went wrong. Please try again." } 46 | errorMessage 47 | }?:"Whoops! Unknown error occurred. Please try again" 48 | } 49 | private fun message(throwable: Throwable?):String{ 50 | when (throwable) { 51 | is SocketTimeoutException -> return "Whoops! Connection time out. Please try again" 52 | is IOException -> return "Whoops! No Internet Connection. Please try again" 53 | is HttpException -> return try { 54 | val errorJsonString = throwable.response()?.errorBody()?.string() 55 | val errorMessage = JsonParser.parseString(errorJsonString).asJsonObject["message"].asString 56 | errorMessage.ifEmpty { "Whoops! Something went wrong. Please try again." } 57 | }catch (e:Exception){ 58 | "Whoops! Unknown error occurred. Please try again" 59 | } 60 | } 61 | return "Whoops! Unknown error occurred. Please try again" 62 | } 63 | private fun code(throwable: Throwable?):Int{ 64 | return if (throwable is HttpException) (throwable).code() 65 | else 0 66 | } 67 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /model/apiresponse/src/main/java/com/iamkamrul/apiresponse/RepoItemApiResponse.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.apiresponse 2 | 3 | data class RepoItemApiResponse( 4 | val archive_url: String?, 5 | val archived: Boolean?, 6 | val assignees_url: String?, 7 | val blobs_url: String?, 8 | val branches_url: String?, 9 | val clone_url: String?, 10 | val collaborators_url: String?, 11 | val comments_url: String?, 12 | val commits_url: String?, 13 | val compare_url: String?, 14 | val contents_url: String?, 15 | val contributors_url: String?, 16 | val created_at: String?, 17 | val default_branch: String?, 18 | val deployments_url: String?, 19 | val description: String?, 20 | val disabled: Boolean?, 21 | val downloads_url: String?, 22 | val events_url: String?, 23 | val fork: Boolean?, 24 | val forks_count: Int?, 25 | val forks_url: String?, 26 | val full_name: String?, 27 | val git_commits_url: String?, 28 | val git_refs_url: String?, 29 | val git_tags_url: String?, 30 | val git_url: String?, 31 | val has_downloads: Boolean?, 32 | val has_issues: Boolean?, 33 | val has_pages: Boolean?, 34 | val has_projects: Boolean?, 35 | val has_wiki: Boolean?, 36 | val homepage: String?, 37 | val hooks_url: String?, 38 | val html_url: String?, 39 | val id: Int?, 40 | val is_template: Boolean?, 41 | val issue_comment_url: String?, 42 | val issue_events_url: String?, 43 | val issues_url: String?, 44 | val keys_url: String?, 45 | val labels_url: String?, 46 | val language: String?, 47 | val languages_url: String?, 48 | val merges_url: String?, 49 | val milestones_url: String?, 50 | val mirror_url: String?, 51 | val name: String?, 52 | val node_id: String?, 53 | val notifications_url: String?, 54 | val open_issues_count: Int?, 55 | val owner: ReposItemOwner?, 56 | val permissions: ReposItemPermissions?, 57 | val `private`: Boolean?, 58 | val pulls_url: String?, 59 | val pushed_at: String?, 60 | val releases_url: String?, 61 | val size: Int?, 62 | val ssh_url: String?, 63 | val stargazers_count: Int?, 64 | val stargazers_url: String?, 65 | val statuses_url: String?, 66 | val subscribers_url: String?, 67 | val subscription_url: String?, 68 | val svn_url: String?, 69 | val tags_url: String?, 70 | val teams_url: String?, 71 | val template_repository: Any?, 72 | val topics: List?, 73 | val trees_url: String?, 74 | val updated_at: String?, 75 | val url: String?, 76 | val visibility: String?, 77 | val watchers_count: Int? 78 | ) 79 | 80 | data class ReposItemOwner( 81 | val avatar_url: String?, 82 | val events_url: String?, 83 | val followers_url: String?, 84 | val following_url: String?, 85 | val gists_url: String?, 86 | val gravatar_id: String?, 87 | val html_url: String?, 88 | val id: Int?, 89 | val login: String?, 90 | val node_id: String?, 91 | val organizations_url: String?, 92 | val received_events_url: String?, 93 | val repos_url: String?, 94 | val site_admin: Boolean?, 95 | val starred_url: String?, 96 | val subscriptions_url: String?, 97 | val type: String?, 98 | val url: String? 99 | ) 100 | 101 | data class ReposItemPermissions( 102 | val admin: Boolean?, 103 | val pull: Boolean?, 104 | val push: Boolean? 105 | ) -------------------------------------------------------------------------------- /core/designsystem/src/main/java/com/iamkamrul/designsystem/component/ScaffoldTopAppbar.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.designsystem.component 2 | 3 | import androidx.compose.foundation.layout.PaddingValues 4 | import androidx.compose.material.icons.Icons 5 | import androidx.compose.material.icons.outlined.ArrowBack 6 | import androidx.compose.material3.CenterAlignedTopAppBar 7 | import androidx.compose.material3.ExperimentalMaterial3Api 8 | import androidx.compose.material3.Icon 9 | import androidx.compose.material3.IconButton 10 | import androidx.compose.material3.MaterialTheme 11 | import androidx.compose.material3.Scaffold 12 | import androidx.compose.material3.Surface 13 | import androidx.compose.material3.Text 14 | import androidx.compose.material3.TopAppBarDefaults 15 | import androidx.compose.material3.contentColorFor 16 | import androidx.compose.runtime.Composable 17 | import androidx.compose.ui.graphics.Color 18 | import androidx.compose.ui.graphics.painter.Painter 19 | import androidx.compose.ui.graphics.vector.rememberVectorPainter 20 | import androidx.compose.ui.unit.dp 21 | import com.iamkamrul.designsystem.theme.color 22 | 23 | @OptIn(ExperimentalMaterial3Api::class) 24 | @Composable 25 | fun ScaffoldTopAppbar( 26 | containerColor: Color = MaterialTheme.colorScheme.background, 27 | contentColor: Color = contentColorFor(containerColor), 28 | title:String, 29 | onNavigationIconClick:()->Unit, 30 | navigationIcon: Painter = rememberVectorPainter(image = Icons.Outlined.ArrowBack), 31 | snackbarHost: @Composable () -> Unit = {}, 32 | bottomBar: @Composable () -> Unit = {}, 33 | content: @Composable (PaddingValues) -> Unit, 34 | ){ 35 | Scaffold( 36 | containerColor = containerColor, 37 | contentColor = contentColor, 38 | snackbarHost = snackbarHost, 39 | topBar = { 40 | Surface(shadowElevation = 1.dp) { 41 | CenterAlignedTopAppBar( 42 | title = { 43 | Text(text = title) 44 | }, 45 | colors = TopAppBarDefaults.centerAlignedTopAppBarColors( 46 | containerColor = MaterialTheme.color.topAppBar, 47 | navigationIconContentColor = MaterialTheme.color.black, 48 | titleContentColor = MaterialTheme.color.black, 49 | actionIconContentColor = MaterialTheme.color.black, 50 | ), 51 | navigationIcon = { 52 | IconButton(onClick = onNavigationIconClick) { 53 | Icon( 54 | painter = navigationIcon, 55 | contentDescription = "navigationIcon" 56 | ) 57 | } 58 | }, 59 | ) 60 | } 61 | }, 62 | bottomBar = bottomBar, 63 | content = content 64 | ) 65 | } 66 | 67 | @OptIn(ExperimentalMaterial3Api::class) 68 | @Composable 69 | fun ScaffoldTopAppbar( 70 | containerColor: Color = MaterialTheme.colorScheme.background, 71 | contentColor: Color = contentColorFor(containerColor), 72 | title:String, 73 | snackbarHost: @Composable () -> Unit = {}, 74 | bottomBar: @Composable () -> Unit = {}, 75 | content: @Composable (PaddingValues) -> Unit, 76 | 77 | ){ 78 | Scaffold( 79 | snackbarHost = snackbarHost, 80 | containerColor = containerColor, 81 | contentColor = contentColor, 82 | topBar = { 83 | Surface( 84 | shadowElevation = 2.dp 85 | ) { 86 | CenterAlignedTopAppBar( 87 | title = { 88 | Text(text = title) 89 | }, 90 | colors = TopAppBarDefaults.centerAlignedTopAppBarColors( 91 | containerColor = MaterialTheme.color.topAppBar, 92 | navigationIconContentColor = MaterialTheme.color.black, 93 | titleContentColor = MaterialTheme.color.black, 94 | actionIconContentColor = MaterialTheme.color.black, 95 | ) 96 | ) 97 | } 98 | }, 99 | bottomBar = bottomBar, 100 | content = content 101 | ) 102 | } 103 | -------------------------------------------------------------------------------- /core/designsystem/src/main/java/com/iamkamrul/designsystem/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.designsystem.theme 2 | 3 | import androidx.compose.material3.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.Font 6 | import androidx.compose.ui.text.font.FontFamily 7 | import androidx.compose.ui.text.font.FontStyle 8 | import androidx.compose.ui.text.font.FontWeight 9 | import androidx.compose.ui.unit.sp 10 | import com.iamkamrul.designsystem.R 11 | 12 | internal val helveticaFamily = FontFamily( 13 | Font(R.font.roboto_extra_light,FontWeight.ExtraLight, FontStyle.Normal), 14 | Font(R.font.roboto_light,FontWeight.Light, FontStyle.Normal), 15 | Font(R.font.roboto_regular,FontWeight.Normal, FontStyle.Normal), 16 | Font(R.font.roboto_italic,FontWeight.Normal, FontStyle.Italic), 17 | Font(R.font.roboto_medium,FontWeight.Medium, FontStyle.Normal), 18 | Font(R.font.roboto_bold,FontWeight.Bold, FontStyle.Normal), 19 | Font(R.font.roboto_bold_italic,FontWeight.Bold, FontStyle.Italic), 20 | Font(R.font.roboto_black,FontWeight.Black, FontStyle.Normal), 21 | 22 | ) 23 | 24 | val Typography = Typography( 25 | displayLarge = TextStyle( 26 | fontFamily = helveticaFamily, 27 | fontWeight = FontWeight.Normal, 28 | fontSize = 57.sp, 29 | lineHeight = 64.sp, 30 | letterSpacing = 0.5.sp 31 | ), 32 | displayMedium = TextStyle( 33 | fontFamily = helveticaFamily, 34 | fontWeight = FontWeight.Normal, 35 | fontSize = 45.sp, 36 | lineHeight = 52.sp, 37 | letterSpacing = 0.5.sp 38 | ), 39 | displaySmall = TextStyle( 40 | fontFamily = helveticaFamily, 41 | fontWeight = FontWeight.Normal, 42 | fontSize = 36.sp, 43 | lineHeight = 44.sp, 44 | letterSpacing = 0.5.sp 45 | ), 46 | headlineLarge = TextStyle( 47 | fontFamily = helveticaFamily, 48 | fontWeight = FontWeight.Normal, 49 | fontSize = 32.sp, 50 | lineHeight = 40.sp, 51 | letterSpacing = 0.5.sp 52 | ), 53 | headlineMedium = TextStyle( 54 | fontFamily = helveticaFamily, 55 | fontWeight = FontWeight.Normal, 56 | fontSize = 28.sp, 57 | lineHeight = 36.sp, 58 | letterSpacing = 0.5.sp 59 | ), 60 | headlineSmall = TextStyle( 61 | fontFamily = helveticaFamily, 62 | fontWeight = FontWeight.Normal, 63 | fontSize = 24.sp, 64 | lineHeight = 32.sp, 65 | letterSpacing = 0.5.sp 66 | ), 67 | titleLarge = TextStyle( 68 | fontFamily = helveticaFamily, 69 | fontWeight = FontWeight.Medium, 70 | fontSize = 22.sp, 71 | lineHeight = 28.sp, 72 | letterSpacing = 0.5.sp 73 | ), 74 | titleMedium = TextStyle( 75 | fontFamily = helveticaFamily, 76 | fontWeight = FontWeight.Medium, 77 | fontSize = 16.sp, 78 | lineHeight = 24.sp, 79 | letterSpacing = 0.5.sp 80 | ), 81 | titleSmall = TextStyle( 82 | fontFamily = helveticaFamily, 83 | fontWeight = FontWeight.Medium, 84 | fontSize = 14.sp, 85 | lineHeight = 20.sp, 86 | letterSpacing = 0.5.sp 87 | ), 88 | bodyLarge = TextStyle( 89 | fontFamily = helveticaFamily, 90 | fontWeight = FontWeight.Normal, 91 | fontSize = 16.sp, 92 | lineHeight = 24.sp, 93 | letterSpacing = 0.5.sp 94 | ), 95 | bodyMedium = TextStyle( 96 | fontFamily = helveticaFamily, 97 | fontWeight = FontWeight.Normal, 98 | fontSize = 12.sp, 99 | lineHeight = 16.sp, 100 | letterSpacing = 0.5.sp 101 | ), 102 | labelLarge = TextStyle( 103 | fontFamily = helveticaFamily, 104 | fontWeight = FontWeight.Medium, 105 | fontSize = 14.sp, 106 | lineHeight = 20.sp, 107 | letterSpacing = 0.5.sp 108 | ), 109 | labelMedium = TextStyle( 110 | fontFamily = helveticaFamily, 111 | fontWeight = FontWeight.Medium, 112 | fontSize = 12.sp, 113 | lineHeight = 16.sp, 114 | letterSpacing = 0.5.sp 115 | ), 116 | labelSmall = TextStyle( 117 | fontFamily = helveticaFamily, 118 | fontWeight = FontWeight.Medium, 119 | fontSize = 11.sp, 120 | lineHeight = 16.sp, 121 | letterSpacing = 0.5.sp 122 | ), 123 | ) -------------------------------------------------------------------------------- /features/profile/src/main/java/com/iamkamrul/profile/ProfileScreen.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.profile 2 | import androidx.compose.foundation.Image 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.border 5 | import androidx.compose.foundation.layout.* 6 | import androidx.compose.foundation.shape.CircleShape 7 | import androidx.compose.material3.CircularProgressIndicator 8 | import androidx.compose.material3.Text 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.runtime.getValue 11 | import androidx.compose.ui.Alignment 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.draw.clip 14 | import androidx.compose.ui.graphics.Color 15 | import androidx.compose.ui.text.font.FontWeight 16 | import androidx.compose.ui.unit.dp 17 | import androidx.compose.ui.unit.sp 18 | import androidx.hilt.navigation.compose.hiltViewModel 19 | import androidx.lifecycle.compose.collectAsStateWithLifecycle 20 | import coil.compose.rememberAsyncImagePainter 21 | import com.iamkamrul.designsystem.component.ScaffoldTopAppbar 22 | import com.iamkamrul.ui.component.NetworkErrorMessage 23 | import com.iamkamrul.entity.ProfileEntity 24 | 25 | @Composable 26 | internal fun ProfileScreenRoute( 27 | viewModel:ProfileViewModel = hiltViewModel(), 28 | onBackBtnClick:()->Unit 29 | ){ 30 | val profileUiState by viewModel.profileUiState.collectAsStateWithLifecycle() 31 | ProfileScreen( 32 | profileUiState = profileUiState, 33 | onRefreshProfile = viewModel::handleAction, 34 | onBackBtnClick = onBackBtnClick 35 | ) 36 | } 37 | 38 | @Composable 39 | private fun ProfileScreen( 40 | profileUiState: ProfileUiState, 41 | onRefreshProfile:(ProfileUiAction)->Unit, 42 | onBackBtnClick:()->Unit 43 | ){ 44 | ScaffoldTopAppbar( 45 | title = "Profile", 46 | onNavigationIconClick = onBackBtnClick 47 | ) { 48 | val modifier = Modifier.padding(it) 49 | Box( 50 | modifier = modifier.fillMaxSize(), 51 | contentAlignment = Alignment.Center 52 | ){ 53 | when(profileUiState){ 54 | is ProfileUiState.Error -> NetworkErrorMessage(message = profileUiState.message){ 55 | onRefreshProfile(ProfileUiAction.FetchProfile) 56 | } 57 | ProfileUiState.Loading -> CircularProgressIndicator() 58 | is ProfileUiState.Success -> ProfileContentView(data = profileUiState.data) 59 | } 60 | } 61 | } 62 | } 63 | 64 | @Composable 65 | private fun ProfileContentView( 66 | data: ProfileEntity, 67 | modifier: Modifier = Modifier, 68 | ){ 69 | Column( 70 | modifier = modifier 71 | .padding(16.dp) 72 | .background(Color.White) 73 | .fillMaxSize(), 74 | horizontalAlignment = Alignment.CenterHorizontally 75 | ) { 76 | Image( 77 | painter = rememberAsyncImagePainter(model = data.userAvatar), 78 | contentDescription = "", 79 | modifier = modifier 80 | .size(80.dp) 81 | .aspectRatio(1f) 82 | .clip(CircleShape) 83 | .border(1.dp, Color.Gray, CircleShape) 84 | ) 85 | 86 | Spacer(modifier = modifier.height(16.dp)) 87 | Text(text = data.userFullName, fontSize = 16.sp, fontWeight = FontWeight.Bold) 88 | Text(text = data.userName) 89 | Spacer(modifier = modifier.height(16.dp)) 90 | Text(text = data.about) 91 | 92 | Spacer(modifier = modifier.height(8.dp)) 93 | Spacer(modifier = modifier 94 | .fillMaxWidth() 95 | .height(1.dp) 96 | .background(Color.LightGray)) 97 | Spacer(modifier = modifier.height(8.dp)) 98 | 99 | Row(modifier = modifier.fillMaxWidth()) { 100 | Column( 101 | modifier = modifier.weight(1f), 102 | horizontalAlignment = Alignment.CenterHorizontally 103 | ) { 104 | Text(text = data.repoCount.toString(), fontSize = 20.sp, fontWeight = FontWeight.Bold) 105 | Text(text = "Repository") 106 | } 107 | 108 | Column( 109 | modifier = modifier.weight(1f), 110 | horizontalAlignment = Alignment.CenterHorizontally 111 | ) { 112 | Text(text = data.followerCount.toString(), fontSize = 20.sp, fontWeight = FontWeight.Bold) 113 | Text(text = "Follower") 114 | } 115 | 116 | 117 | Column( 118 | modifier = modifier.weight(1f), 119 | horizontalAlignment = Alignment.CenterHorizontally 120 | ) { 121 | Text(text = data.followingCount.toString(), fontSize = 20.sp, fontWeight = FontWeight.Bold) 122 | Text(text = "Following") 123 | } 124 | } 125 | } 126 | } 127 | 128 | -------------------------------------------------------------------------------- /core/designsystem/src/main/java/com/iamkamrul/designsystem/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.designsystem.theme 2 | 3 | import androidx.compose.foundation.isSystemInDarkTheme 4 | import androidx.compose.material3.MaterialTheme 5 | import androidx.compose.material3.darkColorScheme 6 | import androidx.compose.material3.lightColorScheme 7 | 8 | import android.app.Activity 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.runtime.CompositionLocalProvider 11 | import androidx.compose.runtime.SideEffect 12 | import androidx.compose.ui.graphics.Color.Companion.Red 13 | import androidx.compose.ui.graphics.toArgb 14 | import androidx.compose.ui.platform.LocalView 15 | import androidx.core.view.WindowCompat 16 | 17 | private val darkColorScheme = darkColorScheme( 18 | //The primary color could be used for the app bar, buttons, and other important UI elements. 19 | primary = Orange40, 20 | //Color used for text and icons displayed on top of the primary color. 21 | onPrimary = White, 22 | //The secondary color could be used for tabs, headers, and other secondary UI elements. 23 | //secondary = PurpleGrey40, 24 | //The onSecondary color could be used for text and icons that appear on top of secondary-colored backgrounds. 25 | //onSecondary = White, 26 | //The Background color could be used for backgrounds. 27 | background = Black, 28 | //The onBackground color could be used for text and icons that appear on top of background backgrounds. 29 | onBackground = White, 30 | //The surface color that affect surfaces of components, such as cards, sheets, and menus. 31 | surface = Black10, 32 | onSurface = White, 33 | surfaceVariant = Black10, 34 | onSurfaceVariant = White, 35 | //It refers to the color or style used to represent an error state in the UI. This color is typically applied to components or indicators that highlight invalid input, validation errors, or other error conditions in the application. The error color helps draw attention to and communicate the presence of errors to the user. 36 | error = Red, 37 | // It refers top of error text and icon colors 38 | onError = White, 39 | // It refers to the border or outline around a UI component. The outline is a visual representation that defines the shape and boundaries of the component. It can be used to provide visual emphasis, highlight selected or focused components, or convey interactivity. 40 | //outline = Pink40, 41 | ) 42 | 43 | private val lightColorScheme = lightColorScheme( 44 | //The primary color could be used for the app bar, buttons, and other important UI elements. 45 | primary = Orange40, 46 | //Color used for text and icons displayed on top of the primary color. 47 | onPrimary = White, 48 | //The secondary color could be used for tabs, headers, and other secondary UI elements. 49 | //secondary = PurpleGrey40, 50 | //The onSecondary color could be used for text and icons that appear on top of secondary-colored backgrounds. 51 | //onSecondary = White, 52 | //The Background color could be used for backgrounds. 53 | background = White, 54 | //The onBackground color could be used for text and icons that appear on top of background backgrounds. 55 | onBackground = Black, 56 | //The surface color that affect surfaces of components, such as cards, sheets, and menus. 57 | surface = White, 58 | onSurface = Black, 59 | surfaceVariant = White, 60 | onSurfaceVariant = Black, 61 | //It refers to the color or style used to represent an error state in the UI. This color is typically applied to components or indicators that highlight invalid input, validation errors, or other error conditions in the application. The error color helps draw attention to and communicate the presence of errors to the user. 62 | error = Red40, 63 | // It refers top of error text and icon colors 64 | onError = White, 65 | // It refers to the border or outline around a UI component. The outline is a visual representation that defines the shape and boundaries of the component. It can be used to provide visual emphasis, highlight selected or focused components, or convey interactivity. 66 | //outline = Pink40 67 | ) 68 | 69 | @Composable 70 | fun JetPackComposeModularizationTheme( 71 | darkTheme: Boolean = isSystemInDarkTheme(), 72 | content: @Composable () -> Unit 73 | ) { 74 | val colorScheme = when { 75 | darkTheme -> darkColorScheme 76 | else -> lightColorScheme 77 | } 78 | val view = LocalView.current 79 | if (!view.isInEditMode) { 80 | SideEffect { 81 | val window = (view.context as Activity).window 82 | val statusBarColor = if(darkTheme) Black10 else White 83 | window.statusBarColor = statusBarColor.toArgb() 84 | WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme 85 | } 86 | } 87 | val appColorScheme = when { 88 | darkTheme -> LocalDarkColorScheme 89 | else -> LocalLightColorScheme 90 | } 91 | CompositionLocalProvider(values = arrayOf(LocalAppColor provides appColorScheme)) { 92 | MaterialTheme( 93 | colorScheme = colorScheme, 94 | typography = Typography, 95 | content = content 96 | ) 97 | } 98 | 99 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /features/repolist/src/main/java/com/iamkamrul/repolist/RepoListScreen.kt: -------------------------------------------------------------------------------- 1 | package com.iamkamrul.repolist 2 | import androidx.compose.foundation.Image 3 | import androidx.compose.foundation.clickable 4 | import androidx.compose.foundation.layout.Arrangement 5 | import androidx.compose.foundation.layout.Box 6 | import androidx.compose.foundation.layout.Column 7 | import androidx.compose.foundation.layout.Row 8 | import androidx.compose.foundation.layout.Spacer 9 | import androidx.compose.foundation.layout.aspectRatio 10 | import androidx.compose.foundation.layout.fillMaxSize 11 | import androidx.compose.foundation.layout.fillMaxWidth 12 | import androidx.compose.foundation.layout.height 13 | import androidx.compose.foundation.layout.padding 14 | import androidx.compose.foundation.layout.size 15 | import androidx.compose.foundation.layout.width 16 | import androidx.compose.foundation.lazy.LazyColumn 17 | import androidx.compose.foundation.lazy.items 18 | import androidx.compose.foundation.shape.CircleShape 19 | import androidx.compose.material.icons.Icons 20 | import androidx.compose.material.icons.outlined.ForkRight 21 | import androidx.compose.material.icons.outlined.Language 22 | import androidx.compose.material.icons.outlined.StarBorder 23 | import androidx.compose.material3.Card 24 | import androidx.compose.material3.CardDefaults 25 | import androidx.compose.material3.CircularProgressIndicator 26 | import androidx.compose.material3.Icon 27 | import androidx.compose.material3.MaterialTheme 28 | import androidx.compose.material3.Text 29 | import androidx.compose.runtime.Composable 30 | import androidx.compose.runtime.getValue 31 | import androidx.compose.ui.Alignment 32 | import androidx.compose.ui.Modifier 33 | import androidx.compose.ui.draw.clip 34 | import androidx.compose.ui.graphics.RectangleShape 35 | import androidx.compose.ui.unit.dp 36 | import androidx.hilt.navigation.compose.hiltViewModel 37 | import androidx.lifecycle.compose.collectAsStateWithLifecycle 38 | import coil.compose.rememberAsyncImagePainter 39 | import com.iamkamrul.designsystem.component.ScaffoldTopAppbar 40 | import com.iamkamrul.designsystem.theme.color 41 | import com.iamkamrul.entity.RepoItemEntity 42 | import com.iamkamrul.ui.component.NetworkErrorMessage 43 | 44 | @Composable 45 | internal fun RepoListRoute( 46 | viewModel:RepoListViewModel = hiltViewModel(), 47 | onRepoItemClick:()->Unit 48 | ){ 49 | val repoListUiSate by viewModel.repoListUiState.collectAsStateWithLifecycle() 50 | 51 | RepoListScreen( 52 | repoListUiSate = repoListUiSate, 53 | onRepoItemClick = onRepoItemClick, 54 | onRefreshRepoList = viewModel::handleAction 55 | ) 56 | } 57 | 58 | @Composable 59 | fun RepoListScreen( 60 | repoListUiSate: RepoListUiState, 61 | onRepoItemClick:()->Unit, 62 | onRefreshRepoList: (RepoListUiAction) -> Unit 63 | ){ 64 | ScaffoldTopAppbar( 65 | title = "Repo List", 66 | containerColor = MaterialTheme.color.secondaryBackground 67 | ) { 68 | val modifier = Modifier.padding(it) 69 | Box( 70 | modifier = modifier.fillMaxSize(), 71 | contentAlignment = Alignment.Center 72 | ) { 73 | when(repoListUiSate){ 74 | is RepoListUiState.Error -> { 75 | NetworkErrorMessage( 76 | message = repoListUiSate.message, 77 | onClickRefresh = { 78 | onRefreshRepoList(RepoListUiAction.FetchRepoList) 79 | } 80 | ) 81 | } 82 | is RepoListUiState.HasRepoList -> { 83 | LazyColumn{ 84 | items(items = repoListUiSate.repoList){repoItem-> 85 | RepoListItem( 86 | repoItem = repoItem, 87 | onItemClick = onRepoItemClick 88 | ) 89 | } 90 | } 91 | 92 | } 93 | RepoListUiState.Loading -> CircularProgressIndicator() 94 | RepoListUiState.RepoListEmpty -> Text(text = "No Repo List Found") 95 | } 96 | } 97 | } 98 | } 99 | 100 | @Composable 101 | private fun RepoListItem( 102 | modifier: Modifier = Modifier, 103 | repoItem: RepoItemEntity, 104 | onItemClick:()->Unit 105 | ){ 106 | Card( 107 | modifier = modifier.padding(bottom = 10.dp).clickable { onItemClick()}, 108 | shape = RectangleShape, 109 | colors = CardDefaults.cardColors(containerColor = MaterialTheme.color.white) 110 | ) { 111 | Column( 112 | modifier = modifier.padding(16.dp).fillMaxWidth() 113 | ) { 114 | Row( 115 | verticalAlignment = Alignment.CenterVertically 116 | ) { 117 | Image( 118 | painter = rememberAsyncImagePainter(model = repoItem.userAvatarUrl), 119 | contentDescription = "", 120 | modifier = modifier.size(80.dp) 121 | .aspectRatio(1f) 122 | .clip(CircleShape) 123 | ) 124 | Spacer(modifier = modifier.width(16.dp)) 125 | Column { 126 | Text(text = repoItem.repoName,style = MaterialTheme.typography.titleMedium) 127 | Text(text = repoItem.userName,style = MaterialTheme.typography.bodyLarge) 128 | } 129 | } 130 | 131 | Spacer(modifier = modifier.height(16.dp)) 132 | Text(text = repoItem.repoFullName,style = MaterialTheme.typography.bodyMedium) 133 | Text(text = repoItem.repoDescription, style = MaterialTheme.typography.bodyMedium) 134 | 135 | Spacer(modifier = modifier.height(16.dp)) 136 | Row { 137 | Row( 138 | modifier = modifier.weight(1f), 139 | verticalAlignment = Alignment.CenterVertically 140 | ) { 141 | Icon( 142 | imageVector = Icons.Outlined.Language, 143 | contentDescription = "" 144 | ) 145 | Spacer(modifier = modifier.width(4.dp)) 146 | Text(text = repoItem.language,style = MaterialTheme.typography.labelLarge) 147 | } 148 | Row( 149 | modifier = modifier.weight(1f), 150 | horizontalArrangement = Arrangement.Center, 151 | verticalAlignment = Alignment.CenterVertically 152 | ) { 153 | Icon(imageVector = Icons.Outlined.StarBorder, contentDescription = "") 154 | Spacer(modifier = modifier.width(4.dp)) 155 | Text(text = "${repoItem.stargazers_count} Star", style = MaterialTheme.typography.labelLarge) 156 | } 157 | Row( 158 | modifier = modifier.weight(1f), 159 | horizontalArrangement = Arrangement.End, 160 | verticalAlignment = Alignment.CenterVertically 161 | ) { 162 | Icon(imageVector = Icons.Outlined.ForkRight, contentDescription = "") 163 | Spacer(modifier = modifier.width(4.dp)) 164 | Text(text = "${repoItem.forksCount} Forked", style = MaterialTheme.typography.labelLarge) 165 | } 166 | } 167 | } 168 | } 169 | } 170 | 171 | 172 | 173 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | agp = "8.7.2" 3 | kotlin = "2.0.20" 4 | secrets = "2.0.1" 5 | ksp = "2.0.20-1.0.25" 6 | hilt = "2.49" 7 | gms = "4.4.2" 8 | firebasecrashlyticsplugin = "3.0.2" 9 | 10 | #Android Compose Dep Version 11 | composeActivity = "1.9.3" 12 | composeBom = "2024.11.00" 13 | composeCompiler = "1.5.4" 14 | composeNavigation = "2.8.4" 15 | composeFoundation = "1.5.1" 16 | accompanist = "0.30.1" 17 | constraintlayoutCompose = "1.1.0" 18 | composeHiltNavigation = "1.2.0" 19 | 20 | #Android MDC Core Dep Version 21 | androidxAppcompat = "1.7.0" 22 | androidxCoreKtx = "1.15.0" 23 | material = "1.12.0" 24 | recyclerview = "1.3.2" 25 | activity = "1.9.3" 26 | fragment = "1.8.5" 27 | constraintLayout = "2.2.0" 28 | cardview = "1.0.0" 29 | browser ="1.7.0" 30 | swiperefreshlayout = "1.1.0" 31 | navigationComponent = "2.8.4" 32 | navigationComponentHilt = "1.1.0" 33 | lifecycle = "2.8.7" 34 | preference = "1.2.1" 35 | 36 | retrofit2 = "2.9.0" 37 | okhHttp3 = "4.12.0" 38 | gson = "2.10.1" 39 | rxjava3 = "3.1.7" 40 | rxjava3Android = "3.0.2" 41 | lottie = "6.1.0" 42 | timber = "5.0.1" 43 | dateced = "1.1.1" 44 | alerter = "7.2.4" 45 | photoview = "2.3.0" 46 | circleimage = "3.1.0" 47 | coil = "2.5.0" 48 | dotsindicator = "4.3" 49 | appupdate = "2.1.0" 50 | facebook = "latest.release" 51 | maps = "19.0.0" 52 | mapsLocation = "21.3.0" 53 | mapsPlaces = "4.1.0" 54 | firebase = "33.6.0" 55 | room = "2.6.1" 56 | playstorereview = "2.0.2" 57 | desugar = "2.1.3" 58 | kotlinxCoroutines = "1.9.0" 59 | viewstate = "1.1.1" 60 | customview = "1.1.2" 61 | customTabs ="3.0.3" 62 | sdp = "1.1.0" 63 | junit = "4.13.2" 64 | extjunit = "1.2.1" 65 | espresso = "3.6.1" 66 | org-jetbrains-kotlin-android = "2.0.20" 67 | 68 | [bundles] 69 | network = ["retrofit2.core","retrofit2.rx3adapter","retrofit2.gsonconverter","gson","okhHttp3.core","okhHttp3.interceptor","kotlinx-coroutines-android"] 70 | rxJava3 = ["rxjava3.core","rxjava3.android"] 71 | 72 | [libraries] 73 | androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } 74 | androidx-compose-ui = { group = "androidx.compose.ui", name = "ui"} 75 | androidx-compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } 76 | androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } 77 | androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } 78 | androidx-compose-ui-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } 79 | androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" } 80 | androidx-compose-material-iconsExtended= { group = "androidx.compose.material", name = "material-icons-extended"} 81 | androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation"} 82 | androidx-compose-activity = {group = "androidx.activity", name="activity-compose", version.ref="composeActivity"} 83 | androidx-compose-navigation = {group = "androidx.navigation", name="navigation-compose", version.ref="composeNavigation"} 84 | androidx-compose-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout-compose", version.ref = "constraintlayoutCompose" } 85 | androidx-compose-hilt-navigation = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "composeHiltNavigation" } 86 | 87 | androidx-appcompat = {group = "androidx.appcompat", name = "appcompat", version.ref="androidxAppcompat"} 88 | androidx-corektx = {group = "androidx.core", name = "core-ktx", version.ref="androidxCoreKtx"} 89 | androidx-constraintlayout = {group = "androidx.constraintlayout", name = "constraintlayout", version.ref="constraintLayout"} 90 | androidx-material = {group = "com.google.android.material", name = "material", version.ref="material"} 91 | androidx-fragment = {group = "androidx.fragment", name = "fragment-ktx", version.ref="fragment"} 92 | androidx-activity = {group = "androidx.activity", name = "activity-ktx", version.ref="activity"} 93 | androidx-cardview = {group = "androidx.cardview", name = "cardview", version.ref="cardview"} 94 | androidx-browser = {group = "androidx.browser", name = "browser", version.ref="browser"} 95 | androidx-recyclerview = {group = "androidx.recyclerview", name = "recyclerview", version.ref="recyclerview"} 96 | androidx-preference = {group = "androidx.preference", name = "preference", version.ref="preference"} 97 | androidx-swiperefreshlayout = {group = "androidx.swiperefreshlayout", name = "swiperefreshlayout", version.ref="swiperefreshlayout"} 98 | 99 | androidx-lifecycle-viewmodel-ktx = {group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref="lifecycle"} 100 | androidx-lifecycle-viewmodel-savedstate = {group = "androidx.lifecycle", name = "lifecycle-viewmodel-savedstate", version.ref="lifecycle"} 101 | androidx-lifecycle-livedata = {group = "androidx.lifecycle", name = "lifecycle-livedata-ktx", version.ref="lifecycle"} 102 | androidx-lifecycle-runtime = {group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref="lifecycle"} 103 | androidx-lifecycle-commonjava8 = {group = "androidx.lifecycle", name = "lifecycle-common-java8", version.ref="lifecycle"} 104 | androidx-lifecycle-service = {group = "androidx.lifecycle", name = "lifecycle-service", version.ref="lifecycle"} 105 | androidx-lifecycle-viewModelCompose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycle" } 106 | androidx-lifecycle-runtimeCompose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycle" } 107 | 108 | desugar = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "desugar" } 109 | 110 | androidx-navigation-ktx = {group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref="navigationComponent"} 111 | androidx-navigation-ui = {group = "androidx.navigation", name = "navigation-ui-ktx", version.ref="navigationComponent"} 112 | 113 | hilt-android = {group = "com.google.dagger", name = "hilt-android", version.ref="hilt"} 114 | hilt-compiler = {group = "com.google.dagger", name = "hilt-android-compiler", version.ref="hilt"} 115 | 116 | retrofit2-core = {group = "com.squareup.retrofit2", name = "retrofit", version.ref="retrofit2"} 117 | retrofit2-rx3adapter = {group = "com.squareup.retrofit2", name = "adapter-rxjava3", version.ref="retrofit2"} 118 | retrofit2-gsonconverter = {group = "com.squareup.retrofit2", name = "converter-gson", version.ref="retrofit2"} 119 | gson = {group = "com.google.code.gson", name = "gson", version.ref="gson"} 120 | kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinxCoroutines" } 121 | 122 | alerter-tapadoo = { group = "com.github.tapadoo", name = "alerter", version.ref = "alerter"} 123 | 124 | okhHttp3-core = {group = "com.squareup.okhttp3", name = "okhttp", version.ref="okhHttp3"} 125 | okhHttp3-interceptor = {group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref="okhHttp3"} 126 | 127 | rxjava3-core = {group = "io.reactivex.rxjava3", name = "rxjava", version.ref="rxjava3"} 128 | rxjava3-android = {group = "io.reactivex.rxjava3", name = "rxandroid", version.ref="rxjava3Android"} 129 | 130 | image-photoviewzoom = {group = "com.github.chrisbanes", name = "PhotoView", version.ref="photoview"} 131 | image-circleimage = {group = "de.hdodenhof", name = "circleimageview", version.ref="circleimage"} 132 | image-coil-compose = {group = "io.coil-kt", name = "coil-compose", version.ref="coil"} 133 | image-coil = {group = "io.coil-kt", name = "coil", version.ref="coil"} 134 | indicator-dot = {group = "com.tbuonomo", name = "dotsindicator", version.ref="dotsindicator"} 135 | 136 | log-timber = {group = "com.jakewharton.timber", name = "timber", version.ref="timber"} 137 | animation-lottie = {group = "com.airbnb.android", name = "lottie", version.ref="lottie"} 138 | dimension-sdp = {group = "com.intuit.sdp", name = "sdp-android", version.ref="sdp"} 139 | dimension-ssp = {group = "com.intuit.ssp", name = "ssp-android", version.ref="sdp"} 140 | 141 | kamrul3288-viewstate = {group = "com.github.kamrul3288", name = "viewstatelayout", version.ref="viewstate"} 142 | kamrul3288-customview = {group = "com.github.kamrul3288", name = "customview-android", version.ref="customview"} 143 | kamrul3288-dateced = {group = "com.github.kamrul3288", name = "dateced", version.ref="dateced"} 144 | 145 | appupdate-core = {group = "com.google.android.play", name = "app-update", version.ref="appupdate"} 146 | appupdate-ktx = {group = "com.google.android.play", name = "app-update-ktx", version.ref="appupdate"} 147 | 148 | review-core = {group = "com.google.android.play", name = "review", version.ref="playstorereview"} 149 | review-ktx = {group = "com.google.android.play", name = "review-ktx", version.ref="playstorereview"} 150 | 151 | room-ktx = {group = "androidx.room", name = "room-ktx", version.ref="room"} 152 | room-runtime = {group = "androidx.room", name = "room-runtime", version.ref="room"} 153 | room-compiler = {group = "androidx.room", name = "room-compiler", version.ref="room"} 154 | room-common = {group = "androidx.room", name = "room-common", version.ref="room"} 155 | 156 | firebase-bom = {group = "com.google.firebase", name = "firebase-bom", version.ref="firebase"} 157 | firebase-messaging = {group = "com.google.firebase", name = "firebase-messaging-ktx"} 158 | firebase-analytics = {group = "com.google.firebase", name = "firebase-analytics-ktx"} 159 | firebase-crashlytics = {group = "com.google.firebase", name = "firebase-crashlytics-ktx"} 160 | 161 | maps-sdk = {group = "com.google.android.gms", name = "play-services-maps", version.ref="maps"} 162 | maps-location = {group = "com.google.android.gms", name = "play-services-location", version.ref="mapsLocation"} 163 | maps-places = {group = "com.google.android.libraries.places", name = "places", version.ref="mapsPlaces"} 164 | 165 | facebook-sdk = {group = "com.facebook.android", name = "facebook-android-sdk", version.ref="facebook"} 166 | facebook-marketing = {group = "com.facebook.android", name = "facebook-marketing", version.ref="facebook"} 167 | 168 | customtabs = {group = "de.peilicke.sascha", name = "android-customtabs", version.ref="customTabs"} 169 | test-junit = {group = "junit", name = "junit", version.ref="junit"} 170 | test-extjunit = {group = "androidx.test.ext", name = "junit", version.ref="extjunit"} 171 | test-espresso = {group = "androidx.test.espresso", name = "espresso-core", version.ref="espresso"} 172 | test-compose-ui-junit = { group = "androidx.compose.ui", name = "ui-test-junit4"} 173 | 174 | # Dependencies of the included build-logic 175 | android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "agp" } 176 | kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } 177 | firebase-crashlytics-gradlePlugin = { group = "com.google.firebase", name = "firebase-crashlytics-gradle", version.ref = "firebasecrashlyticsplugin" } 178 | ksp-gradlePlugin = { group = "com.google.devtools.ksp", name = "com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" } 179 | 180 | [plugins] 181 | com-android-application = { id = "com.android.application", version.ref = "agp" } 182 | com-android-library = { id = "com.android.library", version.ref = "agp" } 183 | com-google-firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebasecrashlyticsplugin" } 184 | com-google-gms-google-services = { id = "com.google.gms.google-services", version.ref = "gms" } 185 | com-google-dagger-hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } 186 | org-jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } 187 | org-jetbrains-kotlin-plugin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } 188 | androidx-navigation-safeargs = { id = "androidx.navigation.safeargs", version.ref = "navigationComponent" } 189 | com-google-devtools-ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } 190 | secrets-gradle-plugin = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin", version.ref = "secrets" } 191 | org-jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "org-jetbrains-kotlin-android" } 192 | compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } 193 | 194 | # Plugins defined by this project 195 | iamkamrul-android-application = { id = "iamkamrul.android.application", version = "unspecified" } 196 | iamkamrul-android-application-compose = { id = "iamkamrul.android.application.compose", version = "unspecified" } 197 | iamkamrul-android-application-firebase = { id = "iamkamrul.android.application.firebase", version = "unspecified" } 198 | iamkamrul-android-hilt = { id = "iamkamrul.android.hilt", version = "unspecified" } 199 | iamkamrul-android-library = { id = "iamkamrul.android.library", version = "unspecified" } 200 | iamkamrul-android-library-compose = { id = "iamkamrul.android.library.compose", version = "unspecified" } 201 | iamkamrul-android-feature-compose = { id = "iamkamrul.android.feature.compose", version = "unspecified" } 202 | iamkamrul-android-room = { id = "iamkamrul.android.room", version = "unspecified" } 203 | iamkamrul-android-retrofit = { id = "iamkamrul.android.retrofit", version = "unspecified" } 204 | iamkamrul-jvm-library = { id = "iamkamrul.jvm.library", version = "unspecified" } 205 | 206 | --------------------------------------------------------------------------------