diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5b7eddb..3346751 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -14,8 +14,15 @@ android { } dependencies { - implementation(project(Modules.coreUi)) - implementation(project(Modules.coreStrings)) + projectImplementation(Modules.coreUi) + projectImplementation(Modules.coreStrings) + projectImplementation(Modules.coreUtils) + + projectImplementation(Modules.commonDomain) + projectImplementation(Modules.commonData) + projectImplementation(Modules.commonDi) + projectImplementation(Modules.commonUi) + projectImplementation(Modules.commonMultiCompose) implementation(Deps.Compose.ui) implementation(Deps.Compose.foundation) diff --git a/app/src/main/java/tech/antee/compose_multimodule_template/MainActivity.kt b/app/src/main/java/tech/antee/compose_multimodule_template/MainActivity.kt index ab9a67a..5c32124 100644 --- a/app/src/main/java/tech/antee/compose_multimodule_template/MainActivity.kt +++ b/app/src/main/java/tech/antee/compose_multimodule_template/MainActivity.kt @@ -3,15 +3,48 @@ package tech.antee.compose_multimodule_template import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.Modifier +import androidx.navigation.compose.rememberNavController +import tech.antee.compose_multimodule_template.di.LocalAppProvider import tech.antee.compose_multimodule_template.ui.theme.MyApplicationTheme +// TODO("Implement your app") class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MyApplicationTheme { -// TODO("Implement your app") + GlobalDependenciesProvider { + Navigation() + } } } } + + @Composable + private fun Navigation(modifier: Modifier = Modifier) { + val navController = rememberNavController() + val destinations = LocalAppProvider.current.destinations +// val someFeature = destinations.find() +// val anotherFeature = destinations.find() + +// Box(modifier.fillMaxSize()) { +// NavHost(navController, someFeature.featureRoute) { +// with(someFeature) { composable(navController, destinations) } +// with(anotherFeature) { composable(navController, destinations) } +// } +// } + } + + @Composable + private fun GlobalDependenciesProvider( + content: @Composable () -> Unit + ) { + CompositionLocalProvider( + LocalAppProvider provides application.appProvider, + content = content + ) + } } diff --git a/app/src/main/java/tech/antee/compose_multimodule_template/MyApplication.kt b/app/src/main/java/tech/antee/compose_multimodule_template/MyApplication.kt index cb24ed4..8585d75 100644 --- a/app/src/main/java/tech/antee/compose_multimodule_template/MyApplication.kt +++ b/app/src/main/java/tech/antee/compose_multimodule_template/MyApplication.kt @@ -1,5 +1,20 @@ package tech.antee.compose_multimodule_template import android.app.Application +import tech.antee.compose_multimodule_template.di.AppProvider -class MyApplication : Application() +// TODO("Implement your app") +class MyApplication : Application() { + + lateinit var appProvider: AppProvider + +// override fun onCreate() { +// super.onCreate() +// appProvider = DaggerMyApplicationComponent.factory().create(this).apply { +// inject(this@App) +// } +// } +} + +val Application.appProvider: AppProvider + get() = (this as MyApplication).appProvider \ No newline at end of file diff --git a/app/src/main/java/tech/antee/compose_multimodule_template/di/AppComponent.kt b/app/src/main/java/tech/antee/compose_multimodule_template/di/AppComponent.kt new file mode 100644 index 0000000..7e6c7f4 --- /dev/null +++ b/app/src/main/java/tech/antee/compose_multimodule_template/di/AppComponent.kt @@ -0,0 +1,24 @@ +package tech.antee.compose_multimodule_template.di + +import android.content.Context +import dagger.BindsInstance +import dagger.Component +import tech.antee.compose_multimodule_template.MyApplication +import tech.antee.compose_multimodule_template.di.qualifiers.ApplicationContext +import javax.inject.Singleton + +@Singleton +@Component(modules = [AppModule::class, FeaturesModule::class]) +interface AppComponent : AppProvider { + + fun inject(app: MyApplication) + + @Component.Factory + interface Factory { + fun create( + @BindsInstance + @ApplicationContext + context: Context + ): AppComponent + } +} diff --git a/app/src/main/java/tech/antee/compose_multimodule_template/di/AppModule.kt b/app/src/main/java/tech/antee/compose_multimodule_template/di/AppModule.kt new file mode 100644 index 0000000..80798b5 --- /dev/null +++ b/app/src/main/java/tech/antee/compose_multimodule_template/di/AppModule.kt @@ -0,0 +1,9 @@ +package tech.antee.compose_multimodule_template.di + +import dagger.Module +import tech.antee.compose_multimodule_template.data.di.DataModule + +@Module( + includes = [DataModule::class] +) +interface AppModule diff --git a/app/src/main/java/tech/antee/compose_multimodule_template/di/AppProvider.kt b/app/src/main/java/tech/antee/compose_multimodule_template/di/AppProvider.kt new file mode 100644 index 0000000..8a33640 --- /dev/null +++ b/app/src/main/java/tech/antee/compose_multimodule_template/di/AppProvider.kt @@ -0,0 +1,10 @@ +package tech.antee.compose_multimodule_template.di + +import androidx.compose.runtime.compositionLocalOf +import tech.antee.compose_multimodule_template.multi_compose.Destinations + +interface AppProvider { + val destinations: Destinations +} + +val LocalAppProvider = compositionLocalOf { error("No app provider found!") } diff --git a/app/src/main/java/tech/antee/compose_multimodule_template/di/FeaturesModule.kt b/app/src/main/java/tech/antee/compose_multimodule_template/di/FeaturesModule.kt new file mode 100644 index 0000000..126f8f4 --- /dev/null +++ b/app/src/main/java/tech/antee/compose_multimodule_template/di/FeaturesModule.kt @@ -0,0 +1,8 @@ +package tech.antee.compose_multimodule_template.di + +import dagger.Module + +@Module( + includes = [] +) +interface FeaturesModule diff --git a/buildSrc/src/main/java/Accessors.kt b/buildSrc/src/main/java/Accessors.kt index 155b5fe..a3a826a 100644 --- a/buildSrc/src/main/java/Accessors.kt +++ b/buildSrc/src/main/java/Accessors.kt @@ -1,9 +1,10 @@ import org.gradle.api.artifacts.Dependency +import org.gradle.api.artifacts.ProjectDependency import org.gradle.api.artifacts.dsl.DependencyHandler /* * Extensions that used to provide above dependency syntax that mimics Gradle Kotlin DSL syntax - * in module\build.gradle.kts.kts files. Mimics to extensions that are generated on the fly by Gradle. + * in module\build.gradle.kts files. Mimics to extensions that are generated on the fly by Gradle. */ fun DependencyHandler.implementation(dependencyNotation: Any): Dependency? = add("implementation", dependencyNotation) @@ -19,3 +20,22 @@ fun DependencyHandler.testImplementation(dependencyNotation: Any): Dependency? = fun DependencyHandler.androidTestImplementation(dependencyNotation: Any): Dependency? = add("androidTestImplementation", dependencyNotation) + +fun DependencyHandler.projectImplementation(dependencyNotation: String): Dependency? = + add("implementation", project(dependencyNotation)) + +fun DependencyHandler.projectApi(dependencyNotation: String): Dependency? = + add("api", project(dependencyNotation)) + +private fun DependencyHandler.project( + path: String, + configuration: String? = null +): ProjectDependency = uncheckedCast( + project( + if (configuration != null) mapOf("path" to path, "configuration" to configuration) + else mapOf("path" to path) + ) +) + +@Suppress("unchecked_cast", "nothing_to_inline") +private inline fun uncheckedCast(obj: Any?): T = obj as T diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index c27111b..cdf23bc 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -6,17 +6,19 @@ object Versions { const val androidxFragment = "1.2.5" const val androidxCoreKtx = "1.7.0" const val androidxConstraint = "2.1.1" + const val androidxConstraintCompose = "1.0.1" const val androidxRecyclerView = "1.0.0" - const val androidxSwipeRefresh = "1.1.0" const val androidxNavigation = "2.4.2" const val androidxLifecycle = "2.4.0" const val androidxViewpager = "1.1.0-alpha01" - const val androidxWorkManager = "2.4.0" + const val androidxWorkManager = "2.7.0" const val androidxRoom = "2.4.0-beta01" const val androidxPaging = "3.0.0-alpha06" const val androidxViewPager2 = "1.0.0" const val androidxHilt = "1.0.0-alpha03" const val hilt = "2.36" + const val authApiPhone = "18.0.1" + const val splashScreen = "1.0.0" const val material = "1.5.0-alpha04" const val playServicesLocation = "17.0.0" @@ -51,16 +53,23 @@ object Versions { const val androidxEspresso = "3.4.0" const val androidxCoreTesting = "1.1.3" + const val protobufPlugin = "0.8.18" + const val protoc = "3.21.2" + const val protobuf = "3.18.0" + const val dataStore = "1.0.0" const val gradle = "7.0.3" } object Deps { - const val coreKtx = "androidx.core:core-ktx:${Versions.androidxCoreKtx}" const val appCompat = "androidx.appcompat:appcompat:${Versions.androidxAppcompat}" const val material = "com.google.android.material:material:${Versions.material}" - const val lifecycle = "androidx.lifecycle:lifecycle-runtime-ktx:${Versions.androidxLifecycle}" + const val lifecycleRuntimeKtx = "androidx.lifecycle:lifecycle-runtime-ktx:${Versions.androidxLifecycle}" + const val lifecycleService = "androidx.lifecycle:lifecycle-service:${Versions.androidxLifecycle}" const val constraintLayout = "androidx.constraintlayout:constraintlayout:${Versions.androidxConstraint}" + const val inject = "javax.inject:javax.inject:${Versions.javaxInject}" + const val workManager = "androidx.work:work-runtime-ktx:${Versions.androidxWorkManager}" + const val splashScreen = "androidx.core:core-splashscreen:${Versions.splashScreen}" object Test { const val jUnit = "junit:junit:${Versions.junit}" @@ -73,6 +82,35 @@ object Deps { const val kotlinGradle = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}" } + object Coroutines { + const val kotlinCore = "org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.coroutine}" + const val android = "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.coroutine}" + const val viewModel = "androidx.lifecycle:lifecycle-viewmodel-ktx:${Versions.androidxLifecycle}" + } + + object Network { + const val retrofit = "com.squareup.retrofit2:retrofit:${Versions.retrofit}" + const val gsonConverter = "com.squareup.retrofit2:converter-gson:${Versions.retrofit}" + const val okHttpLogging = "com.squareup.okhttp3:logging-interceptor:${Versions.okHttp}" + } + + object Local { + const val room = "androidx.room:room-runtime:${Versions.androidxRoom}" + const val roomKapt = "androidx.room:room-compiler:${Versions.androidxRoom}" + const val roomKtx = "androidx.room:room-ktx:${Versions.androidxRoom}" + } + + object LocalStore { + const val dataStore = "androidx.datastore:datastore:${Versions.dataStore}" + const val protobuf = "com.google.protobuf:protobuf-javalite:${Versions.protobuf}" + const val protoc = "com.google.protobuf:protoc:${Versions.protoc}" + } + + object Dagger { + const val core = "com.google.dagger:dagger:${Versions.dagger}" + const val compiler = "com.google.dagger:dagger-compiler:${Versions.dagger}" + } + object Compose { const val ui = "androidx.compose.ui:ui:${Versions.compose}" const val tools = "androidx.compose.ui:ui-tooling:${Versions.compose}" @@ -87,20 +125,19 @@ object Deps { const val navigation = "androidx.navigation:navigation-compose:${Versions.androidxNavigation}" const val viewModel = "androidx.lifecycle:lifecycle-viewmodel-compose:${Versions.androidxLifecycle}" const val jUnit = "androidx.compose.ui:ui-test-junit4:${Versions.compose}" - } - - object Coroutines { - const val android = "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.coroutine}" - const val viewModel = "androidx.lifecycle:lifecycle-viewmodel-ktx:${Versions.androidxLifecycle}" - } - - object Dagger { - const val core = "com.google.dagger:dagger:${Versions.dagger}" - const val compiler = "com.google.dagger:dagger-compiler:${Versions.dagger}" + const val constraintLayoutCompose = + "androidx.constraintlayout:constraintlayout-compose:${Versions.androidxConstraintCompose}" } object Accompanist { const val permissions = "com.google.accompanist:accompanist-permissions:${Versions.accompanist}" const val systemUiController = "com.google.accompanist:accompanist-systemuicontroller:${Versions.accompanist}" + const val pager = "com.google.accompanist:accompanist-pager:${Versions.accompanist}" + const val pagerIndicators = "com.google.accompanist:accompanist-pager-indicators:${Versions.accompanist}" + const val swipeRefresh = "com.google.accompanist:accompanist-swiperefresh:${Versions.accompanist}" + } + + object GMS { + const val authApiPhone = "com.google.android.gms:play-services-auth-api-phone:${Versions.authApiPhone}" } } diff --git a/buildSrc/src/main/java/Modules.kt b/buildSrc/src/main/java/Modules.kt index 18682ae..24783aa 100644 --- a/buildSrc/src/main/java/Modules.kt +++ b/buildSrc/src/main/java/Modules.kt @@ -1,8 +1,16 @@ object Modules { const val app = ":app" - const val commonMultiCompose = ":common:multi_compose" - const val commonUiComponents = ":common:ui-components" const val coreUi = ":core:ui" + const val coreUtils = ":core:utils" const val coreStrings = ":core:strings" + + const val commonDomain = ":common:domain" + const val commonData = ":common:data" + const val commonDataRemote = ":common:data-remote" + const val commonDataLocal = ":common:data-local" + const val commonDi = ":common:di" + const val commonUi = ":common:ui" + const val commonUiComponents = ":common:ui-components" + const val commonMultiCompose = ":common:multi-compose" } diff --git a/common/data-local/.gitignore b/common/data-local/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/common/data-local/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/common/data-local/build.gradle.kts b/common/data-local/build.gradle.kts new file mode 100644 index 0000000..180b43b --- /dev/null +++ b/common/data-local/build.gradle.kts @@ -0,0 +1,22 @@ +plugins { + id(Plugins.androidLibrary) + id(Plugins.androidBase) + id(Plugins.kotlinKapt) +} + +dependencies { + projectImplementation(Modules.coreUtils) + projectApi(Modules.commonDomain) + projectImplementation(Modules.commonDi) + + implementation(Deps.appCompat) + implementation(Deps.coreKtx) + implementation(Deps.Coroutines.kotlinCore) + + implementation(Deps.Local.room) + implementation(Deps.Local.roomKtx) + kapt(Deps.Local.roomKapt) + + implementation(Deps.Dagger.core) + kapt(Deps.Dagger.compiler) +} diff --git a/common/data-local/consumer-rules.pro b/common/data-local/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/common/data-local/proguard-rules.pro b/common/data-local/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/common/data-local/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/common/data-local/src/main/AndroidManifest.xml b/common/data-local/src/main/AndroidManifest.xml new file mode 100644 index 0000000..5489889 --- /dev/null +++ b/common/data-local/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/common/data-local/src/main/java/tech/antee/compose_multimodule_template/data/local/dao/AppDao.kt b/common/data-local/src/main/java/tech/antee/compose_multimodule_template/data/local/dao/AppDao.kt new file mode 100644 index 0000000..f5d2a69 --- /dev/null +++ b/common/data-local/src/main/java/tech/antee/compose_multimodule_template/data/local/dao/AppDao.kt @@ -0,0 +1,6 @@ +package tech.antee.compose_multimodule_template.data.local.dao + +import androidx.room.Dao + +@Dao +interface AppDao \ No newline at end of file diff --git a/common/data-local/src/main/java/tech/antee/compose_multimodule_template/data/local/db/AppDatabase.kt b/common/data-local/src/main/java/tech/antee/compose_multimodule_template/data/local/db/AppDatabase.kt new file mode 100644 index 0000000..1e84b30 --- /dev/null +++ b/common/data-local/src/main/java/tech/antee/compose_multimodule_template/data/local/db/AppDatabase.kt @@ -0,0 +1,20 @@ +package tech.antee.compose_multimodule_template.data.local.db + +import androidx.room.Database +import androidx.room.RoomDatabase +import tech.antee.compose_multimodule_template.data.local.dao.AppDao + +@Database( + entities = [], + version = 1 +) +abstract class AppDatabase : RoomDatabase() { + abstract fun appDao(): AppDao + + + companion object { + const val NAME = "app-database" + } +} + + diff --git a/common/data-local/src/main/java/tech/antee/compose_multimodule_template/data/local/di/LocalModule.kt b/common/data-local/src/main/java/tech/antee/compose_multimodule_template/data/local/di/LocalModule.kt new file mode 100644 index 0000000..94c6c93 --- /dev/null +++ b/common/data-local/src/main/java/tech/antee/compose_multimodule_template/data/local/di/LocalModule.kt @@ -0,0 +1,6 @@ +package tech.antee.compose_multimodule_template.data.local.di + +import dagger.Module + +@Module(includes = [RoomModule::class]) +interface LocalModule diff --git a/common/data-local/src/main/java/tech/antee/compose_multimodule_template/data/local/di/RoomModule.kt b/common/data-local/src/main/java/tech/antee/compose_multimodule_template/data/local/di/RoomModule.kt new file mode 100644 index 0000000..cb1c0b3 --- /dev/null +++ b/common/data-local/src/main/java/tech/antee/compose_multimodule_template/data/local/di/RoomModule.kt @@ -0,0 +1,26 @@ +package tech.antee.compose_multimodule_template.data.local.di + +import android.content.Context +import androidx.room.Room +import dagger.Module +import dagger.Provides +import tech.antee.compose_multimodule_template.data.local.dao.AppDao +import tech.antee.compose_multimodule_template.data.local.db.AppDatabase +import tech.antee.compose_multimodule_template.di.qualifiers.ApplicationContext +import javax.inject.Singleton + +@Module +class RoomModule { + + @Provides + @Singleton + fun appDatabase(@ApplicationContext context: Context): AppDatabase = Room.databaseBuilder( + context, + AppDatabase::class.java, + AppDatabase.NAME + ).build() + + @Provides + @Singleton + fun appDao(db: AppDatabase): AppDao = db.appDao() +} diff --git a/common/data-remote/.gitignore b/common/data-remote/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/common/data-remote/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/common/data-remote/build.gradle.kts b/common/data-remote/build.gradle.kts new file mode 100644 index 0000000..6dd1e8a --- /dev/null +++ b/common/data-remote/build.gradle.kts @@ -0,0 +1,22 @@ +plugins { + id(Plugins.androidLibrary) + id(Plugins.androidBase) + id(Plugins.kotlinKapt) +} + +dependencies { + projectImplementation(Modules.coreUtils) + projectApi(Modules.commonDomain) + projectImplementation(Modules.commonDi) + + implementation(Deps.appCompat) + implementation(Deps.coreKtx) + implementation(Deps.Coroutines.kotlinCore) + + implementation(Deps.Network.retrofit) + implementation(Deps.Network.gsonConverter) + implementation(Deps.Network.okHttpLogging) + + implementation(Deps.Dagger.core) + kapt(Deps.Dagger.compiler) +} diff --git a/common/data-remote/consumer-rules.pro b/common/data-remote/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/common/data-remote/proguard-rules.pro b/common/data-remote/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/common/data-remote/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/common/data-remote/src/main/AndroidManifest.xml b/common/data-remote/src/main/AndroidManifest.xml new file mode 100644 index 0000000..4a0283a --- /dev/null +++ b/common/data-remote/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/common/data-remote/src/main/java/tech/antee/compose_multimodule_template/data/remote/api/NetworkApi.kt b/common/data-remote/src/main/java/tech/antee/compose_multimodule_template/data/remote/api/NetworkApi.kt new file mode 100644 index 0000000..dc235d8 --- /dev/null +++ b/common/data-remote/src/main/java/tech/antee/compose_multimodule_template/data/remote/api/NetworkApi.kt @@ -0,0 +1,3 @@ +package tech.antee.compose_multimodule_template.data.remote.api + +interface NetworkApi \ No newline at end of file diff --git a/common/data-remote/src/main/java/tech/antee/compose_multimodule_template/data/remote/config/NetworkConfig.kt b/common/data-remote/src/main/java/tech/antee/compose_multimodule_template/data/remote/config/NetworkConfig.kt new file mode 100644 index 0000000..15a19e9 --- /dev/null +++ b/common/data-remote/src/main/java/tech/antee/compose_multimodule_template/data/remote/config/NetworkConfig.kt @@ -0,0 +1,5 @@ +package tech.antee.compose_multimodule_template.data.remote.config + +object NetworkConfig { + val BASE_URL = "" // TODO: paste your URL +} diff --git a/common/data-remote/src/main/java/tech/antee/compose_multimodule_template/data/remote/di/NetworkModule.kt b/common/data-remote/src/main/java/tech/antee/compose_multimodule_template/data/remote/di/NetworkModule.kt new file mode 100644 index 0000000..6303e8f --- /dev/null +++ b/common/data-remote/src/main/java/tech/antee/compose_multimodule_template/data/remote/di/NetworkModule.kt @@ -0,0 +1,8 @@ +package tech.antee.compose_multimodule_template.data.remote.di + +import dagger.Module + +@Module( + includes = [RetrofitModule::class, NetworkSourcesModule::class] +) +interface NetworkModule diff --git a/common/data-remote/src/main/java/tech/antee/compose_multimodule_template/data/remote/di/NetworkSourcesModule.kt b/common/data-remote/src/main/java/tech/antee/compose_multimodule_template/data/remote/di/NetworkSourcesModule.kt new file mode 100644 index 0000000..821d88f --- /dev/null +++ b/common/data-remote/src/main/java/tech/antee/compose_multimodule_template/data/remote/di/NetworkSourcesModule.kt @@ -0,0 +1,6 @@ +package tech.antee.compose_multimodule_template.data.remote.di + +import dagger.Module + +@Module +interface NetworkSourcesModule \ No newline at end of file diff --git a/common/data-remote/src/main/java/tech/antee/compose_multimodule_template/data/remote/di/RetrofitModule.kt b/common/data-remote/src/main/java/tech/antee/compose_multimodule_template/data/remote/di/RetrofitModule.kt new file mode 100644 index 0000000..c48f90d --- /dev/null +++ b/common/data-remote/src/main/java/tech/antee/compose_multimodule_template/data/remote/di/RetrofitModule.kt @@ -0,0 +1,32 @@ +package tech.antee.compose_multimodule_template.data.remote.di + +import dagger.Module +import dagger.Provides +import okhttp3.OkHttpClient +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import tech.antee.compose_multimodule_template.data.remote.api.NetworkApi +import tech.antee.compose_multimodule_template.data.remote.config.NetworkConfig +import javax.inject.Singleton + +@Module +class RetrofitModule { + + @Provides + @Singleton + fun appNetworkApi(retrofit: Retrofit): NetworkApi = retrofit.create(NetworkApi::class.java) + + @Provides + @Singleton + fun retrofit( + okHttpClient: OkHttpClient + ): Retrofit = Retrofit.Builder() + .baseUrl(NetworkConfig.BASE_URL) + .addConverterFactory(GsonConverterFactory.create()) + .client(okHttpClient) + .build() + + @Provides + @Singleton + fun okHttpClient(): OkHttpClient = OkHttpClient.Builder().build() +} diff --git a/common/data/.gitignore b/common/data/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/common/data/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/common/data/build.gradle.kts b/common/data/build.gradle.kts new file mode 100644 index 0000000..40eedfa --- /dev/null +++ b/common/data/build.gradle.kts @@ -0,0 +1,28 @@ +plugins { + id(Plugins.androidLibrary) + id(Plugins.androidBase) + id(Plugins.kotlinKapt) +} + +dependencies { + projectApi(Modules.commonDomain) + projectImplementation(Modules.coreUtils) + projectImplementation(Modules.commonDi) + projectImplementation(Modules.commonDataRemote) + projectImplementation(Modules.commonDataLocal) + + implementation(Deps.appCompat) + implementation(Deps.coreKtx) + implementation(Deps.Coroutines.kotlinCore) + + implementation(Deps.Network.retrofit) + implementation(Deps.Network.gsonConverter) + implementation(Deps.Network.okHttpLogging) + + implementation(Deps.Local.room) + implementation(Deps.Local.roomKtx) + kapt(Deps.Local.roomKapt) + + implementation(Deps.Dagger.core) + kapt(Deps.Dagger.compiler) +} diff --git a/common/data/consumer-rules.pro b/common/data/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/common/data/proguard-rules.pro b/common/data/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/common/data/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/common/data/src/main/AndroidManifest.xml b/common/data/src/main/AndroidManifest.xml new file mode 100644 index 0000000..76c9e03 --- /dev/null +++ b/common/data/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/common/data/src/main/java/tech/antee/compose_multimodule_template/data/di/DataModule.kt b/common/data/src/main/java/tech/antee/compose_multimodule_template/data/di/DataModule.kt new file mode 100644 index 0000000..bb83b06 --- /dev/null +++ b/common/data/src/main/java/tech/antee/compose_multimodule_template/data/di/DataModule.kt @@ -0,0 +1,13 @@ +package tech.antee.compose_multimodule_template.data.di + +import dagger.Module +import tech.antee.compose_multimodule_template.data.local.di.LocalModule +import tech.antee.compose_multimodule_template.data.remote.di.NetworkModule + +@Module( + includes = [ + NetworkModule::class, + LocalModule::class + ] +) +interface DataModule diff --git a/common/di/.gitignore b/common/di/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/common/di/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/common/di/build.gradle.kts b/common/di/build.gradle.kts new file mode 100644 index 0000000..0bafc6f --- /dev/null +++ b/common/di/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + id(Plugins.androidLibrary) + id(Plugins.androidBase) + id(Plugins.kotlinKapt) +} + +dependencies { + implementation(Deps.appCompat) + implementation(Deps.coreKtx) + + implementation(Deps.Dagger.core) + kapt(Deps.Dagger.compiler) +} diff --git a/common/di/consumer-rules.pro b/common/di/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/common/di/proguard-rules.pro b/common/di/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/common/di/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/common/di/src/main/AndroidManifest.xml b/common/di/src/main/AndroidManifest.xml new file mode 100644 index 0000000..4f23b02 --- /dev/null +++ b/common/di/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/common/di/src/main/java/tech/antee/compose_multimodule_template/di/qualifiers/ActivityContext.kt b/common/di/src/main/java/tech/antee/compose_multimodule_template/di/qualifiers/ActivityContext.kt new file mode 100644 index 0000000..130f148 --- /dev/null +++ b/common/di/src/main/java/tech/antee/compose_multimodule_template/di/qualifiers/ActivityContext.kt @@ -0,0 +1,7 @@ +package tech.antee.compose_multimodule_template.di.qualifiers + +import javax.inject.Qualifier + +@Qualifier +@Retention(AnnotationRetention.RUNTIME) +annotation class ActivityContext diff --git a/common/di/src/main/java/tech/antee/compose_multimodule_template/di/qualifiers/ApplicationContext.kt b/common/di/src/main/java/tech/antee/compose_multimodule_template/di/qualifiers/ApplicationContext.kt new file mode 100644 index 0000000..4b53652 --- /dev/null +++ b/common/di/src/main/java/tech/antee/compose_multimodule_template/di/qualifiers/ApplicationContext.kt @@ -0,0 +1,7 @@ +package tech.antee.compose_multimodule_template.di.qualifiers + +import javax.inject.Qualifier + +@Qualifier +@Retention(AnnotationRetention.RUNTIME) +annotation class ApplicationContext diff --git a/common/multi-compose/src/main/java/tech/antee/compose_multimodule_template/multi_compose/di/FeatureScope.kt b/common/di/src/main/java/tech/antee/compose_multimodule_template/di/scopes/FeatureScope.kt similarity index 61% rename from common/multi-compose/src/main/java/tech/antee/compose_multimodule_template/multi_compose/di/FeatureScope.kt rename to common/di/src/main/java/tech/antee/compose_multimodule_template/di/scopes/FeatureScope.kt index 844c804..a0c9ada 100644 --- a/common/multi-compose/src/main/java/tech/antee/compose_multimodule_template/multi_compose/di/FeatureScope.kt +++ b/common/di/src/main/java/tech/antee/compose_multimodule_template/di/scopes/FeatureScope.kt @@ -1,4 +1,4 @@ -package tech.antee.compose_multimodule_template.multi_compose.di +package tech.antee.compose_multimodule_template.di.scopes import javax.inject.Scope diff --git a/common/domain/.gitignore b/common/domain/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/common/domain/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/common/domain/build.gradle.kts b/common/domain/build.gradle.kts new file mode 100644 index 0000000..e9a5bda --- /dev/null +++ b/common/domain/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + id(Plugins.androidLibrary) + id(Plugins.androidBase) +} + +dependencies { + projectImplementation(Modules.coreStrings) + + implementation(Deps.appCompat) + implementation(Deps.coreKtx) + implementation(Deps.Coroutines.kotlinCore) + implementation(Deps.inject) +} diff --git a/common/domain/consumer-rules.pro b/common/domain/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/common/domain/proguard-rules.pro b/common/domain/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/common/domain/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/common/domain/src/main/AndroidManifest.xml b/common/domain/src/main/AndroidManifest.xml new file mode 100644 index 0000000..6671f4f --- /dev/null +++ b/common/domain/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/common/domain/src/main/java/tech/antee/compose_multimodule_template/domain/README.md b/common/domain/src/main/java/tech/antee/compose_multimodule_template/domain/README.md new file mode 100644 index 0000000..6b0c406 --- /dev/null +++ b/common/domain/src/main/java/tech/antee/compose_multimodule_template/domain/README.md @@ -0,0 +1 @@ +Write your Domain models in this module \ No newline at end of file diff --git a/common/multi-compose/src/main/java/tech/antee/compose_multimodule_template/multi_compose/CommonProvider.kt b/common/multi-compose/src/main/java/tech/antee/compose_multimodule_template/multi_compose/CommonProvider.kt deleted file mode 100644 index 0dbf1c6..0000000 --- a/common/multi-compose/src/main/java/tech/antee/compose_multimodule_template/multi_compose/CommonProvider.kt +++ /dev/null @@ -1,10 +0,0 @@ -package tech.antee.compose_multimodule_template.multi_compose - -import android.content.Context -import androidx.compose.runtime.compositionLocalOf - -interface CommonProvider { - val context: Context -} - -val LocalCommonProvider = compositionLocalOf { error("No common provider found!") } diff --git a/common/multi-compose/src/main/java/tech/antee/compose_multimodule_template/multi_compose/di/CommonComponent.kt b/common/multi-compose/src/main/java/tech/antee/compose_multimodule_template/multi_compose/di/CommonComponent.kt deleted file mode 100644 index dd3bdf4..0000000 --- a/common/multi-compose/src/main/java/tech/antee/compose_multimodule_template/multi_compose/di/CommonComponent.kt +++ /dev/null @@ -1,17 +0,0 @@ -package tech.antee.compose_multimodule_template.multi_compose.di - -import android.content.Context -import dagger.BindsInstance -import dagger.Component -import tech.antee.compose_multimodule_template.multi_compose.CommonProvider -import javax.inject.Singleton - -@Singleton -@Component -interface CommonComponent : CommonProvider { - - @Component.Factory - interface Factory { - fun create(@BindsInstance context: Context): CommonComponent - } -} diff --git a/common/multi-compose/src/main/java/tech/antee/compose_multimodule_template/multi_compose/di/FlowScope.kt b/common/multi-compose/src/main/java/tech/antee/compose_multimodule_template/multi_compose/di/FlowScope.kt deleted file mode 100644 index 62fb973..0000000 --- a/common/multi-compose/src/main/java/tech/antee/compose_multimodule_template/multi_compose/di/FlowScope.kt +++ /dev/null @@ -1,7 +0,0 @@ -package tech.antee.compose_multimodule_template.multi_compose.di - -import javax.inject.Scope - -@Scope -@Retention(AnnotationRetention.RUNTIME) -annotation class FlowScope diff --git a/common/ui/.gitignore b/common/ui/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/common/ui/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/common/ui/build.gradle.kts b/common/ui/build.gradle.kts new file mode 100644 index 0000000..a38c69b --- /dev/null +++ b/common/ui/build.gradle.kts @@ -0,0 +1,18 @@ +plugins { + id(Plugins.androidLibrary) + id(Plugins.androidBase) + id(Plugins.kotlinKapt) + id(Plugins.compose) +} + +dependencies { + projectImplementation(Modules.coreUtils) + + implementation(Deps.appCompat) + implementation(Deps.coreKtx) + + implementation(Deps.Compose.ui) + implementation(Deps.Compose.foundation) + implementation(Deps.Compose.viewModel) + implementation(Deps.Compose.navigation) +} diff --git a/common/ui/consumer-rules.pro b/common/ui/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/common/ui/proguard-rules.pro b/common/ui/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/common/ui/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/common/ui/src/main/AndroidManifest.xml b/common/ui/src/main/AndroidManifest.xml new file mode 100644 index 0000000..f92f704 --- /dev/null +++ b/common/ui/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/common/ui/src/main/java/tech/antee/compose_multimodule_template/ui/BaseViewModel.kt b/common/ui/src/main/java/tech/antee/compose_multimodule_template/ui/BaseViewModel.kt new file mode 100644 index 0000000..a160b9c --- /dev/null +++ b/common/ui/src/main/java/tech/antee/compose_multimodule_template/ui/BaseViewModel.kt @@ -0,0 +1,100 @@ +package tech.antee.compose_multimodule_template.ui + +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.Job +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.ChannelResult +import kotlinx.coroutines.flow.* +import tech.antee.compose_multimodule_template.utils.coroutines.ktx.launchSecurely +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext + + +abstract class BaseViewModel : ViewModel() { + + protected abstract val _uiState: MutableStateFlow + val uiState: StateFlow get() = _uiState + + private val _uiEvents = Channel(capacity = Channel.UNLIMITED) + val uiEvents: Flow = _uiEvents.receiveAsFlow() + + + protected abstract fun onAction(action: Action) + + protected open fun onLoading(inProgress: Boolean) {} + + protected open fun onError(t: Throwable) { + Log.e(javaClass.simpleName, "onError: ${t.message}", t) + } + + protected fun launchSafely( + context: CoroutineContext = EmptyCoroutineContext, + start: CoroutineStart = CoroutineStart.DEFAULT, + onLoading: (Boolean) -> Unit = ::onLoading, + onError: (Throwable) -> Unit = ::onError, + onFinally: () -> Unit = {}, + block: suspend CoroutineScope.() -> Unit + ): Job = launchSecurely( + scope = viewModelScope, + context = context, + start = start, + onLoading = onLoading, + onError = onError, + onFinally = onFinally, + block = block + ) + + + protected inline fun updateIfStateIs( + afterUpdate: (currentState: T) -> Unit = {}, + newState: (currentState: T) -> T + ) { + updateState( + afterUpdate = { state -> + if (state is T) afterUpdate(state) + } + ) { currentState -> + if (currentState is T) { + newState(currentState) + } else { + Log.w( + javaClass.simpleName, + "updateState: current state = $currentState, expected = ${T::class.qualifiedName}" + ) + currentState + } + } + } + + protected inline fun replaceIfStateIs( + crossinline afterUpdate: (currentState: New) -> Unit = {}, + newState: (currentState: Old) -> New + ) { + updateState( + afterUpdate = { state -> + if (state is New) afterUpdate(state) + } + ) { currentState -> + if (currentState is Old) { + newState(currentState) + } else { + Log.w( + javaClass.simpleName, + "replaceState: current state = $currentState, expected = ${Old::class.qualifiedName}" + ) + currentState + } + } + } + + protected inline fun updateState(afterUpdate: (State) -> Unit = {}, function: (State) -> State) { + _uiState.update(function) + afterUpdate(_uiState.value) + } + + protected fun emitEvent(event: Event): ChannelResult = _uiEvents.trySend(event) +} diff --git a/core/utils/.gitignore b/core/utils/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/core/utils/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/utils/build.gradle.kts b/core/utils/build.gradle.kts new file mode 100644 index 0000000..2895fae --- /dev/null +++ b/core/utils/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + id(Plugins.androidLibrary) + id(Plugins.androidBase) +} + +dependencies { + implementation(Deps.appCompat) + implementation(Deps.coreKtx) + implementation(Deps.Coroutines.kotlinCore) + implementation(Deps.inject) +} diff --git a/core/utils/consumer-rules.pro b/core/utils/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/core/utils/proguard-rules.pro b/core/utils/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/core/utils/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/core/utils/src/main/AndroidManifest.xml b/core/utils/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a20d788 --- /dev/null +++ b/core/utils/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/core/utils/src/main/java/tech/antee/compose_multimodule_template/utils/coroutines/ktx/CoroutineKtx.kt b/core/utils/src/main/java/tech/antee/compose_multimodule_template/utils/coroutines/ktx/CoroutineKtx.kt new file mode 100644 index 0000000..20ff3cd --- /dev/null +++ b/core/utils/src/main/java/tech/antee/compose_multimodule_template/utils/coroutines/ktx/CoroutineKtx.kt @@ -0,0 +1,27 @@ +package tech.antee.compose_multimodule_template.utils.coroutines.ktx + +import kotlinx.coroutines.* +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext + +fun launchSecurely( + scope: CoroutineScope, + context: CoroutineContext = EmptyCoroutineContext, + start: CoroutineStart = CoroutineStart.DEFAULT, + onLoading: ((Boolean) -> Unit)? = null, + onError: ((Throwable) -> Unit)? = null, + onFinally: (() -> Unit)? = null, + block: suspend CoroutineScope.() -> Unit +): Job = scope.launch(context, start) { + try { + onLoading?.invoke(true) + block() + } catch (e: Throwable) { + if (e is CancellationException) throw e + e.printStackTrace() + onError?.invoke(e) + } finally { + onLoading?.invoke(false) + onFinally?.invoke() + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 395159f..d871526 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,15 +1,22 @@ rootProject.name = "MyApplication" -include(":app") -include(":core:ui") -include(":core:strings") -include(":common:multi-compose") -include(":common:ui-components") - pluginManagement { repositories { gradlePluginPortal() google() mavenCentral() } -} \ No newline at end of file +} + +include(":app") +include(":core:ui") +include(":core:strings") +include(":core:utils") +include(":common:domain") +include(":common:data") +include(":common:data-remote") +include(":common:data-local") +include(":common:di") +include(":common:ui") +include(":common:ui-components") +include(":common:multi-compose")