diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b187eeb7..b6ce1b60 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -kotli = "0.5.2" +kotli = "0.5.3" kotlin = "1.9.23" kotlinxCoroutines = "1.8.0" logback = "1.5.6" diff --git a/processor/build.gradle b/processor/build.gradle index 936a8862..92eba059 100644 --- a/processor/build.gradle +++ b/processor/build.gradle @@ -1,4 +1,4 @@ apply from: "${project.rootDir}/gradle/kotlin/processor.gradle" group = 'com.kotlitecture.kotli' -version = '0.5.2' \ No newline at end of file +version = '0.6.0' \ No newline at end of file diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/MultiplatformComposeTemplateProcessor.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/MultiplatformComposeTemplateProcessor.kt index 87389b4a..a5651bdc 100644 --- a/processor/src/main/kotlin/kotli/template/multiplatform/compose/MultiplatformComposeTemplateProcessor.kt +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/MultiplatformComposeTemplateProcessor.kt @@ -182,9 +182,13 @@ object MultiplatformComposeTemplateProcessor : BaseTemplateProcessor() { ) renamePackage(state, "${Rules.CommonAppSrcDir}/androidMain/kotlin") renamePackage(state, "${Rules.CommonAppSrcDir}/commonMain/kotlin") + renamePackage(state, "${Rules.CommonAppSrcDir}/iosArm64Main/kotlin") renamePackage(state, "${Rules.CommonAppSrcDir}/iosMain/kotlin") + renamePackage(state, "${Rules.CommonAppSrcDir}/iosSimulatorArm64Main/kotlin") + renamePackage(state, "${Rules.CommonAppSrcDir}/iosX64Main/kotlin") renamePackage(state, "${Rules.CommonAppSrcDir}/jsMain/kotlin") renamePackage(state, "${Rules.CommonAppSrcDir}/jvmMain/kotlin") + renamePackage(state, "${Rules.CommonAppSrcDir}/mobileAndDesktopMain/kotlin") } private fun renamePackage(state: TemplateState, root: String) { diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/Rules.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/Rules.kt index 30984c3b..e3a5d09f 100644 --- a/processor/src/main/kotlin/kotli/template/multiplatform/compose/Rules.kt +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/Rules.kt @@ -23,7 +23,7 @@ object Rules { // sources const val IosAppDir = "iosApp" const val SrcAndroidMainDir = "*/src/androidMain" - const val SrcIosMainDir = "*/src/iosMain" + const val SrcIosMainDir = "*/src/ios*" const val SrcJsMainDir = "*/src/jsMain" const val SrcJvmMainDir = "*/src/jvmMain" const val SharedPresentationDir = "shared/presentation" @@ -33,12 +33,12 @@ object Rules { const val BackendDir = "backend" // kotlin + const val DIKt = "*/DI*.kt" const val CommonAppSrcDir = "app/src" const val SharedDesignSrcDir = "shared/design/src" const val CommonAppMainDir = "${CommonAppSrcDir}/commonMain" const val AppIconsProviderKt = "${SharedDesignSrcDir}/commonMain/kotlin/shared/design/icon/AppIconsProvider.kt" const val AppModuleKt = "${CommonAppMainDir}/kotlin/kotli/app/di/presentation/AppModule.kt" - const val AppDIKt = "${CommonAppMainDir}/kotlin/kotli/app/di/DI.kt" const val AppWebPackConfigDir = "app/webpack.config.d" const val AppSqlDelightConfigJs = "${AppWebPackConfigDir}/sqljs-config.js" const val AppPresentationDir = "${CommonAppMainDir}/kotlin/kotli/app/presentation" @@ -60,7 +60,7 @@ object Rules { const val AppNavigationModalProvider = "${AppNavigationDir}/ModalProvider.kt" const val AppNavigationPermanentProvider = "${AppNavigationDir}/PermanentProvider.kt" const val AppNavigationRailProvider = "${AppNavigationDir}/RailProvider.kt" - const val ShowcasesDir = "${AppPresentationDir}/showcases" + const val ShowcasesDir = "*/presentation/showcases" const val ShowcasesDataFlowDir = "${ShowcasesDir}/dataflow" const val ShowcasesUserFlowDir = "${ShowcasesDir}/userflow" const val ShowcasesHttpDir = "${ShowcasesDataFlowDir}/http" @@ -68,6 +68,7 @@ object Rules { const val ShowcasesPagingDir = "${ShowcasesDataFlowDir}/paging" const val ShowcasesKeyValueDir = "${ShowcasesDataFlowDir}/keyvalue" const val ShowcasesSqlDelightDir = "${ShowcasesDataFlowDir}/sqldelight" + const val ShowcasesRoomDir = "${ShowcasesDataFlowDir}/room" const val ShowcasesNavigationDir = "${ShowcasesUserFlowDir}/navigation" const val ShowcasesThemeDir = "${ShowcasesUserFlowDir}/theme" const val ShowcasesLoaderDir = "${ShowcasesUserFlowDir}/loader" @@ -82,11 +83,13 @@ object Rules { const val AnalyticsSource = "*/*AnalyticsSource*.kt" const val CacheSource = "*/*CacheSource*.kt" const val SqlDelightSource = "*/*SqlDelightSource*.kt" + const val SqlDelightDir = "*/sqldelight/*" + const val RoomSource = "*/*RoomSource*.kt" + const val RoomDir = "*/database/room/*" const val ConfigSource = "*/*ConfigSource*.kt" const val PagingSource = "*/*Paging*.kt" const val HttpSource = "*/*HttpSource*.kt" const val KeyValueSource = "*/*KeyValueSource*.kt" const val SettingsKeyValueSource = "*/*SettingsKeyValueSource*.kt" - const val AppSqlDelightDir = "*/sqldelight/*" } \ No newline at end of file diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/common/CommonKspProcessor.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/common/CommonKspProcessor.kt new file mode 100644 index 00000000..1751a199 --- /dev/null +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/common/CommonKspProcessor.kt @@ -0,0 +1,37 @@ +package kotli.template.multiplatform.compose.common + +import kotli.engine.BaseFeatureProcessor +import kotli.engine.TemplateState +import kotli.engine.template.VersionCatalogRules +import kotli.engine.template.rule.CleanupMarkedBlock +import kotli.engine.template.rule.CleanupMarkedLine +import kotli.engine.template.rule.RemoveMarkedBlock +import kotli.engine.template.rule.RemoveMarkedLine +import kotli.template.multiplatform.compose.Rules + +object CommonKspProcessor : BaseFeatureProcessor() { + + override fun getId(): String = "common.ksp" + override fun isInternal(): Boolean = true + + override fun doApply(state: TemplateState) { + state.onApplyRules( + Rules.BuildGradleApp, + CleanupMarkedLine("{common.ksp}"), + CleanupMarkedBlock("{common.ksp.config}") + ) + } + + override fun doRemove(state: TemplateState) { + state.onApplyRules( + VersionCatalogRules( + RemoveMarkedLine("ksp") + ) + ) + state.onApplyRules( + Rules.BuildGradleApp, + RemoveMarkedLine("{common.ksp}"), + RemoveMarkedBlock("{common.ksp.config}") + ) + } +} \ No newline at end of file diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/common/CommonProvider.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/common/CommonProvider.kt index c535bc6d..9b3e163c 100644 --- a/processor/src/main/kotlin/kotli/template/multiplatform/compose/common/CommonProvider.kt +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/common/CommonProvider.kt @@ -11,7 +11,8 @@ object CommonProvider : BaseFeatureProvider() { override fun getId(): String = "common" override fun createProcessors(): List = listOf( CommonKtorProcessor, - CommonStatelyProcessor + CommonStatelyProcessor, + CommonKspProcessor ) } \ No newline at end of file diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/analytics/facade/FacadeAnalyticsProcessor.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/analytics/facade/FacadeAnalyticsProcessor.kt index a1d31322..44bed228 100644 --- a/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/analytics/facade/FacadeAnalyticsProcessor.kt +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/analytics/facade/FacadeAnalyticsProcessor.kt @@ -23,7 +23,7 @@ object FacadeAnalyticsProcessor : BaseFeatureProcessor() { RemoveFile() ) state.onApplyRules( - Rules.AppDIKt, + Rules.DIKt, RemoveMarkedLine("AnalyticsSource") ) } diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/cache/basic/BasicCacheProcessor.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/cache/basic/BasicCacheProcessor.kt index 0aab4ba4..5285e277 100644 --- a/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/cache/basic/BasicCacheProcessor.kt +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/cache/basic/BasicCacheProcessor.kt @@ -11,7 +11,7 @@ import kotli.engine.template.rule.RemoveMarkedLine import kotli.template.multiplatform.compose.Rules import kotli.template.multiplatform.compose.Tags import kotli.template.multiplatform.compose.common.CommonStatelyProcessor -import kotli.template.multiplatform.compose.showcases.datasource.cache.CacheShowcasesProcessor +import kotli.template.multiplatform.compose.showcases.dataflow.cache.CacheShowcasesProcessor import kotlin.time.Duration.Companion.hours object BasicCacheProcessor : BaseFeatureProcessor() { @@ -39,7 +39,7 @@ object BasicCacheProcessor : BaseFeatureProcessor() { RemoveFile() ) state.onApplyRules( - Rules.AppDIKt, + Rules.DIKt, RemoveMarkedLine("CacheSource") ) state.onApplyRules( diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/common/CommonDataFlowProcessor.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/common/CommonDataFlowProcessor.kt index 04edb947..044b354a 100644 --- a/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/common/CommonDataFlowProcessor.kt +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/common/CommonDataFlowProcessor.kt @@ -2,6 +2,7 @@ package kotli.template.multiplatform.compose.dataflow.common import kotli.engine.BaseFeatureProcessor import kotli.engine.TemplateState +import kotli.engine.template.VersionCatalogRules import kotli.engine.template.rule.RemoveFile import kotli.engine.template.rule.RemoveMarkedLine import kotli.template.multiplatform.compose.Rules @@ -26,6 +27,11 @@ object CommonDataFlowProcessor : BaseFeatureProcessor() { Rules.BuildGradle, RemoveMarkedLine("shared.data") ) + state.onApplyRules( + VersionCatalogRules( + RemoveMarkedLine("kotlin-test =") + ) + ) } } \ No newline at end of file diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/config/facade/FacadeConfigProcessor.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/config/facade/FacadeConfigProcessor.kt index 17d51b8a..2b548cf1 100644 --- a/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/config/facade/FacadeConfigProcessor.kt +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/config/facade/FacadeConfigProcessor.kt @@ -23,7 +23,7 @@ object FacadeConfigProcessor : BaseFeatureProcessor() { RemoveFile() ) state.onApplyRules( - Rules.AppDIKt, + Rules.DIKt, RemoveMarkedLine("ConfigSource") ) } diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/database/DatabaseProvider.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/database/DatabaseProvider.kt index cdf2b767..5caea3bd 100644 --- a/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/database/DatabaseProvider.kt +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/database/DatabaseProvider.kt @@ -2,6 +2,7 @@ package kotli.template.multiplatform.compose.dataflow.database import kotli.engine.FeatureProcessor import kotli.template.multiplatform.compose.dataflow.BaseDataFlowProvider +import kotli.template.multiplatform.compose.dataflow.database.room.RoomProcessor import kotli.template.multiplatform.compose.dataflow.database.sqldelight.SqlDelightProcessor object DatabaseProvider : BaseDataFlowProvider() { @@ -10,7 +11,10 @@ object DatabaseProvider : BaseDataFlowProvider() { override fun isMultiple(): Boolean = false override fun createProcessors(): List = listOf( - SqlDelightProcessor + SqlDelightProcessor, + RoomProcessor, + SqliteProcessor, + SqliteLinkerProcessor ) } \ No newline at end of file diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/database/SqliteLinkerProcessor.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/database/SqliteLinkerProcessor.kt new file mode 100644 index 00000000..b1f00cad --- /dev/null +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/database/SqliteLinkerProcessor.kt @@ -0,0 +1,30 @@ +package kotli.template.multiplatform.compose.dataflow.database + +import kotli.engine.BaseFeatureProcessor +import kotli.engine.TemplateState +import kotli.engine.template.rule.CleanupMarkedLine +import kotli.engine.template.rule.RemoveMarkedLine +import kotli.template.multiplatform.compose.Rules + +object SqliteLinkerProcessor : BaseFeatureProcessor() { + + const val ID = "dataflow.database.sqlite-linker" + override fun isInternal(): Boolean = true + + override fun getId(): String = ID + + override fun doApply(state: TemplateState) { + state.onApplyRules( + Rules.BuildGradleApp, + CleanupMarkedLine("{dataflow.database.sqlite-linker}") + ) + } + + override fun doRemove(state: TemplateState) { + state.onApplyRules( + Rules.BuildGradleApp, + RemoveMarkedLine("{dataflow.database.sqlite-linker}") + ) + } + +} \ No newline at end of file diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/database/SqliteProcessor.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/database/SqliteProcessor.kt new file mode 100644 index 00000000..dfe1090a --- /dev/null +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/database/SqliteProcessor.kt @@ -0,0 +1,40 @@ +package kotli.template.multiplatform.compose.dataflow.database + +import kotli.engine.BaseFeatureProcessor +import kotli.engine.FeatureProcessor +import kotli.engine.TemplateState +import kotli.engine.template.VersionCatalogRules +import kotli.engine.template.rule.CleanupMarkedLine +import kotli.engine.template.rule.RemoveMarkedLine +import kotli.template.multiplatform.compose.Rules + +object SqliteProcessor : BaseFeatureProcessor() { + + const val ID = "dataflow.database.sqlite" + override fun isInternal(): Boolean = true + override fun dependencies(): List> = listOf( + SqliteLinkerProcessor::class.java + ) + + override fun getId(): String = ID + + override fun doApply(state: TemplateState) { + state.onApplyRules( + Rules.BuildGradleApp, + CleanupMarkedLine("{dataflow.database.sqlite}") + ) + } + + override fun doRemove(state: TemplateState) { + state.onApplyRules( + Rules.BuildGradleApp, + RemoveMarkedLine("{dataflow.database.sqlite}") + ) + state.onApplyRules( + VersionCatalogRules( + RemoveMarkedLine("sqlite-bundled") + ) + ) + } + +} \ No newline at end of file diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/database/room/RoomProcessor.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/database/room/RoomProcessor.kt new file mode 100644 index 00000000..1293baa3 --- /dev/null +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/database/room/RoomProcessor.kt @@ -0,0 +1,101 @@ +package kotli.template.multiplatform.compose.dataflow.database.room + +import kotli.engine.BaseFeatureProcessor +import kotli.engine.FeatureProcessor +import kotli.engine.FeatureTag +import kotli.engine.TemplateState +import kotli.engine.template.VersionCatalogRules +import kotli.engine.template.rule.CleanupMarkedBlock +import kotli.engine.template.rule.CleanupMarkedLine +import kotli.engine.template.rule.RemoveFile +import kotli.engine.template.rule.RemoveMarkedBlock +import kotli.engine.template.rule.RemoveMarkedLine +import kotli.template.multiplatform.compose.Rules +import kotli.template.multiplatform.compose.Tags +import kotli.template.multiplatform.compose.common.CommonKspProcessor +import kotli.template.multiplatform.compose.dataflow.database.SqliteProcessor +import kotli.template.multiplatform.compose.platform.client.MobileAndDesktopProcessor +import kotli.template.multiplatform.compose.platform.client.android.AndroidPlatformProcessor +import kotli.template.multiplatform.compose.platform.client.ios.IOSPlatformProcessor +import kotli.template.multiplatform.compose.platform.client.jvm.JvmPlatformProcessor +import kotlin.time.Duration.Companion.hours + +object RoomProcessor : BaseFeatureProcessor() { + + const val ID = "dataflow.database.room" + + override fun getId(): String = ID + override fun getTags(): List = Tags.MobileAndDesktop + override fun getIntegrationEstimate(state: TemplateState): Long = 4.hours.inWholeMilliseconds + override fun getWebUrl(state: TemplateState): String = + "https://developer.android.com/kotlin/multiplatform/room" + + override fun getIntegrationUrl(state: TemplateState): String = + "https://developer.android.com/kotlin/multiplatform/room#defining-database" + + override fun canApply(state: TemplateState): Boolean { + return listOfNotNull( + state.getFeature(AndroidPlatformProcessor.ID), + state.getFeature(IOSPlatformProcessor.ID), + state.getFeature(JvmPlatformProcessor.ID) + ).isNotEmpty() + } + + override fun dependencies(): List> = listOf( + MobileAndDesktopProcessor::class.java, + CommonKspProcessor::class.java, + SqliteProcessor::class.java, + ) + + override fun doApply(state: TemplateState) { + state.onApplyRules( + Rules.BuildGradleApp, + CleanupMarkedBlock("{dataflow.database.room.config}"), + CleanupMarkedLine("{dataflow.database.room}") + ) + } + + override fun doRemove(state: TemplateState) { + state.onApplyRules( + Rules.BuildGradleApp, + RemoveMarkedBlock("{dataflow.database.room.config}"), + RemoveMarkedLine("{dataflow.database.room}") + ) + state.onApplyRules( + VersionCatalogRules( + RemoveMarkedLine("androidx-room") + ) + ) + state.onApplyRules( + Rules.RoomSource, + RemoveFile() + ) + state.onApplyRules( + Rules.RoomDir, + RemoveFile() + ) + state.onApplyRules( + "app/schemas", + RemoveFile() + ) + state.onApplyRules( + Rules.DIKt, + RemoveMarkedLine("RoomSource") + ) + + // showcases + state.onApplyRules( + Rules.ShowcasesKt, + RemoveMarkedLine("Room") + ) + state.onApplyRules( + Rules.ShowcasesRoomDir, + RemoveFile() + ) + state.onApplyRules( + Rules.AppModuleKt, + RemoveMarkedLine("createRoom") + ) + } + +} \ No newline at end of file diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/database/sqldelight/SqlDelightProcessor.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/database/sqldelight/SqlDelightProcessor.kt index 8c9fceaa..408b7814 100644 --- a/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/database/sqldelight/SqlDelightProcessor.kt +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/database/sqldelight/SqlDelightProcessor.kt @@ -14,7 +14,10 @@ import kotli.engine.template.rule.RenamePackage import kotli.template.multiplatform.compose.Rules import kotli.template.multiplatform.compose.Tags import kotli.template.multiplatform.compose.common.CommonStatelyProcessor +import kotli.template.multiplatform.compose.dataflow.database.SqliteLinkerProcessor +import kotli.template.multiplatform.compose.dataflow.database.SqliteProcessor import kotli.template.multiplatform.compose.dataflow.paging.cashapp.CashAppPagingProcessor +import kotli.template.multiplatform.compose.platform.client.MobileAndDesktopProcessor import kotlin.time.Duration.Companion.hours object SqlDelightProcessor : BaseFeatureProcessor() { @@ -29,7 +32,9 @@ object SqlDelightProcessor : BaseFeatureProcessor() { override fun getIntegrationEstimate(state: TemplateState): Long = 4.hours.inWholeMilliseconds override fun dependencies(): List> = listOf( - CommonStatelyProcessor::class.java + MobileAndDesktopProcessor::class.java, + CommonStatelyProcessor::class.java, + SqliteProcessor::class.java ) override fun doApply(state: TemplateState) { @@ -59,7 +64,7 @@ object SqlDelightProcessor : BaseFeatureProcessor() { RemoveFile() ) state.onApplyRules( - Rules.AppSqlDelightDir, + Rules.SqlDelightDir, RemoveFile() ) state.onApplyRules( @@ -75,7 +80,7 @@ object SqlDelightProcessor : BaseFeatureProcessor() { RemoveFile() ) state.onApplyRules( - Rules.AppDIKt, + Rules.DIKt, RemoveMarkedLine("SqlDelightSource") ) state.onApplyRules( diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/http/HttpProvider.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/http/HttpProvider.kt index e4315da2..1a038df8 100644 --- a/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/http/HttpProvider.kt +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/http/HttpProvider.kt @@ -4,7 +4,7 @@ import kotli.engine.FeatureProcessor import kotli.template.multiplatform.compose.dataflow.BaseDataFlowProvider import kotli.template.multiplatform.compose.dataflow.config.facade.FacadeConfigProcessor import kotli.template.multiplatform.compose.dataflow.http.ktor.KtorHttpProcessor -import kotli.template.multiplatform.compose.showcases.datasource.http.HttpShowcasesProcessor +import kotli.template.multiplatform.compose.showcases.dataflow.http.HttpShowcasesProcessor object HttpProvider : BaseDataFlowProvider() { diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/http/ktor/KtorHttpProcessor.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/http/ktor/KtorHttpProcessor.kt index 4bcda5a4..7aea6a46 100644 --- a/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/http/ktor/KtorHttpProcessor.kt +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/http/ktor/KtorHttpProcessor.kt @@ -31,7 +31,7 @@ object KtorHttpProcessor : BaseFeatureProcessor() { RemoveFile() ) state.onApplyRules( - Rules.AppDIKt, + Rules.DIKt, RemoveMarkedLine("HttpSource") ) state.onApplyRules( diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/keyvalue/common/CommonKeyValueProcessor.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/keyvalue/common/CommonKeyValueProcessor.kt index e8dad9d7..fe5e0668 100644 --- a/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/keyvalue/common/CommonKeyValueProcessor.kt +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/keyvalue/common/CommonKeyValueProcessor.kt @@ -6,7 +6,7 @@ import kotli.engine.TemplateState import kotli.engine.template.rule.RemoveFile import kotli.engine.template.rule.RemoveMarkedLine import kotli.template.multiplatform.compose.Rules -import kotli.template.multiplatform.compose.showcases.datasource.keyvalue.KeyValueShowcasesProcessor +import kotli.template.multiplatform.compose.showcases.dataflow.keyvalue.KeyValueShowcasesProcessor import kotlin.time.Duration.Companion.minutes object CommonKeyValueProcessor : BaseFeatureProcessor() { @@ -27,7 +27,7 @@ object CommonKeyValueProcessor : BaseFeatureProcessor() { RemoveFile() ) state.onApplyRules( - Rules.AppDIKt, + Rules.DIKt, RemoveMarkedLine("KeyValueSource") ) } diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/paging/PagingProvider.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/paging/PagingProvider.kt index f44442da..8c2da5cd 100644 --- a/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/paging/PagingProvider.kt +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/paging/PagingProvider.kt @@ -4,7 +4,7 @@ import kotli.engine.FeatureProcessor import kotli.template.multiplatform.compose.dataflow.BaseDataFlowProvider import kotli.template.multiplatform.compose.dataflow.config.facade.FacadeConfigProcessor import kotli.template.multiplatform.compose.dataflow.paging.cashapp.CashAppPagingProcessor -import kotli.template.multiplatform.compose.showcases.datasource.paging.PagingShowcasesProcessor +import kotli.template.multiplatform.compose.showcases.dataflow.paging.PagingShowcasesProcessor object PagingProvider : BaseDataFlowProvider() { diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/paging/cashapp/CashAppPagingProcessor.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/paging/cashapp/CashAppPagingProcessor.kt index 79df800c..0ecec6c3 100644 --- a/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/paging/cashapp/CashAppPagingProcessor.kt +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/dataflow/paging/cashapp/CashAppPagingProcessor.kt @@ -23,7 +23,7 @@ object CashAppPagingProcessor : BaseFeatureProcessor() { override fun doRemove(state: TemplateState) { state.onApplyRules( VersionCatalogRules( - RemoveMarkedLine("paging") + RemoveMarkedLine("cashapp") ) ) state.onApplyRules( @@ -31,7 +31,7 @@ object CashAppPagingProcessor : BaseFeatureProcessor() { RemoveFile() ) state.onApplyRules( - Rules.AppDIKt, + Rules.DIKt, RemoveMarkedLine("PagingSource") ) state.onApplyRules( diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/essentials/navigation/NavigationProvider.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/essentials/navigation/NavigationProvider.kt index 7f299330..6d3f5787 100644 --- a/processor/src/main/kotlin/kotli/template/multiplatform/compose/essentials/navigation/NavigationProvider.kt +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/essentials/navigation/NavigationProvider.kt @@ -3,7 +3,7 @@ package kotli.template.multiplatform.compose.essentials.navigation import kotli.engine.FeatureProcessor import kotli.template.multiplatform.compose.essentials.EssentialsProvider import kotli.template.multiplatform.compose.essentials.navigation.jetpack.JetpackProcessor -import kotli.template.multiplatform.compose.showcases.navigation.NavigationShowcasesProcessor +import kotli.template.multiplatform.compose.showcases.userflow.navigation.NavigationShowcasesProcessor object NavigationProvider : EssentialsProvider() { diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/platform/client/ClientPlatformProvider.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/platform/client/ClientPlatformProvider.kt index 842bf233..65280609 100644 --- a/processor/src/main/kotlin/kotli/template/multiplatform/compose/platform/client/ClientPlatformProvider.kt +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/platform/client/ClientPlatformProvider.kt @@ -20,7 +20,8 @@ object ClientPlatformProvider : BaseFeatureProvider() { IOSPlatformProcessor, AndroidPlatformProcessor, JvmPlatformProcessor, - JsPlatformProcessor + JsPlatformProcessor, + MobileAndDesktopProcessor ) } \ No newline at end of file diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/platform/client/MobileAndDesktopProcessor.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/platform/client/MobileAndDesktopProcessor.kt new file mode 100644 index 00000000..663a6494 --- /dev/null +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/platform/client/MobileAndDesktopProcessor.kt @@ -0,0 +1,32 @@ +package kotli.template.multiplatform.compose.platform.client + +import kotli.engine.TemplateState +import kotli.engine.template.rule.CleanupMarkedBlock +import kotli.engine.template.rule.RemoveMarkedBlock +import kotli.engine.template.rule.RemoveMarkedLine +import kotli.template.multiplatform.compose.Rules +import kotli.template.multiplatform.compose.platform.PlatformProcessor + +object MobileAndDesktopProcessor : PlatformProcessor() { + + const val ID = "platform.mobile_and_desktop" + override fun isInternal(): Boolean = true + + override fun getId(): String = ID + + override fun doApply(state: TemplateState) { + state.onApplyRules( + Rules.BuildGradleApp, + CleanupMarkedBlock("{platform.mobile_and_desktop.dependencies}") + ) + } + + override fun doRemove(state: TemplateState) { + state.onApplyRules( + Rules.BuildGradleApp, + RemoveMarkedBlock("{platform.mobile_and_desktop.dependencies}"), + RemoveMarkedLine("mobileAndDesktopMain") + ) + } + +} \ No newline at end of file diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/platform/client/android/AndroidPlatformProcessor.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/platform/client/android/AndroidPlatformProcessor.kt index 6f508432..f3244fbd 100644 --- a/processor/src/main/kotlin/kotli/template/multiplatform/compose/platform/client/android/AndroidPlatformProcessor.kt +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/platform/client/android/AndroidPlatformProcessor.kt @@ -37,6 +37,7 @@ object AndroidPlatformProcessor : PlatformProcessor() { RemoveMarkedLine("androidx-appcompat"), RemoveMarkedLine("androidx-splashscreen"), RemoveMarkedLine("compose-android"), + RemoveMarkedLine("coroutines-android"), ) ) } diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/platform/client/jvm/JvmPlatformProcessor.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/platform/client/jvm/JvmPlatformProcessor.kt index 7c5fd6e8..5fcf9e5d 100644 --- a/processor/src/main/kotlin/kotli/template/multiplatform/compose/platform/client/jvm/JvmPlatformProcessor.kt +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/platform/client/jvm/JvmPlatformProcessor.kt @@ -3,7 +3,9 @@ package kotli.template.multiplatform.compose.platform.client.jvm import kotli.engine.FeatureTag import kotli.engine.TemplateState import kotli.engine.model.FeatureTags +import kotli.engine.template.VersionCatalogRules import kotli.engine.template.rule.RemoveFile +import kotli.engine.template.rule.RemoveMarkedLine import kotli.template.multiplatform.compose.Rules import kotli.template.multiplatform.compose.platform.PlatformProcessor @@ -20,6 +22,11 @@ object JvmPlatformProcessor : PlatformProcessor() { Rules.SrcJvmMainDir, RemoveFile() ) + state.onApplyRules( + VersionCatalogRules( + RemoveMarkedLine("coroutines-swing") + ) + ) } } \ No newline at end of file diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/platform/server/ktor/KtorBackendProcessor.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/platform/server/ktor/KtorBackendProcessor.kt index 2bb2c2c7..ef6e6147 100644 --- a/processor/src/main/kotlin/kotli/template/multiplatform/compose/platform/server/ktor/KtorBackendProcessor.kt +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/platform/server/ktor/KtorBackendProcessor.kt @@ -47,7 +47,8 @@ object KtorBackendProcessor : PlatformProcessor() { RemoveMarkedLine("logback"), RemoveMarkedLine("server"), RemoveMarkedLine("kotlin.jvm"), - RemoveMarkedLine("ktor.plugin") + RemoveMarkedLine("ktor.plugin"), + RemoveMarkedLine("kotlin-test-junit") ) ) } diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/ShowcasesProvider.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/ShowcasesProvider.kt index 89177bf7..3955cae4 100644 --- a/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/ShowcasesProvider.kt +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/ShowcasesProvider.kt @@ -4,16 +4,16 @@ import kotli.engine.BaseFeatureProvider import kotli.engine.FeatureProcessor import kotli.engine.FeatureType import kotli.engine.model.FeatureTypes -import kotli.template.multiplatform.compose.showcases.datasource.cache.CacheShowcasesProcessor -import kotli.template.multiplatform.compose.showcases.datasource.http.HttpShowcasesProcessor -import kotli.template.multiplatform.compose.showcases.datasource.keyvalue.KeyValueShowcasesProcessor -import kotli.template.multiplatform.compose.showcases.datasource.paging.PagingShowcasesProcessor -import kotli.template.multiplatform.compose.showcases.feature.loader.data.DataLoaderShowcasesProcessor -import kotli.template.multiplatform.compose.showcases.feature.passcode.PasscodeShowcasesProcessor -import kotli.template.multiplatform.compose.showcases.feature.theme.ThemeShowcasesProcessor -import kotli.template.multiplatform.compose.showcases.feature.theme.change.ChangeThemeShowcasesProcessor -import kotli.template.multiplatform.compose.showcases.feature.theme.toggle.ToggleThemeShowcasesProcessor -import kotli.template.multiplatform.compose.showcases.navigation.NavigationShowcasesProcessor +import kotli.template.multiplatform.compose.showcases.dataflow.cache.CacheShowcasesProcessor +import kotli.template.multiplatform.compose.showcases.dataflow.http.HttpShowcasesProcessor +import kotli.template.multiplatform.compose.showcases.dataflow.keyvalue.KeyValueShowcasesProcessor +import kotli.template.multiplatform.compose.showcases.dataflow.paging.PagingShowcasesProcessor +import kotli.template.multiplatform.compose.showcases.userflow.loader.data.DataLoaderShowcasesProcessor +import kotli.template.multiplatform.compose.showcases.userflow.passcode.PasscodeShowcasesProcessor +import kotli.template.multiplatform.compose.showcases.userflow.theme.ThemeShowcasesProcessor +import kotli.template.multiplatform.compose.showcases.userflow.theme.change.ChangeThemeShowcasesProcessor +import kotli.template.multiplatform.compose.showcases.userflow.theme.toggle.ToggleThemeShowcasesProcessor +import kotli.template.multiplatform.compose.showcases.userflow.navigation.NavigationShowcasesProcessor object ShowcasesProvider : BaseFeatureProvider() { diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/datasource/cache/CacheShowcasesProcessor.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/dataflow/cache/CacheShowcasesProcessor.kt similarity index 91% rename from processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/datasource/cache/CacheShowcasesProcessor.kt rename to processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/dataflow/cache/CacheShowcasesProcessor.kt index 4109045b..78cfab40 100644 --- a/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/datasource/cache/CacheShowcasesProcessor.kt +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/dataflow/cache/CacheShowcasesProcessor.kt @@ -1,4 +1,4 @@ -package kotli.template.multiplatform.compose.showcases.datasource.cache +package kotli.template.multiplatform.compose.showcases.dataflow.cache import kotli.engine.BaseFeatureProcessor import kotli.engine.TemplateState diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/datasource/http/HttpShowcasesProcessor.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/dataflow/http/HttpShowcasesProcessor.kt similarity index 91% rename from processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/datasource/http/HttpShowcasesProcessor.kt rename to processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/dataflow/http/HttpShowcasesProcessor.kt index af5dbc2f..ebf60960 100644 --- a/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/datasource/http/HttpShowcasesProcessor.kt +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/dataflow/http/HttpShowcasesProcessor.kt @@ -1,4 +1,4 @@ -package kotli.template.multiplatform.compose.showcases.datasource.http +package kotli.template.multiplatform.compose.showcases.dataflow.http import kotli.engine.BaseFeatureProcessor import kotli.engine.TemplateState diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/datasource/keyvalue/KeyValueShowcasesProcessor.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/dataflow/keyvalue/KeyValueShowcasesProcessor.kt similarity index 91% rename from processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/datasource/keyvalue/KeyValueShowcasesProcessor.kt rename to processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/dataflow/keyvalue/KeyValueShowcasesProcessor.kt index b5f1b548..3b06cd7f 100644 --- a/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/datasource/keyvalue/KeyValueShowcasesProcessor.kt +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/dataflow/keyvalue/KeyValueShowcasesProcessor.kt @@ -1,4 +1,4 @@ -package kotli.template.multiplatform.compose.showcases.datasource.keyvalue +package kotli.template.multiplatform.compose.showcases.dataflow.keyvalue import kotli.engine.BaseFeatureProcessor import kotli.engine.TemplateState diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/datasource/paging/PagingShowcasesProcessor.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/dataflow/paging/PagingShowcasesProcessor.kt similarity index 91% rename from processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/datasource/paging/PagingShowcasesProcessor.kt rename to processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/dataflow/paging/PagingShowcasesProcessor.kt index 402ce7b3..dbba221f 100644 --- a/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/datasource/paging/PagingShowcasesProcessor.kt +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/dataflow/paging/PagingShowcasesProcessor.kt @@ -1,4 +1,4 @@ -package kotli.template.multiplatform.compose.showcases.datasource.paging +package kotli.template.multiplatform.compose.showcases.dataflow.paging import kotli.engine.BaseFeatureProcessor import kotli.engine.TemplateState diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/feature/loader/data/DataLoaderShowcasesProcessor.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/userflow/loader/data/DataLoaderShowcasesProcessor.kt similarity index 91% rename from processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/feature/loader/data/DataLoaderShowcasesProcessor.kt rename to processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/userflow/loader/data/DataLoaderShowcasesProcessor.kt index 725a53e6..4c7ccb00 100644 --- a/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/feature/loader/data/DataLoaderShowcasesProcessor.kt +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/userflow/loader/data/DataLoaderShowcasesProcessor.kt @@ -1,4 +1,4 @@ -package kotli.template.multiplatform.compose.showcases.feature.loader.data +package kotli.template.multiplatform.compose.showcases.userflow.loader.data import kotli.engine.BaseFeatureProcessor import kotli.engine.TemplateState diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/navigation/NavigationShowcasesProcessor.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/userflow/navigation/NavigationShowcasesProcessor.kt similarity index 91% rename from processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/navigation/NavigationShowcasesProcessor.kt rename to processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/userflow/navigation/NavigationShowcasesProcessor.kt index fae23965..0c411420 100644 --- a/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/navigation/NavigationShowcasesProcessor.kt +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/userflow/navigation/NavigationShowcasesProcessor.kt @@ -1,4 +1,4 @@ -package kotli.template.multiplatform.compose.showcases.navigation +package kotli.template.multiplatform.compose.showcases.userflow.navigation import kotli.engine.BaseFeatureProcessor import kotli.engine.TemplateState diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/feature/passcode/PasscodeShowcasesProcessor.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/userflow/passcode/PasscodeShowcasesProcessor.kt similarity index 90% rename from processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/feature/passcode/PasscodeShowcasesProcessor.kt rename to processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/userflow/passcode/PasscodeShowcasesProcessor.kt index d7969996..dad1c03f 100644 --- a/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/feature/passcode/PasscodeShowcasesProcessor.kt +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/userflow/passcode/PasscodeShowcasesProcessor.kt @@ -1,4 +1,4 @@ -package kotli.template.multiplatform.compose.showcases.feature.passcode +package kotli.template.multiplatform.compose.showcases.userflow.passcode import kotli.engine.BaseFeatureProcessor import kotli.engine.TemplateState diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/feature/theme/ThemeShowcasesProcessor.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/userflow/theme/ThemeShowcasesProcessor.kt similarity index 88% rename from processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/feature/theme/ThemeShowcasesProcessor.kt rename to processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/userflow/theme/ThemeShowcasesProcessor.kt index 3a3b0187..cd7b27c7 100644 --- a/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/feature/theme/ThemeShowcasesProcessor.kt +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/userflow/theme/ThemeShowcasesProcessor.kt @@ -1,4 +1,4 @@ -package kotli.template.multiplatform.compose.showcases.feature.theme +package kotli.template.multiplatform.compose.showcases.userflow.theme import kotli.engine.BaseFeatureProcessor import kotli.engine.TemplateState diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/feature/theme/change/ChangeThemeShowcasesProcessor.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/userflow/theme/change/ChangeThemeShowcasesProcessor.kt similarity index 86% rename from processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/feature/theme/change/ChangeThemeShowcasesProcessor.kt rename to processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/userflow/theme/change/ChangeThemeShowcasesProcessor.kt index 9e201617..7b848ba5 100644 --- a/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/feature/theme/change/ChangeThemeShowcasesProcessor.kt +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/userflow/theme/change/ChangeThemeShowcasesProcessor.kt @@ -1,4 +1,4 @@ -package kotli.template.multiplatform.compose.showcases.feature.theme.change +package kotli.template.multiplatform.compose.showcases.userflow.theme.change import kotli.engine.BaseFeatureProcessor import kotli.engine.FeatureProcessor @@ -6,7 +6,7 @@ import kotli.engine.TemplateState import kotli.engine.template.rule.RemoveFile import kotli.engine.template.rule.RemoveMarkedLine import kotli.template.multiplatform.compose.Rules -import kotli.template.multiplatform.compose.showcases.feature.theme.ThemeShowcasesProcessor +import kotli.template.multiplatform.compose.showcases.userflow.theme.ThemeShowcasesProcessor object ChangeThemeShowcasesProcessor : BaseFeatureProcessor() { diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/feature/theme/toggle/ToggleThemeShowcasesProcessor.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/userflow/theme/toggle/ToggleThemeShowcasesProcessor.kt similarity index 85% rename from processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/feature/theme/toggle/ToggleThemeShowcasesProcessor.kt rename to processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/userflow/theme/toggle/ToggleThemeShowcasesProcessor.kt index ff492a3c..36fbbe8d 100644 --- a/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/feature/theme/toggle/ToggleThemeShowcasesProcessor.kt +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/showcases/userflow/theme/toggle/ToggleThemeShowcasesProcessor.kt @@ -1,4 +1,4 @@ -package kotli.template.multiplatform.compose.showcases.feature.theme.toggle +package kotli.template.multiplatform.compose.showcases.userflow.theme.toggle import kotli.engine.BaseFeatureProcessor import kotli.engine.FeatureProcessor @@ -6,7 +6,7 @@ import kotli.engine.TemplateState import kotli.engine.template.rule.RemoveFile import kotli.engine.template.rule.RemoveMarkedLine import kotli.template.multiplatform.compose.Rules -import kotli.template.multiplatform.compose.showcases.feature.theme.ThemeShowcasesProcessor +import kotli.template.multiplatform.compose.showcases.userflow.theme.ThemeShowcasesProcessor object ToggleThemeShowcasesProcessor : BaseFeatureProcessor() { diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/userflow/loader/data/DataLoaderProcessor.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/userflow/loader/data/DataLoaderProcessor.kt index 629af8d8..902e35e8 100644 --- a/processor/src/main/kotlin/kotli/template/multiplatform/compose/userflow/loader/data/DataLoaderProcessor.kt +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/userflow/loader/data/DataLoaderProcessor.kt @@ -9,7 +9,7 @@ import kotli.engine.template.rule.RemoveMarkedLine import kotli.template.multiplatform.compose.Rules import kotli.template.multiplatform.compose.Tags import kotli.template.multiplatform.compose.dataflow.config.facade.FacadeConfigProcessor -import kotli.template.multiplatform.compose.showcases.feature.loader.data.DataLoaderShowcasesProcessor +import kotli.template.multiplatform.compose.showcases.userflow.loader.data.DataLoaderShowcasesProcessor import kotlin.time.Duration.Companion.hours object DataLoaderProcessor : BaseFeatureProcessor() { diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/userflow/navigation/NavigationBarProcessor.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/userflow/navigation/NavigationBarProcessor.kt index 5097dff5..b4d2440a 100644 --- a/processor/src/main/kotlin/kotli/template/multiplatform/compose/userflow/navigation/NavigationBarProcessor.kt +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/userflow/navigation/NavigationBarProcessor.kt @@ -24,7 +24,7 @@ object NavigationBarProcessor : BaseFeatureProcessor() { override fun doRemove(state: TemplateState) { state.onApplyRules( - Rules.AppDIKt, + Rules.DIKt, RemoveMarkedLine("navigationBarModule") ) state.onApplyRules( diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/userflow/theme/change/ChangeThemeProcessor.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/userflow/theme/change/ChangeThemeProcessor.kt index e3fe42e6..096d5d34 100644 --- a/processor/src/main/kotlin/kotli/template/multiplatform/compose/userflow/theme/change/ChangeThemeProcessor.kt +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/userflow/theme/change/ChangeThemeProcessor.kt @@ -8,7 +8,7 @@ import kotli.engine.template.rule.RemoveFile import kotli.engine.template.rule.RemoveMarkedLine import kotli.template.multiplatform.compose.Rules import kotli.template.multiplatform.compose.Tags -import kotli.template.multiplatform.compose.showcases.feature.theme.change.ChangeThemeShowcasesProcessor +import kotli.template.multiplatform.compose.showcases.userflow.theme.change.ChangeThemeShowcasesProcessor import kotli.template.multiplatform.compose.userflow.theme.save.SaveThemeProcessor import kotlin.time.Duration.Companion.hours diff --git a/processor/src/main/kotlin/kotli/template/multiplatform/compose/userflow/theme/toggle/ToggleThemeProcessor.kt b/processor/src/main/kotlin/kotli/template/multiplatform/compose/userflow/theme/toggle/ToggleThemeProcessor.kt index 049f5f7f..982e3fc0 100644 --- a/processor/src/main/kotlin/kotli/template/multiplatform/compose/userflow/theme/toggle/ToggleThemeProcessor.kt +++ b/processor/src/main/kotlin/kotli/template/multiplatform/compose/userflow/theme/toggle/ToggleThemeProcessor.kt @@ -8,7 +8,7 @@ import kotli.engine.template.rule.RemoveFile import kotli.engine.template.rule.RemoveMarkedLine import kotli.template.multiplatform.compose.Rules import kotli.template.multiplatform.compose.Tags -import kotli.template.multiplatform.compose.showcases.feature.theme.toggle.ToggleThemeShowcasesProcessor +import kotli.template.multiplatform.compose.showcases.userflow.theme.toggle.ToggleThemeShowcasesProcessor import kotli.template.multiplatform.compose.userflow.theme.save.SaveThemeProcessor import kotlin.time.Duration.Companion.hours diff --git a/processor/src/main/resources/kotli/template/multiplatform/compose/dataflow/cache/basic/usage.md b/processor/src/main/resources/kotli/template/multiplatform/compose/dataflow/cache/basic/usage.md index 907e27ae..57c5a468 100644 --- a/processor/src/main/resources/kotli/template/multiplatform/compose/dataflow/cache/basic/usage.md +++ b/processor/src/main/resources/kotli/template/multiplatform/compose/dataflow/cache/basic/usage.md @@ -13,7 +13,6 @@ Facade **CacheSource** provides the following methods: - `invalidate(key: K)` - Invalidates the cache entry associated with the specified key. - `remove(type: Class)` - Removes all cache entries associated with the specified key type. - `remove(key: K)` - Removes the cache entry associated with the specified key. -- `put(key: CacheKey, value: T)` - Associates the specified value with the specified key in the cache. - `clear()` - Clears all entries from the cache. ## Example diff --git a/processor/src/main/resources/kotli/template/multiplatform/compose/dataflow/database/room/description.md b/processor/src/main/resources/kotli/template/multiplatform/compose/dataflow/database/room/description.md new file mode 100644 index 00000000..cd841fde --- /dev/null +++ b/processor/src/main/resources/kotli/template/multiplatform/compose/dataflow/database/room/description.md @@ -0,0 +1 @@ +Save data in a local database using Room. The Room persistence library provides an abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite. \ No newline at end of file diff --git a/processor/src/main/resources/kotli/template/multiplatform/compose/dataflow/database/room/title.md b/processor/src/main/resources/kotli/template/multiplatform/compose/dataflow/database/room/title.md new file mode 100644 index 00000000..8d46b2a5 --- /dev/null +++ b/processor/src/main/resources/kotli/template/multiplatform/compose/dataflow/database/room/title.md @@ -0,0 +1 @@ +SQLite (Room) \ No newline at end of file diff --git a/processor/src/main/resources/kotli/template/multiplatform/compose/dataflow/database/room/usage.md b/processor/src/main/resources/kotli/template/multiplatform/compose/dataflow/database/room/usage.md new file mode 100644 index 00000000..478dc4eb --- /dev/null +++ b/processor/src/main/resources/kotli/template/multiplatform/compose/dataflow/database/room/usage.md @@ -0,0 +1,115 @@ +## Overview + +- Component package: `app.data.source.database.room` +- DI integration: `app.di.data.RoomSourceModule` + +The integration includes the following components: + +- **AppDatabase**: A pre-configured Room database designed to register all entities and DAO objects. +- **AppRoomSource**: Serves as a holder of the AppDatabase instance and acts as a service locator for all DAO objects. +- **User** and **UserDao**: An example entity and its associated DAO object. These classes serve as templates that can be used to create your own entities and DAOs. + +## Create new Entity and DAO + +The official guide for defining entities can be found here: [https://developer.android.com/training/data-storage/room/defining-data](https://developer.android.com/training/data-storage/room/defining-data) + +Imagine you need to add a new entity called **Address**. Here are the steps to follow. + +### 1. Create the `Address` entity class + +You can use the `User` class in `app.data.source.database.room.entity` as a template. + +```kotlin +@Entity(tableName = "address") +data class Address( + @PrimaryKey(autoGenerate = true) + var id: Int = 0, + @ColumnInfo(name = "country") + var country: String? = null, + @ColumnInfo(name = "city") + var city: String? = null, + @ColumnInfo(name = "street") + var street: String? = null, +) +``` + +### 2. Create the `AddressDao` interface + +You can use the `UserDao` interface in `app.data.source.database.room.dao` as a template. + +```kotlin +@Dao +interface AddressDao { + @Insert + fun create(vararg addresses: Address) + @Update + fun update(vararg addresses: Address) + @Delete + fun delete(vararg addresses: Address) + @Query("SELECT * FROM address WHERE id = :id LIMIT 1") + fun get(id: Long): Address? + @Query("SELECT * FROM address") + fun getAll(): List
+ @Query("SELECT * FROM address") + fun getAllAsFlow(): Flow
+} +``` + +### 3. Register `Address` and `AddressDao` in `AppDatabase` + +```kotlin +@Database( + entities = [ + Address::class + ], + version = 1 +) +abstract class AppDatabase : RoomDatabase() { + + abstract fun getAddressDao(): AddressDao + +} +``` + +### 4. Register `AddressDao` in `AppRoomSource` + +```kotlin +class AppRoomSource( + private val databaseName: String = "db" +) { + ... + val addressDao by lazy { db.getAddressDao() } + ... +} +``` + +## Usage of DAO in your code + +In this example, we will directly use `AppRoomSource` from the `ViewModel` to access `AddressDao`. However, it is recommended to create a separate `Repository` layer and call all data sources from there. + +```kotlin +class AddressViewModel( + private val roomSource: AppRoomSource, + private val appState: AppState +) : BaseViewModel() { + + val addressesStore = DataState>() + + override fun doBind() = launchAsync("getAll") { + val addressDao = roomSource.addressDao + addressDao.getAllAsFlow().collectLatest(addressesStore::set) + } + + fun onCreate(address: Address) = launchAsync("onCreate", appState) { + val addressDao = roomSource.addressDao + addressDao.create(address) + } + + fun onDelete(address: Address) = launchAsync("onRemove", appState) { + val addressDao = roomSource.addressDao + addressDao.delete(address) + } + +} +``` + diff --git a/processor/src/test/kotlin/kotli/template/multiplatform/compose/MultiplatformComposeTemplateProcessorTest.kt b/processor/src/test/kotlin/kotli/template/multiplatform/compose/MultiplatformComposeTemplateProcessorTest.kt index 8b2a88a9..b75af24c 100644 --- a/processor/src/test/kotlin/kotli/template/multiplatform/compose/MultiplatformComposeTemplateProcessorTest.kt +++ b/processor/src/test/kotlin/kotli/template/multiplatform/compose/MultiplatformComposeTemplateProcessorTest.kt @@ -8,10 +8,13 @@ import kotli.engine.generator.PathOutputGenerator import kotli.engine.generator.ZipOutputGenerator import kotli.engine.model.Feature import kotli.engine.model.Layer +import kotli.template.multiplatform.compose.dataflow.database.room.RoomProcessor import kotli.template.multiplatform.compose.platform.client.android.AndroidPlatformProcessor +import kotli.template.multiplatform.compose.platform.client.ios.IOSPlatformProcessor import kotli.template.multiplatform.compose.platform.client.jvm.JvmPlatformProcessor import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.RepeatedTest import org.slf4j.LoggerFactory import java.io.ByteArrayOutputStream import java.io.File @@ -108,7 +111,9 @@ class MultiplatformComposeTemplateProcessorTest { processorId = processor.getId(), namespace = "my.app", name = "myApp", - features = listOf(Feature(AndroidPlatformProcessor.ID)) + features = listOf( + Feature(AndroidPlatformProcessor.ID) + ) ) val generator = PathOutputGenerator(buildPath(), registry) val gradleGenerator = GradleProjectGenerator(commands(layer.features), generator) @@ -131,7 +136,7 @@ class MultiplatformComposeTemplateProcessorTest { } } - @Test + @RepeatedTest(3) fun `compose template with random features`() { runBlocking { val processors = processor.getFeatureProviders() diff --git a/template/app/build.gradle.kts b/template/app/build.gradle.kts index 5ac54f21..0bf93888 100644 --- a/template/app/build.gradle.kts +++ b/template/app/build.gradle.kts @@ -1,5 +1,6 @@ import org.jetbrains.compose.desktop.application.dsl.TargetFormat // {platform.jvm} import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig // {platform.js} + plugins { alias(libs.plugins.kotlin.multiplatform) alias(libs.plugins.kotlinx.serialization) @@ -8,6 +9,8 @@ plugins { alias(libs.plugins.android.application) // {platform.android} alias(libs.plugins.sqldelight) // {dataflow.database.sqldelight} alias(libs.plugins.skie) // {platform.ios} + alias(libs.plugins.ksp) // {common.ksp} + alias(libs.plugins.room) // {dataflow.database.room} } kotlin { @@ -29,6 +32,7 @@ kotlin { iosTarget.binaries.framework { baseName = "App" isStatic = true + linkerOpts.add("-lsqlite3") // {dataflow.database.sqlite-linker} } } // {platform.ios.target} @@ -104,6 +108,18 @@ kotlin { implementation(libs.sqldelight.sqlite.driver) // {dataflow.database.sqldelight} } // {platform.jvm.dependencies} + // {platform.mobile_and_desktop.dependencies} + val mobileAndDesktopMain by creating { + dependsOn(commonMain.get()) + dependencies { + implementation(libs.androidx.room.runtime) // {dataflow.database.room} + implementation(libs.sqlite.bundled) // {dataflow.database.sqlite} + } + } + // {platform.mobile_and_desktop.dependencies} + androidMain.get().dependsOn(mobileAndDesktopMain) // {platform.android} + iosMain.get().dependsOn(mobileAndDesktopMain) // {platform.ios} + jvmMain.get().dependsOn(mobileAndDesktopMain) // {platform.jvm} } } // {sqldelight.config} @@ -142,7 +158,10 @@ android { isDebuggable = false isMinifyEnabled = true isShrinkResources = true - proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "assemble/proguard-rules.pro") + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "assemble/proguard-rules.pro" + ) } } compileOptions { @@ -170,3 +189,17 @@ compose.desktop { } } // {platform.jvm.config} +// {common.ksp.config} +dependencies { + add("kspAndroid", libs.androidx.room.compiler) // {platform.android} + add("kspJvm", libs.androidx.room.compiler) // {platform.jvm} + add("kspIosX64", libs.androidx.room.compiler) // {platform.ios} + add("kspIosArm64", libs.androidx.room.compiler) // {platform.ios} + add("kspIosSimulatorArm64", libs.androidx.room.compiler) // {platform.ios} +} +// {common.ksp.config} +// {dataflow.database.room.config} +room { + schemaDirectory("$projectDir/schemas") +} +// {dataflow.database.room.config} diff --git a/template/app/src/androidMain/kotlin/kotli/app/data/source/database/room/AppRoomSource.android.kt b/template/app/src/androidMain/kotlin/kotli/app/data/source/database/room/AppRoomSource.android.kt new file mode 100644 index 00000000..5b2d8d6e --- /dev/null +++ b/template/app/src/androidMain/kotlin/kotli/app/data/source/database/room/AppRoomSource.android.kt @@ -0,0 +1,14 @@ +package kotli.app.data.source.database.room + +import androidx.room.Room +import androidx.room.RoomDatabase +import kotli.app.Application + +actual fun getRoomDatabaseBuilder(name: String): RoomDatabase.Builder { + val context = Application.instance + val dbFile = context.getDatabasePath(name) + return Room.databaseBuilder( + context = context, + name = dbFile.absolutePath + ) +} \ No newline at end of file diff --git a/template/app/src/androidMain/kotlin/kotli/app/data/source/database/sqldelight/AppSqlDelightSource.android.kt b/template/app/src/androidMain/kotlin/kotli/app/data/source/database/sqldelight/AppSqlDelightSource.android.kt index 365e9c08..db8ca76e 100644 --- a/template/app/src/androidMain/kotlin/kotli/app/data/source/database/sqldelight/AppSqlDelightSource.android.kt +++ b/template/app/src/androidMain/kotlin/kotli/app/data/source/database/sqldelight/AppSqlDelightSource.android.kt @@ -1,6 +1,5 @@ package kotli.app.data.source.database.sqldelight -import android.content.Context import app.cash.sqldelight.async.coroutines.synchronous import app.cash.sqldelight.db.SqlDriver import app.cash.sqldelight.driver.android.AndroidSqliteDriver @@ -8,6 +7,6 @@ import kotli.app.Application import kotli.app.data.source.database.sqldelight.AppDatabase actual fun createSqlDriver(name: String): SqlDriver { - val context: Context = Application.instance + val context = Application.instance return AndroidSqliteDriver(AppDatabase.Schema.synchronous(), context, name) } \ No newline at end of file diff --git a/template/app/src/androidMain/kotlin/kotli/app/di/DI.android.kt b/template/app/src/androidMain/kotlin/kotli/app/di/DI.android.kt new file mode 100644 index 00000000..8d18cb8c --- /dev/null +++ b/template/app/src/androidMain/kotlin/kotli/app/di/DI.android.kt @@ -0,0 +1,10 @@ +package kotli.app.di + +import kotli.app.di.data.roomSourceModule +import org.koin.core.KoinApplication + +actual fun configure(app: KoinApplication) { + app.modules( + roomSourceModule + ) +} \ No newline at end of file diff --git a/template/app/src/androidMain/kotlin/kotli/app/di/data/RoomSourceModule.kt b/template/app/src/androidMain/kotlin/kotli/app/di/data/RoomSourceModule.kt new file mode 100644 index 00000000..394c882b --- /dev/null +++ b/template/app/src/androidMain/kotlin/kotli/app/di/data/RoomSourceModule.kt @@ -0,0 +1,8 @@ +package kotli.app.di.data + +import kotli.app.data.source.database.room.AppRoomSource +import org.koin.dsl.module + +val roomSourceModule = module { + single { AppRoomSource() } +} \ No newline at end of file diff --git a/template/app/src/commonMain/kotlin/kotli/app/data/source/database/sqldelight/AppSqlDelightSource.kt b/template/app/src/commonMain/kotlin/kotli/app/data/source/database/sqldelight/AppSqlDelightSource.kt index ee08740b..0672b8b1 100644 --- a/template/app/src/commonMain/kotlin/kotli/app/data/source/database/sqldelight/AppSqlDelightSource.kt +++ b/template/app/src/commonMain/kotlin/kotli/app/data/source/database/sqldelight/AppSqlDelightSource.kt @@ -1,15 +1,12 @@ package kotli.app.data.source.database.sqldelight import app.cash.sqldelight.db.SqlDriver -import kotli.app.data.source.database.sqldelight.AppDatabase import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.shareIn -expect fun createSqlDriver(name: String): SqlDriver - /** * This class represents a source for accessing the SqlDelight database. * @@ -30,4 +27,6 @@ class AppSqlDelightSource( suspend fun getDatabase(): AppDatabase = db.first() -} \ No newline at end of file +} + +expect fun createSqlDriver(name: String): SqlDriver \ No newline at end of file diff --git a/template/app/src/commonMain/kotlin/kotli/app/di/DI.kt b/template/app/src/commonMain/kotlin/kotli/app/di/DI.kt index 51a9b676..39bc1e4f 100644 --- a/template/app/src/commonMain/kotlin/kotli/app/di/DI.kt +++ b/template/app/src/commonMain/kotlin/kotli/app/di/DI.kt @@ -11,6 +11,7 @@ import kotli.app.di.presentation.appModule import kotli.app.di.presentation.navigationBarModule import kotli.app.di.presentation.navigationModule import kotli.app.di.presentation.themeModule +import org.koin.core.KoinApplication import org.koin.core.context.startKoin val koinApp = startKoin { @@ -28,6 +29,9 @@ val koinApp = startKoin { themeModule, appModule ) + configure(this) } +expect fun configure(app: KoinApplication) + inline fun get(): T = koinApp.koin.get() \ No newline at end of file diff --git a/template/app/src/commonMain/kotlin/kotli/app/di/presentation/AppModule.kt b/template/app/src/commonMain/kotlin/kotli/app/di/presentation/AppModule.kt index 925e2b15..f21da1bb 100644 --- a/template/app/src/commonMain/kotlin/kotli/app/di/presentation/AppModule.kt +++ b/template/app/src/commonMain/kotlin/kotli/app/di/presentation/AppModule.kt @@ -25,6 +25,7 @@ import kotli.app.presentation.showcases.userflow.navigation.args.to.ArgsNavigati import kotli.app.presentation.showcases.userflow.navigation.no_args.from.NoArgsNavigationFromViewModel import kotli.app.presentation.showcases.userflow.navigation.no_args.to.NoArgsNavigationToViewModel import kotli.app.presentation.loader.LoaderViewModel +import kotli.app.presentation.showcases.dataflow.room.crud.createRoomCrudViewModel import kotli.app.presentation.template.screen_with_args.TemplateViewModel import kotli.app.presentation.template.screen_without_args.TemplateNoArgsViewModel import kotli.app.presentation.theme.AppThemePersistenceViewModel @@ -63,6 +64,7 @@ val appModule = module { initializer { SqlDelightCrudViewModel(get(), get()) } initializer { SqlDelightPagingViewModel(get(), get(), get(), get()) } initializer { BasicCacheViewModel(get(), get()) } + initializer { createRoomCrudViewModel() } } } } \ No newline at end of file diff --git a/template/app/src/commonMain/kotlin/kotli/app/presentation/showcases/Showcases.kt b/template/app/src/commonMain/kotlin/kotli/app/presentation/showcases/Showcases.kt index 59e6782e..45f93910 100644 --- a/template/app/src/commonMain/kotlin/kotli/app/presentation/showcases/Showcases.kt +++ b/template/app/src/commonMain/kotlin/kotli/app/presentation/showcases/Showcases.kt @@ -5,6 +5,7 @@ import kotli.app.presentation.showcases.dataflow.http.basic.BasicHttpShowcase import kotli.app.presentation.showcases.dataflow.keyvalue.`object`.ObjectKeyValueShowcase import kotli.app.presentation.showcases.dataflow.keyvalue.primitive.PrimitiveKeyValueShowcase import kotli.app.presentation.showcases.dataflow.paging.basic.BasicPagingShowcase +import kotli.app.presentation.showcases.dataflow.room.crud.RoomCrudShowcase import kotli.app.presentation.showcases.dataflow.sqldelight.crud.SqlDelightCrudShowcase import kotli.app.presentation.showcases.dataflow.sqldelight.paging.SqlDelightPagingShowcase import kotli.app.presentation.showcases.userflow.loader.data.DataLoaderShowcase @@ -35,6 +36,8 @@ object Showcases { ShowcaseItemGroup("Dataflow :: SqlDelight"), SqlDelightCrudShowcase, SqlDelightPagingShowcase, + ShowcaseItemGroup("Dataflow :: Room"), + RoomCrudShowcase, ShowcaseItemGroup("Userflow :: Navigation + MVVM"), NoArgsNavigationShowcase, ArgsNavigationShowcase, diff --git a/template/app/src/commonMain/kotlin/kotli/app/presentation/showcases/dataflow/cache/basic/BasicCacheViewModel.kt b/template/app/src/commonMain/kotlin/kotli/app/presentation/showcases/dataflow/cache/basic/BasicCacheViewModel.kt index 94e5aca6..bc097731 100644 --- a/template/app/src/commonMain/kotlin/kotli/app/presentation/showcases/dataflow/cache/basic/BasicCacheViewModel.kt +++ b/template/app/src/commonMain/kotlin/kotli/app/presentation/showcases/dataflow/cache/basic/BasicCacheViewModel.kt @@ -24,11 +24,11 @@ class BasicCacheViewModel( launchAsync { val cacheKey = SimpleCacheKey() val cacheEntry = cacheSource.get(cacheKey, ::getDateAsFormattedString) - cacheEntry.getChanges().collectLatest(cacheState::set) + cacheEntry.changes().collectLatest(cacheState::set) } } - private fun getDateAsFormattedString(): String { + private fun getDateAsFormattedString(key: SimpleCacheKey): String { val time = Clock.System.now() return time.format(DateTimeComponents.Format { byUnicodePattern("yyyy-MM-dd HH:mm:ss") diff --git a/template/app/src/commonMain/kotlin/kotli/app/presentation/showcases/dataflow/room/crud/RoomCrudDestination.kt b/template/app/src/commonMain/kotlin/kotli/app/presentation/showcases/dataflow/room/crud/RoomCrudDestination.kt new file mode 100644 index 00000000..c42e73fb --- /dev/null +++ b/template/app/src/commonMain/kotlin/kotli/app/presentation/showcases/dataflow/room/crud/RoomCrudDestination.kt @@ -0,0 +1,13 @@ +package kotli.app.presentation.showcases.dataflow.room.crud + +import androidx.navigation.NavGraphBuilder +import shared.presentation.navigation.NavigationDestinationNoArgs +import shared.presentation.navigation.NavigationStrategy + +object RoomCrudDestination : NavigationDestinationNoArgs() { + + override val id: String = "room_crud_screen" + override val navStrategy: NavigationStrategy = NavigationStrategy.NewInstance + override fun doBind(builder: NavGraphBuilder) = composable(builder) { RoomCrudScreen() } + +} \ No newline at end of file diff --git a/template/app/src/commonMain/kotlin/kotli/app/presentation/showcases/dataflow/room/crud/RoomCrudScreen.kt b/template/app/src/commonMain/kotlin/kotli/app/presentation/showcases/dataflow/room/crud/RoomCrudScreen.kt new file mode 100644 index 00000000..872673a7 --- /dev/null +++ b/template/app/src/commonMain/kotlin/kotli/app/presentation/showcases/dataflow/room/crud/RoomCrudScreen.kt @@ -0,0 +1,82 @@ +package kotli.app.presentation.showcases.dataflow.room.crud + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import kotli.app.presentation.showcases.dataflow.room.crud.model.UserData +import shared.design.component.AppActionButton +import shared.design.component.AppHorizontalDivider +import shared.design.component.AppOutlinedButton +import shared.design.component.AppText +import shared.design.container.AppFixedTopBarLazyColumn +import shared.design.icon.AppIcons +import shared.design.theme.AppTheme +import shared.presentation.viewmodel.provideViewModel + +@Composable +fun RoomCrudScreen() { + val viewModel: RoomCrudViewModel = provideViewModel() + val users = viewModel.usersState.asStateValueNotNull() + AppFixedTopBarLazyColumn( + title = RoomCrudShowcase.label, + onBack = viewModel::onBack, + actions = { ActionsBlock(viewModel) }, + content = { + users.forEach { user -> + item { + UserBlock(user, viewModel::onDelete) + } + } + if (users.isEmpty()) { + item { + AppText( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + text = "No users", + textAlign = TextAlign.Center + ) + } + } + } + ) +} + +@Composable +private fun ActionsBlock(viewModel: RoomCrudViewModel) { + AppOutlinedButton( + modifier = Modifier.padding(16.dp), + onClick = viewModel::onAdd, + text = "Add user" + ) +} + +@Composable +private fun UserBlock(user: UserData?, onDelete: (user: UserData) -> Unit) { + Row( + modifier = Modifier + .height(56.dp) + .fillMaxWidth() + .padding(horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + AppText(text = user?.firstName.orEmpty()) + AppText(text = user?.lastName.orEmpty()) + Spacer(Modifier.weight(1f)) + AppActionButton( + onClick = { user?.let(onDelete) }, + tint = AppTheme.current.error, + icon = AppIcons.delete, + ) + } + AppHorizontalDivider() +} \ No newline at end of file diff --git a/template/app/src/commonMain/kotlin/kotli/app/presentation/showcases/dataflow/room/crud/RoomCrudShowcase.kt b/template/app/src/commonMain/kotlin/kotli/app/presentation/showcases/dataflow/room/crud/RoomCrudShowcase.kt new file mode 100644 index 00000000..2bca6ae1 --- /dev/null +++ b/template/app/src/commonMain/kotlin/kotli/app/presentation/showcases/dataflow/room/crud/RoomCrudShowcase.kt @@ -0,0 +1,19 @@ +package kotli.app.presentation.showcases.dataflow.room.crud + +import kotli.app.presentation.showcases.ShowcaseItem +import kotli.app.presentation.showcases.ShowcasesViewModel +import shared.presentation.navigation.NavigationDestination + +object RoomCrudShowcase : ShowcaseItem { + + override val label: String = "Room CRUD" + + override fun onClick(viewModel: ShowcasesViewModel) { + viewModel.navigationStore.onNext(RoomCrudDestination) + } + + override fun dependsOn(): List> = listOf( + RoomCrudDestination + ) + +} \ No newline at end of file diff --git a/template/app/src/commonMain/kotlin/kotli/app/presentation/showcases/dataflow/room/crud/RoomCrudViewModel.kt b/template/app/src/commonMain/kotlin/kotli/app/presentation/showcases/dataflow/room/crud/RoomCrudViewModel.kt new file mode 100644 index 00000000..98ee24d7 --- /dev/null +++ b/template/app/src/commonMain/kotlin/kotli/app/presentation/showcases/dataflow/room/crud/RoomCrudViewModel.kt @@ -0,0 +1,19 @@ +package kotli.app.presentation.showcases.dataflow.room.crud + +import kotli.app.presentation.showcases.dataflow.room.crud.model.UserData +import shared.presentation.navigation.NavigationStore +import shared.presentation.store.DataState +import shared.presentation.viewmodel.BaseViewModel + +expect fun createRoomCrudViewModel(): RoomCrudViewModel + +abstract class RoomCrudViewModel(val navigationStore: NavigationStore) : BaseViewModel() { + + val usersState = DataState>(emptyList()) + + fun onBack() = navigationStore.onBack() + + abstract fun onAdd() + + abstract fun onDelete(user: UserData) +} diff --git a/template/app/src/commonMain/kotlin/kotli/app/presentation/showcases/dataflow/room/crud/model/UserData.kt b/template/app/src/commonMain/kotlin/kotli/app/presentation/showcases/dataflow/room/crud/model/UserData.kt new file mode 100644 index 00000000..21a16879 --- /dev/null +++ b/template/app/src/commonMain/kotlin/kotli/app/presentation/showcases/dataflow/room/crud/model/UserData.kt @@ -0,0 +1,7 @@ +package kotli.app.presentation.showcases.dataflow.room.crud.model + +data class UserData( + val id: Long, + val firstName: String?, + val lastName: String?, +) \ No newline at end of file diff --git a/template/app/src/commonMain/kotlin/kotli/app/presentation/showcases/dataflow/sqldelight/crud/SqlDelightCrudShowcase.kt b/template/app/src/commonMain/kotlin/kotli/app/presentation/showcases/dataflow/sqldelight/crud/SqlDelightCrudShowcase.kt index 8b714d80..8e25bdbc 100644 --- a/template/app/src/commonMain/kotlin/kotli/app/presentation/showcases/dataflow/sqldelight/crud/SqlDelightCrudShowcase.kt +++ b/template/app/src/commonMain/kotlin/kotli/app/presentation/showcases/dataflow/sqldelight/crud/SqlDelightCrudShowcase.kt @@ -6,7 +6,7 @@ import shared.presentation.navigation.NavigationDestination object SqlDelightCrudShowcase : ShowcaseItem { - override val label: String = "SQL CRUD" + override val label: String = "SqlDelight CRUD" override fun onClick(viewModel: ShowcasesViewModel) { viewModel.navigationStore.onNext(SqlDelightCrudDestination) diff --git a/template/app/src/commonMain/kotlin/kotli/app/presentation/showcases/dataflow/sqldelight/paging/SqlDelightPagingShowcase.kt b/template/app/src/commonMain/kotlin/kotli/app/presentation/showcases/dataflow/sqldelight/paging/SqlDelightPagingShowcase.kt index 086c7bc9..4ebe8dd6 100644 --- a/template/app/src/commonMain/kotlin/kotli/app/presentation/showcases/dataflow/sqldelight/paging/SqlDelightPagingShowcase.kt +++ b/template/app/src/commonMain/kotlin/kotli/app/presentation/showcases/dataflow/sqldelight/paging/SqlDelightPagingShowcase.kt @@ -6,7 +6,7 @@ import shared.presentation.navigation.NavigationDestination object SqlDelightPagingShowcase : ShowcaseItem { - override val label: String = "SQL Paging" + override val label: String = "SqlDelight Paging" override fun onClick(viewModel: ShowcasesViewModel) { viewModel.navigationStore.onNext(SqlDelightPagingDestination) diff --git a/template/app/src/iosArm64Main/kotlin/kotli/app/data/source/database/room/AppRoomSource.ios.kt b/template/app/src/iosArm64Main/kotlin/kotli/app/data/source/database/room/AppRoomSource.ios.kt new file mode 100644 index 00000000..99abe271 --- /dev/null +++ b/template/app/src/iosArm64Main/kotlin/kotli/app/data/source/database/room/AppRoomSource.ios.kt @@ -0,0 +1,13 @@ +package kotli.app.data.source.database.room + +import androidx.room.Room +import androidx.room.RoomDatabase +import platform.Foundation.NSHomeDirectory + +actual fun getRoomDatabaseBuilder(name: String): RoomDatabase.Builder { + val dbFilePath = NSHomeDirectory() + "/$name" + return Room.databaseBuilder( + name = dbFilePath, + factory = { AppDatabase::class.instantiateImpl() } + ) +} \ No newline at end of file diff --git a/template/app/src/iosMain/kotlin/kotli/app/di/DI.ios.kt b/template/app/src/iosMain/kotlin/kotli/app/di/DI.ios.kt new file mode 100644 index 00000000..8d18cb8c --- /dev/null +++ b/template/app/src/iosMain/kotlin/kotli/app/di/DI.ios.kt @@ -0,0 +1,10 @@ +package kotli.app.di + +import kotli.app.di.data.roomSourceModule +import org.koin.core.KoinApplication + +actual fun configure(app: KoinApplication) { + app.modules( + roomSourceModule + ) +} \ No newline at end of file diff --git a/template/app/src/iosMain/kotlin/kotli/app/di/data/RoomSourceModule.kt b/template/app/src/iosMain/kotlin/kotli/app/di/data/RoomSourceModule.kt new file mode 100644 index 00000000..394c882b --- /dev/null +++ b/template/app/src/iosMain/kotlin/kotli/app/di/data/RoomSourceModule.kt @@ -0,0 +1,8 @@ +package kotli.app.di.data + +import kotli.app.data.source.database.room.AppRoomSource +import org.koin.dsl.module + +val roomSourceModule = module { + single { AppRoomSource() } +} \ No newline at end of file diff --git a/template/app/src/iosSimulatorArm64Main/kotlin/kotli/app/data/source/database/room/AppRoomSource.ios.kt b/template/app/src/iosSimulatorArm64Main/kotlin/kotli/app/data/source/database/room/AppRoomSource.ios.kt new file mode 100644 index 00000000..99abe271 --- /dev/null +++ b/template/app/src/iosSimulatorArm64Main/kotlin/kotli/app/data/source/database/room/AppRoomSource.ios.kt @@ -0,0 +1,13 @@ +package kotli.app.data.source.database.room + +import androidx.room.Room +import androidx.room.RoomDatabase +import platform.Foundation.NSHomeDirectory + +actual fun getRoomDatabaseBuilder(name: String): RoomDatabase.Builder { + val dbFilePath = NSHomeDirectory() + "/$name" + return Room.databaseBuilder( + name = dbFilePath, + factory = { AppDatabase::class.instantiateImpl() } + ) +} \ No newline at end of file diff --git a/template/app/src/iosX64Main/kotlin/kotli/app/data/source/database/room/AppRoomSource.ios.kt b/template/app/src/iosX64Main/kotlin/kotli/app/data/source/database/room/AppRoomSource.ios.kt new file mode 100644 index 00000000..99abe271 --- /dev/null +++ b/template/app/src/iosX64Main/kotlin/kotli/app/data/source/database/room/AppRoomSource.ios.kt @@ -0,0 +1,13 @@ +package kotli.app.data.source.database.room + +import androidx.room.Room +import androidx.room.RoomDatabase +import platform.Foundation.NSHomeDirectory + +actual fun getRoomDatabaseBuilder(name: String): RoomDatabase.Builder { + val dbFilePath = NSHomeDirectory() + "/$name" + return Room.databaseBuilder( + name = dbFilePath, + factory = { AppDatabase::class.instantiateImpl() } + ) +} \ No newline at end of file diff --git a/template/app/src/jsMain/kotlin/kotli/app/di/DI.js.kt b/template/app/src/jsMain/kotlin/kotli/app/di/DI.js.kt new file mode 100644 index 00000000..35d12cea --- /dev/null +++ b/template/app/src/jsMain/kotlin/kotli/app/di/DI.js.kt @@ -0,0 +1,6 @@ +package kotli.app.di + +import org.koin.core.KoinApplication + +actual fun configure(app: KoinApplication) { +} \ No newline at end of file diff --git a/template/app/src/jsMain/kotlin/kotli/app/presentation/showcases/dataflow/room/crud/RoomCrudViewModel.js.kt b/template/app/src/jsMain/kotlin/kotli/app/presentation/showcases/dataflow/room/crud/RoomCrudViewModel.js.kt new file mode 100644 index 00000000..79caef70 --- /dev/null +++ b/template/app/src/jsMain/kotlin/kotli/app/presentation/showcases/dataflow/room/crud/RoomCrudViewModel.js.kt @@ -0,0 +1,27 @@ +package kotli.app.presentation.showcases.dataflow.room.crud + +import kotli.app.di.get +import kotli.app.presentation.showcases.dataflow.room.crud.model.UserData +import shared.design.component.AppSnackbarStore +import shared.presentation.navigation.NavigationStore + +actual fun createRoomCrudViewModel(): RoomCrudViewModel = RoomCrudViewModelImpl( + snackbarStore = get(), + navigationStore = get() +) + +private class RoomCrudViewModelImpl( + private val snackbarStore: AppSnackbarStore, + navigationStore: NavigationStore +) : RoomCrudViewModel( + navigationStore +) { + override fun onAdd() = launchAsync { + snackbarStore.showSnackbar("Room is not supported for the Web target") + } + + override fun onDelete(user: UserData) = launchAsync { + snackbarStore.showSnackbar("Room is not supported for the Web target") + } + +} \ No newline at end of file diff --git a/template/app/src/jvmMain/kotlin/kotli/app/data/source/database/room/AppRoomSource.jvm.kt b/template/app/src/jvmMain/kotlin/kotli/app/data/source/database/room/AppRoomSource.jvm.kt new file mode 100644 index 00000000..fb76f894 --- /dev/null +++ b/template/app/src/jvmMain/kotlin/kotli/app/data/source/database/room/AppRoomSource.jvm.kt @@ -0,0 +1,12 @@ +package kotli.app.data.source.database.room + +import androidx.room.Room +import androidx.room.RoomDatabase +import java.io.File + +actual fun getRoomDatabaseBuilder(name: String): RoomDatabase.Builder { + val dbFile = File(System.getProperty("java.io.tmpdir"), name) + return Room.databaseBuilder( + name = dbFile.absolutePath, + ) +} \ No newline at end of file diff --git a/template/app/src/jvmMain/kotlin/kotli/app/di/DI.jvm.kt b/template/app/src/jvmMain/kotlin/kotli/app/di/DI.jvm.kt new file mode 100644 index 00000000..8d18cb8c --- /dev/null +++ b/template/app/src/jvmMain/kotlin/kotli/app/di/DI.jvm.kt @@ -0,0 +1,10 @@ +package kotli.app.di + +import kotli.app.di.data.roomSourceModule +import org.koin.core.KoinApplication + +actual fun configure(app: KoinApplication) { + app.modules( + roomSourceModule + ) +} \ No newline at end of file diff --git a/template/app/src/jvmMain/kotlin/kotli/app/di/data/RoomSourceModule.kt b/template/app/src/jvmMain/kotlin/kotli/app/di/data/RoomSourceModule.kt new file mode 100644 index 00000000..394c882b --- /dev/null +++ b/template/app/src/jvmMain/kotlin/kotli/app/di/data/RoomSourceModule.kt @@ -0,0 +1,8 @@ +package kotli.app.di.data + +import kotli.app.data.source.database.room.AppRoomSource +import org.koin.dsl.module + +val roomSourceModule = module { + single { AppRoomSource() } +} \ No newline at end of file diff --git a/template/app/src/mobileAndDesktopMain/kotlin/kotli/app/data/source/database/room/AppDatabase.kt b/template/app/src/mobileAndDesktopMain/kotlin/kotli/app/data/source/database/room/AppDatabase.kt new file mode 100644 index 00000000..790f3361 --- /dev/null +++ b/template/app/src/mobileAndDesktopMain/kotlin/kotli/app/data/source/database/room/AppDatabase.kt @@ -0,0 +1,28 @@ +package kotli.app.data.source.database.room + +import androidx.room.Database +import androidx.room.RoomDatabase +import androidx.room.TypeConverters +import kotli.app.data.source.database.room.dao.UserDao +import kotli.app.data.source.database.room.entity.User + +/** + * This class represents the Room database for the application. + */ +@Database( + entities = [ + User::class + ], + version = 1 +) +@TypeConverters(Converters::class) +abstract class AppDatabase : RoomDatabase() { + + /** + * Retrieves the DAO (Data Access Object) for interacting with the User entity. + * + * @return The UserDao instance. + */ + abstract fun getUserDao(): UserDao + +} \ No newline at end of file diff --git a/template/app/src/mobileAndDesktopMain/kotlin/kotli/app/data/source/database/room/AppRoomSource.kt b/template/app/src/mobileAndDesktopMain/kotlin/kotli/app/data/source/database/room/AppRoomSource.kt new file mode 100644 index 00000000..716cd89c --- /dev/null +++ b/template/app/src/mobileAndDesktopMain/kotlin/kotli/app/data/source/database/room/AppRoomSource.kt @@ -0,0 +1,43 @@ +package kotli.app.data.source.database.room + +import androidx.room.RoomDatabase +import androidx.room.useWriterConnection +import androidx.sqlite.driver.bundled.BundledSQLiteDriver + +/** + * This class represents a source for accessing the Room database. + * + * It provides access to all underlying DAO objects as well. + * + * @property databaseName The name of the database. + */ +class AppRoomSource( + private val databaseName: String = "room.db" +) { + + private val db by lazy { + getRoomDatabaseBuilder(databaseName) + .fallbackToDestructiveMigrationOnDowngrade(false) + .setDriver(BundledSQLiteDriver()) + .build() + } + + /** + * Retrieves the UserDao for interacting with the User entity. + * + * @return The UserDao instance. + */ + val userDao by lazy { db.getUserDao() } + + /** + * Executes a transaction on the database. + * + * @param The type of the result. + * @param block The block of code to execute within the transaction. + * @return The result of the transaction. + */ + suspend fun withTransaction(block: suspend () -> R): R = db.useWriterConnection { block() } + +} + +expect fun getRoomDatabaseBuilder(name: String): RoomDatabase.Builder \ No newline at end of file diff --git a/template/app/src/mobileAndDesktopMain/kotlin/kotli/app/data/source/database/room/Converters.kt b/template/app/src/mobileAndDesktopMain/kotlin/kotli/app/data/source/database/room/Converters.kt new file mode 100644 index 00000000..ce802382 --- /dev/null +++ b/template/app/src/mobileAndDesktopMain/kotlin/kotli/app/data/source/database/room/Converters.kt @@ -0,0 +1,37 @@ +package kotli.app.data.source.database.room + +import androidx.room.TypeConverter +import kotlinx.datetime.Instant +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toInstant +import kotlinx.datetime.toLocalDateTime + +/** + * Class containing Room type converters for Date objects. + */ +class Converters { + + /** + * Converts a timestamp value (Long) to a Date object. + * + * @param value The timestamp value to convert. + * @return The corresponding Date object, or null if the input value is null. + */ + @TypeConverter + fun fromTimestamp(value: Long?): LocalDateTime? { + return value?.let(Instant::fromEpochMilliseconds)?.toLocalDateTime(TimeZone.UTC) + } + + /** + * Converts a Date object to a timestamp value (Long). + * + * @param date The Date object to convert. + * @return The corresponding timestamp value, or null if the input date is null. + */ + @TypeConverter + fun dateToTimestamp(date: LocalDateTime?): Long? { + return date?.toInstant(TimeZone.UTC)?.toEpochMilliseconds() + } + +} \ No newline at end of file diff --git a/template/app/src/mobileAndDesktopMain/kotlin/kotli/app/data/source/database/room/dao/UserDao.kt b/template/app/src/mobileAndDesktopMain/kotlin/kotli/app/data/source/database/room/dao/UserDao.kt new file mode 100644 index 00000000..3c76d0a2 --- /dev/null +++ b/template/app/src/mobileAndDesktopMain/kotlin/kotli/app/data/source/database/room/dao/UserDao.kt @@ -0,0 +1,43 @@ +package kotli.app.data.source.database.room.dao + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.Query +import androidx.room.Update +import kotli.app.data.source.database.room.entity.User +import kotlinx.coroutines.flow.Flow + +/** + * Represents a DAO (Data Access Object) for interacting with the [User] domain. + * + * This interface serves as an example of a typical DAO. + * + * For more information on the anatomy of a DAO, refer to: + * [https://developer.android.com/training/data-storage/room/accessing-data#kotlin] + */ +@Dao +interface UserDao { + + @Insert + suspend fun create(user: User): Long + + @Update + suspend fun update(vararg users: User) + + @Delete + suspend fun delete(vararg user: User) + + @Query("SELECT * FROM user WHERE id = :id LIMIT 1") + suspend fun get(id: Long): User? + + @Query("SELECT * FROM user") + suspend fun getAll(): List + + @Query("SELECT * FROM user") + fun getAllAsFlow(): Flow> + + @Query("SELECT COUNT(*) FROM user") + suspend fun count(): Long + +} \ No newline at end of file diff --git a/template/app/src/mobileAndDesktopMain/kotlin/kotli/app/data/source/database/room/entity/User.kt b/template/app/src/mobileAndDesktopMain/kotlin/kotli/app/data/source/database/room/entity/User.kt new file mode 100644 index 00000000..cd151e72 --- /dev/null +++ b/template/app/src/mobileAndDesktopMain/kotlin/kotli/app/data/source/database/room/entity/User.kt @@ -0,0 +1,27 @@ +package kotli.app.data.source.database.room.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import kotlinx.datetime.Clock +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime + +/** + * This class represents a User entity in the database. + *

+ * This entity is provided as an example to define custom entities in Room. + * Developers should create their own entities tailored to their application's requirements. + */ +@Entity(tableName = "user") +data class User( + @PrimaryKey(autoGenerate = true) + var id: Long = 0L, + @ColumnInfo(name = "first_name", index = true, collate = ColumnInfo.NOCASE) + var firstName: String? = null, + @ColumnInfo(name = "last_name", index = true, collate = ColumnInfo.NOCASE) + var lastName: String? = null, + @ColumnInfo(name = "created") + var created: LocalDateTime = Clock.System.now().toLocalDateTime(TimeZone.UTC) +) \ No newline at end of file diff --git a/template/app/src/mobileAndDesktopMain/kotlin/kotli/app/presentation/showcases/dataflow/room/crud/RoomCrudViewModel.mobileAndDesktop.kt b/template/app/src/mobileAndDesktopMain/kotlin/kotli/app/presentation/showcases/dataflow/room/crud/RoomCrudViewModel.mobileAndDesktop.kt new file mode 100644 index 00000000..29cc7426 --- /dev/null +++ b/template/app/src/mobileAndDesktopMain/kotlin/kotli/app/presentation/showcases/dataflow/room/crud/RoomCrudViewModel.mobileAndDesktop.kt @@ -0,0 +1,46 @@ +package kotli.app.presentation.showcases.dataflow.room.crud + +import kotli.app.data.source.database.room.AppRoomSource +import kotli.app.data.source.database.room.entity.User +import kotli.app.di.get +import kotli.app.presentation.showcases.dataflow.room.crud.model.UserData +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.map +import shared.presentation.navigation.NavigationStore + +actual fun createRoomCrudViewModel(): RoomCrudViewModel = RoomCrudViewModelImpl( + navigationStore = get(), + roomSource = get() +) + +private class RoomCrudViewModelImpl( + navigationStore: NavigationStore, + private val roomSource: AppRoomSource +) : RoomCrudViewModel(navigationStore) { + + override fun doBind() { + launchAsync("getUsers") { + roomSource.userDao + .getAllAsFlow().map { users -> + users.map { user -> + UserData(user.id, user.firstName, user.lastName) + } + } + .collectLatest(usersState::set) + } + } + + override fun onAdd() = launchAsync { + val dao = roomSource.userDao + val count = dao.count() + 1 + val firstName = "first_name_$count" + val lastName = "last_name_$count" + dao.create(User(firstName = firstName, lastName = lastName)) + } + + override fun onDelete(user: UserData) = launchAsync { + val dao = roomSource.userDao + dao.delete(User(user.id)) + } + +} \ No newline at end of file diff --git a/template/gradle.properties b/template/gradle.properties index 198a2cd3..b9bcec36 100644 --- a/template/gradle.properties +++ b/template/gradle.properties @@ -5,4 +5,5 @@ android.nonTransitiveRClass=true org.jetbrains.compose.experimental.jscanvas.enabled=true kotlin.mpp.enableCInteropCommonization=true kotlin.native.ignoreDisabledTargets=true +kotlin.native.disableCompilerDaemon=true kotlin.js.yarn=true \ No newline at end of file diff --git a/template/gradle/libs.versions.toml b/template/gradle/libs.versions.toml index 52f944df..4e2cc8b0 100644 --- a/template/gradle/libs.versions.toml +++ b/template/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "8.4.2" +agp = "8.3.2" android-compileSdk = "34" android-jvmTarget = "11" android-minSdk = "24" @@ -8,22 +8,23 @@ androidx-activity = "1.9.0" androidx-appcompat = "1.7.0" androidx-lifecycle = "2.8.0" androidx-navigation = "2.7.0-alpha07" -androidx-paging = "3.3.0" +androidx-room = "2.7.0-alpha05" androidx-splashscreen = "1.0.1" cashapp-paging = "3.3.0-alpha02-0.5.1" compose-multiplatform = "1.6.11" -junit = "4.13.2" koin = "3.6.0-Beta4" kotlin = "2.0.0" kotlinx-coroutines = "1.8.1" kotlinx-datetime = "0.6.0" kotlinx-serialization-json = "1.6.3" ktor = "2.3.11" +ksp = "2.0.0-1.0.22" logback = "1.5.6" multiplatform-settings = "1.1.1" napier = "2.7.1" skie = "0.8.2" sqldelight = "2.0.2" +sqlite-bundled = "2.5.0-alpha05" touchlab-stately = "2.0.7" touchlab-kermit = "2.0.3" @@ -32,15 +33,15 @@ androidx-activity-compose = { module = "androidx.activity:activity-compose", ver androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" } androidx-lifecycle-viewmodel-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" } androidx-navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "androidx-navigation" } -androidx-paging-compose = { module = "androidx.paging:paging-compose", version.ref = "androidx-paging" } +androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "androidx-room" } +androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "androidx-room" } +androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "androidx-room" } androidx-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "androidx-splashscreen" } cashapp-paging-compose-common = { module = "app.cash.paging:paging-compose-common", version.ref = "cashapp-paging" } -junit = { group = "junit", name = "junit", version.ref = "junit" } koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" } kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" } -kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" } kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" } kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } @@ -72,6 +73,7 @@ sqldelight-coroutines = { module = "app.cash.sqldelight:coroutines-extensions", sqldelight-native-driver = { module = "app.cash.sqldelight:native-driver", version.ref = "sqldelight" } sqldelight-sqlite-driver = { module = "app.cash.sqldelight:sqlite-driver", version.ref = "sqldelight" } sqldelight-web-worker-driver = { module = "app.cash.sqldelight:web-worker-driver", version.ref = "sqldelight" } +sqlite-bundled = { module = "androidx.sqlite:sqlite-bundled", version.ref = "sqlite-bundled" } touchlab-kermit = { module = "co.touchlab:kermit", version.ref = "touchlab-kermit" } touchlab-stately-common = { module = "co.touchlab:stately-common", version.ref = "touchlab-stately" } touchlab-stately-concurrent-collections = { module = "co.touchlab:stately-concurrent-collections", version.ref = "touchlab-stately" } @@ -90,7 +92,9 @@ compose-multiplatform = { id = "org.jetbrains.compose", version.ref = "compose-m kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } +ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } ktor = { id = "io.ktor.plugin", version.ref = "ktor" } +room = { id = "androidx.room", version.ref = "androidx-room" } skie = { id = "co.touchlab.skie", version.ref = "skie" } sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" } diff --git a/template/shared/data/src/commonMain/kotlin/shared/data/source/cache/CacheEntry.kt b/template/shared/data/src/commonMain/kotlin/shared/data/source/cache/CacheEntry.kt index 5147d540..e3adf036 100644 --- a/template/shared/data/src/commonMain/kotlin/shared/data/source/cache/CacheEntry.kt +++ b/template/shared/data/src/commonMain/kotlin/shared/data/source/cache/CacheEntry.kt @@ -9,45 +9,45 @@ import kotlinx.coroutines.flow.update * * @param T The type of the cached value. */ -interface CacheEntry { +interface CacheEntry> { /** The key associated with this cache entry. */ - val key: CacheKey + val key: K /** * Sets the specified value to the given entry. * * @param value The value to be stored in the entry. */ - suspend fun setValue(value: T?) + suspend fun set(value: T?) /** * Retrieves the cached value. * * @return The cached value, or new one if the value is not present in the cache or expired. */ - suspend fun getValue(): T? + suspend fun get(): T? /** * Retrieves the last cached value. * * @return The last cached value, or null if the value is not present in the cache. */ - suspend fun getLast(): T? + suspend fun last(): T? /** * Retrieves a fresh copy of the cached value. * * @return A fresh copy of the cached value, or null if the value is not available. */ - suspend fun getFresh(): T? + suspend fun fresh(): T? /** * Retrieves the last cached value if available, otherwise retrieves a fresh copy of the value. * * @return The last cached value if available, or a fresh copy of the value. Returns null if the value is not present in the cache. */ - suspend fun getLastOrFresh() = getLast() ?: getFresh() + suspend fun lastOrFresh() = last() ?: fresh() /** * Emits the cached value whenever it changes. @@ -55,7 +55,7 @@ interface CacheEntry { * * @return A flow representing the changes to the cached value. */ - suspend fun getChanges(): Flow + suspend fun changes(): Flow companion object { /** @@ -65,16 +65,17 @@ interface CacheEntry { * @param value The cached value. * @return A CacheState instance representing the single cached value. */ - fun of(key: CacheKey, value: T): CacheEntry = object : CacheEntry { - private val valueChanges = MutableStateFlow(value) + fun > of(key: K, value: T): CacheEntry = + object : CacheEntry { + private val valueChanges = MutableStateFlow(value) - override val key: CacheKey = key - override suspend fun getValue(): T? = value - override suspend fun getLast(): T? = value - override suspend fun getFresh(): T? = value - override suspend fun getChanges(): Flow = valueChanges - override suspend fun setValue(value: T?) = valueChanges.update { value } - } + override val key: K = key + override suspend fun get(): T? = value + override suspend fun last(): T? = value + override suspend fun fresh(): T? = value + override suspend fun changes(): Flow = valueChanges + override suspend fun set(value: T?) = valueChanges.update { value } + } } } \ No newline at end of file diff --git a/template/shared/data/src/commonMain/kotlin/shared/data/source/cache/CacheSource.kt b/template/shared/data/src/commonMain/kotlin/shared/data/source/cache/CacheSource.kt index 1d93fd66..4dbdbf3a 100644 --- a/template/shared/data/src/commonMain/kotlin/shared/data/source/cache/CacheSource.kt +++ b/template/shared/data/src/commonMain/kotlin/shared/data/source/cache/CacheSource.kt @@ -20,7 +20,7 @@ interface CacheSource : DataSource { * @param valueProvider A suspend function that provides the value if the cache entry is not found. * @return A CacheState object representing the state of the cache entry. */ - fun get(key: CacheKey, valueProvider: suspend () -> T?): CacheEntry + fun > get(key: K, valueProvider: suspend (key: K) -> T?): CacheEntry /** * Invalidates all cache entries associated with the specified key type. diff --git a/template/shared/data/src/commonMain/kotlin/shared/data/source/cache/InMemoryCacheSource.kt b/template/shared/data/src/commonMain/kotlin/shared/data/source/cache/InMemoryCacheSource.kt index 5b56a5ed..1500abc4 100644 --- a/template/shared/data/src/commonMain/kotlin/shared/data/source/cache/InMemoryCacheSource.kt +++ b/template/shared/data/src/commonMain/kotlin/shared/data/source/cache/InMemoryCacheSource.kt @@ -41,13 +41,16 @@ open class InMemoryCacheSource( private val dispatcher = Dispatchers.Default private val jobs = ConcurrentMutableMap, Deferred<*>>() - private val cache = ConcurrentMutableMap, EntryData<*>>() + private val cache = ConcurrentMutableMap, EntryData<*, *>>() - override fun get(key: CacheKey, valueProvider: suspend () -> T?): CacheEntry { + override fun > get( + key: K, + valueProvider: suspend (key: K) -> T? + ): CacheEntry { val keyData = KeyData(key) val entryData = cache.computeIfAbsent(keyData) { EntryData(keyData, valueProvider) - } as CacheEntry + } as CacheEntry return entryData } @@ -108,8 +111,8 @@ open class InMemoryCacheSource( cache.remove(cacheKey) } - private data class KeyData( - val key: CacheKey, + private data class KeyData>( + val key: K, val type: KClass<*> = key::class ) @@ -118,25 +121,25 @@ open class InMemoryCacheSource( val updateTime: Long = Clock.System.now().toEpochMilliseconds() ) - private inner class EntryData( - private val keyData: KeyData, - private val valueProvider: suspend () -> T? - ) : CacheEntry { + private inner class EntryData>( + private val keyData: KeyData, + private val valueProvider: suspend (key: K) -> T? + ) : CacheEntry { @Transient private var invalidated = false private val liveChanges by lazy { fetchLiveChanges() } private val changes = MutableStateFlow?>(null) - override val key: CacheKey = keyData.key + override val key: K = keyData.key - override suspend fun setValue(value: T?) = changes.update { value?.let(::EntrySnapshot) } + override suspend fun set(value: T?) = changes.update { value?.let(::EntrySnapshot) } - override suspend fun getFresh(): T? = invalidate().run { getValue() } + override suspend fun fresh(): T? = invalidate().run { get() } - override suspend fun getLast(): T? = changes.value?.value + override suspend fun last(): T? = changes.value?.value - override suspend fun getValue(): T? { + override suspend fun get(): T? { return if (!isValid(key.ttl)) { val newValue = fetchValue() changes.updateAndGet { newValue?.let(::EntrySnapshot) }?.value @@ -145,7 +148,7 @@ open class InMemoryCacheSource( } } - override suspend fun getChanges(): Flow = liveChanges + override suspend fun changes(): Flow = liveChanges .flatMapLatest { changes } .map { snapshot -> snapshot?.value } .retry { th -> !th.isCancellationException().also { delay(changesRetryInterval) } } @@ -180,12 +183,12 @@ open class InMemoryCacheSource( ?: run { if (key.immortal()) { jobs.computeIfAbsent(keyData) { - GlobalScope.async { valueProvider() } + GlobalScope.async { valueProvider(key) } } } else { withContext(dispatcher) { jobs.computeIfAbsent(keyData) { - async { valueProvider() } + async { valueProvider(key) } } } } @@ -199,8 +202,8 @@ open class InMemoryCacheSource( emit(true) var retryAttempt = 0 while (currentCoroutineContext().isActive) { - try { - getValue() + runCatching { + get() val updateTime = changes.value?.updateTime if (updateTime == null) { @@ -212,10 +215,10 @@ open class InMemoryCacheSource( } retryAttempt = 0 - } catch (e: Exception) { + }.onFailure { th -> retryAttempt++ when { - retryAttempt >= exceptionRetryCount -> throw e + retryAttempt >= exceptionRetryCount -> throw th else -> delay(exceptionRetryInterval) } } diff --git a/template/shared/data/src/jvmTest/kotlin/shared/data/source/cache/InMemoryCacheSourceTest.kt b/template/shared/data/src/jvmTest/kotlin/shared/data/source/cache/InMemoryCacheSourceTest.kt index 219d06db..9657ad0e 100644 --- a/template/shared/data/src/jvmTest/kotlin/shared/data/source/cache/InMemoryCacheSourceTest.kt +++ b/template/shared/data/src/jvmTest/kotlin/shared/data/source/cache/InMemoryCacheSourceTest.kt @@ -29,7 +29,7 @@ class InMemoryCacheSourceTest { delay(2.seconds) iteration } - .getValue() + .get() ?.let(cached::add) } } @@ -50,7 +50,7 @@ class InMemoryCacheSourceTest { delay(1.seconds) UUID.randomUUID() } - .getValue() + .get() ?.let(cached::add) } } @@ -71,7 +71,7 @@ class InMemoryCacheSourceTest { delay(1.seconds) UUID.randomUUID() } - .getChanges() + .changes() .filterNotNull() .take(1) .first() @@ -86,14 +86,14 @@ class InMemoryCacheSourceTest { fun `check cached state logic`() = runBlocking { val key = UUIDCacheKey(Int.MAX_VALUE, ttl = 100) val entry = cache.get(key) { UUID.randomUUID() } - val value1 = entry.getValue() - val value1Last = entry.getLast() + val value1 = entry.get() + val value1Last = entry.last() delay(100) - val value2 = entry.getValue() + val value2 = entry.get() delay(100) assertNotEquals(value1, value2) assertEquals(value1, value1Last) - assertNotEquals(value2, entry.getValue()) + assertNotEquals(value2, entry.get()) } private data class TestCacheKey(