WIP: Move app to use compose multiplatform

pull/2064/head
lihenggui 1 year ago
parent b16f78c83a
commit 539d36fb0b

@ -14,16 +14,14 @@
* limitations under the License.
*/
import com.google.samples.apps.nowinandroid.NiaBuildType
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetTree
plugins {
alias(libs.plugins.nowinandroid.android.application)
alias(libs.plugins.nowinandroid.android.application.compose)
alias(libs.plugins.nowinandroid.android.application.flavors)
alias(libs.plugins.nowinandroid.android.application.jacoco)
alias(libs.plugins.nowinandroid.android.application.firebase)
alias(libs.plugins.nowinandroid.hilt)
id("com.google.android.gms.oss-licenses-plugin")
alias(libs.plugins.baselineprofile)
alias(libs.plugins.nowinandroid.cmp.application)
alias(libs.plugins.nowinandroid.di.koin)
// alias(libs.plugins.baselineprofile)
alias(libs.plugins.roborazzi)
alias(libs.plugins.kotlin.serialization)
}
@ -34,8 +32,7 @@ android {
versionCode = 8
versionName = "0.1.2" // X.Y.Z; X = Major, Y = minor, Z = Patch level
// Custom test runner to set up Hilt dependency graph
testInstrumentationRunner = "com.google.samples.apps.nowinandroid.core.testing.NiaTestRunner"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
@ -55,7 +52,7 @@ android {
// TODO: Abstract the signing configuration to a separate file to avoid hardcoding this.
signingConfig = signingConfigs.named("debug").get()
// Ensure Baseline Profile is fresh for release builds.
baselineProfile.automaticGenerationDuringBuild = true
// baselineProfile.automaticGenerationDuringBuild = true
}
}
@ -72,76 +69,127 @@ android {
namespace = "com.google.samples.apps.nowinandroid"
}
kotlin {
androidTarget {
@OptIn(ExperimentalKotlinGradlePluginApi::class)
instrumentedTestVariant.sourceSetTree.set(KotlinSourceSetTree.test)
}
sourceSets {
commonMain.dependencies {
implementation(projects.feature.interests)
implementation(projects.feature.foryou)
implementation(projects.feature.bookmarks)
implementation(projects.feature.topic)
implementation(projects.feature.search)
implementation(projects.feature.settings)
implementation(projects.core.common)
implementation(projects.core.ui)
implementation(projects.core.designsystem)
implementation(projects.core.data)
implementation(projects.core.model)
implementation(projects.core.analytics)
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material)
implementation(compose.ui)
implementation(compose.components.resources)
implementation(compose.components.uiToolingPreview)
// implementation(projects.sync.work)
implementation(libs.coil.core)
implementation(libs.coil.compose)
implementation(libs.kotlinx.serialization.json)
}
androidMain.dependencies {
implementation(compose.preview)
implementation(project.dependencies.platform(libs.androidx.compose.bom))
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.compose.material3.adaptive)
implementation(libs.androidx.compose.material3.adaptive.layout)
implementation(libs.androidx.compose.material3.adaptive.navigation)
implementation(libs.androidx.compose.material3.windowSizeClass)
implementation(libs.androidx.compose.runtime.tracing)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.core.splashscreen)
implementation(libs.androidx.lifecycle.runtimeCompose)
implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.profileinstaller)
implementation(libs.androidx.tracing.ktx)
implementation(libs.androidx.window.core)
implementation(libs.kotlinx.coroutines.guava)
implementation(libs.koin.android)
}
commonTest.dependencies {
implementation(projects.core.dataTest)
implementation(projects.core.testing)
implementation(projects.core.screenshotTesting)
// implementation(projects.sync.syncTest)
implementation(libs.kotlin.test)
@OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class)
implementation(compose.uiTest)
}
androidInstrumentedTest.dependencies {
implementation(projects.core.dataTest)
implementation(projects.core.testing)
implementation(libs.androidx.test.espresso.core)
implementation(libs.androidx.navigation.testing)
implementation(project.dependencies.platform(libs.androidx.compose.bom))
implementation(libs.androidx.compose.ui.test)
}
jvmMain.dependencies {
implementation(compose.desktop.currentOs)
implementation(libs.kotlinx.coroutines.swing)
}
jvmTest.dependencies {
implementation(compose.desktop.uiTestJUnit4)
}
}
}
dependencies {
implementation(projects.feature.interests)
implementation(projects.feature.foryou)
implementation(projects.feature.bookmarks)
implementation(projects.feature.topic)
implementation(projects.feature.search)
implementation(projects.feature.settings)
implementation(projects.core.common)
implementation(projects.core.ui)
implementation(projects.core.designsystem)
implementation(projects.core.data)
implementation(projects.core.model)
implementation(projects.core.analytics)
implementation(projects.sync.work)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.compose.material3.adaptive)
implementation(libs.androidx.compose.material3.adaptive.layout)
implementation(libs.androidx.compose.material3.adaptive.navigation)
implementation(libs.androidx.compose.material3.windowSizeClass)
implementation(libs.androidx.compose.runtime.tracing)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.core.splashscreen)
implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.androidx.lifecycle.runtimeCompose)
implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.profileinstaller)
implementation(libs.androidx.tracing.ktx)
implementation(libs.androidx.window.core)
implementation(libs.kotlinx.coroutines.guava)
implementation(libs.coil)
implementation(libs.kotlinx.serialization.json)
ksp(libs.hilt.compiler)
debugImplementation(libs.androidx.compose.ui.testManifest)
debugImplementation(projects.uiTestHiltManifest)
kspTest(libs.hilt.compiler)
testImplementation(projects.core.dataTest)
testImplementation(libs.hilt.android.testing)
testImplementation(projects.sync.syncTest)
testImplementation(libs.kotlin.test)
testDemoImplementation(libs.robolectric)
testDemoImplementation(libs.roborazzi)
testDemoImplementation(projects.core.screenshotTesting)
androidTestImplementation(kotlin("test"))
androidTestImplementation(projects.core.testing)
androidTestImplementation(projects.core.dataTest)
androidTestImplementation(libs.androidx.test.espresso.core)
androidTestImplementation(libs.androidx.navigation.testing)
androidTestImplementation(libs.androidx.compose.ui.test)
androidTestImplementation(libs.hilt.android.testing)
baselineProfile(projects.benchmarks)
debugImplementation(compose.uiTooling)
androidTestImplementation(libs.androidx.compose.ui.test.android)
androidTestImplementation(libs.androidx.compose.ui.testManifest)
}
baselineProfile {
// Don't build on every iteration of a full assemble.
// Instead enable generation directly for the release build variant.
automaticGenerationDuringBuild = false
compose.desktop {
application {
mainClass = "com.google.sample.apps.nowinandroid.MainKt"
// Make use of Dex Layout Optimizations via Startup Profiles
dexLayoutOptimization = true
nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
packageName = "com.google.sample.apps.nowinandroid"
packageVersion = "1.0.0"
}
}
}
dependencyGuard {
configuration("prodReleaseRuntimeClasspath")
}
//dependencies {
//
// debugImplementation(libs.androidx.compose.ui.testManifest)
// debugImplementation(projects.uiTestHiltManifest)
//
//
//
// testDemoImplementation(libs.robolectric)
// testDemoImplementation(libs.roborazzi)
// testDemoImplementation(projects.core.screenshotTesting)
//
// baselineProfile(projects.benchmarks)
//}
//
//baselineProfile {
// // Don't build on every iteration of a full assemble.
// // Instead enable generation directly for the release build variant.
// automaticGenerationDuringBuild = false
//
// // Make use of Dex Layout Optimizations via Startup Profiles
// dexLayoutOptimization = true
//}
//

@ -31,27 +31,30 @@ import androidx.compose.ui.test.performScrollToNode
import androidx.test.espresso.Espresso
import androidx.test.espresso.NoActivityResumedException
import com.google.samples.apps.nowinandroid.MainActivity
import com.google.samples.apps.nowinandroid.R
import com.google.samples.apps.nowinandroid.core.data.repository.TopicsRepository
import com.google.samples.apps.nowinandroid.core.model.data.Topic
import com.google.samples.apps.nowinandroid.core.rules.GrantPostNotificationsPermissionRule
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import nowinandroid.app.generated.resources.Res
import nowinandroid.feature.bookmarks.generated.resources.feature_bookmarks_title
import nowinandroid.feature.foryou.generated.resources.feature_foryou_navigate_up
import nowinandroid.feature.foryou.generated.resources.feature_foryou_title
import nowinandroid.feature.search.generated.resources.feature_search_interests
import nowinandroid.feature.settings.generated.resources.feature_settings_brand_android
import nowinandroid.feature.settings.generated.resources.feature_settings_dismiss_dialog_button_text
import nowinandroid.feature.settings.generated.resources.feature_settings_top_app_bar_action_icon_description
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import javax.inject.Inject
import com.google.samples.apps.nowinandroid.feature.bookmarks.R as BookmarksR
import com.google.samples.apps.nowinandroid.feature.foryou.R as FeatureForyouR
import com.google.samples.apps.nowinandroid.feature.search.R as FeatureSearchR
import com.google.samples.apps.nowinandroid.feature.settings.R as SettingsR
import nowinandroid.feature.bookmarks.generated.resources.Res as BookmarksR
import nowinandroid.feature.foryou.generated.resources.Res as FeatureForyouR
import nowinandroid.feature.search.generated.resources.Res as FeatureSearchR
import nowinandroid.feature.settings.generated.resources.Res as SettingsR
/**
* Tests all the navigation flows that are handled by the navigation library.
*/
@HiltAndroidTest
class NavigationTest {
/**
@ -80,7 +83,7 @@ class NavigationTest {
private val forYou by composeTestRule.stringResource(FeatureForyouR.string.feature_foryou_title)
private val interests by composeTestRule.stringResource(FeatureSearchR.string.feature_search_interests)
private val sampleTopic = "Headlines"
private val appName by composeTestRule.stringResource(R.string.app_name)
private val appName by composeTestRule.stringResource(Res.string.app_name)
private val saved by composeTestRule.stringResource(BookmarksR.string.feature_bookmarks_title)
private val settings by composeTestRule.stringResource(SettingsR.string.feature_settings_top_app_bar_action_icon_description)
private val brand by composeTestRule.stringResource(SettingsR.string.feature_settings_brand_android)

@ -16,11 +16,18 @@
package com.google.samples.apps.nowinandroid.ui
import androidx.annotation.StringRes
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import kotlinx.coroutines.runBlocking
import org.jetbrains.compose.resources.StringResource
import kotlin.properties.ReadOnlyProperty
fun AndroidComposeTestRule<*, *>.stringResource(
@StringRes resId: Int,
resId: StringResource,
): ReadOnlyProperty<Any, String> =
ReadOnlyProperty { _, _ -> activity.getString(resId) }
ReadOnlyProperty { _, _ ->
runBlocking {
// TODO remove runBlocking
org.jetbrains.compose.resources.getString(resId)
}
}

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

@ -43,27 +43,22 @@ import com.google.samples.apps.nowinandroid.core.data.repository.UserNewsResourc
import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor
import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
import com.google.samples.apps.nowinandroid.core.di.ApplicationComponent
import com.google.samples.apps.nowinandroid.core.di.ApplicationComponentProvider
import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig
import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand
import com.google.samples.apps.nowinandroid.core.ui.LocalTimeZone
import com.google.samples.apps.nowinandroid.ui.NiaApp
import com.google.samples.apps.nowinandroid.ui.rememberNiaAppState
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import javax.inject.Inject
import org.koin.core.context.GlobalContext.get
@AndroidEntryPoint
class MainActivity : ComponentActivity(), ApplicationComponentProvider {
class MainActivity : ComponentActivity() {
/**
* Lazily inject [JankStats], which is used to track jank throughout the app.
*/
@Inject
lateinit var lazyStats: dagger.Lazy<JankStats>
private val lazyStats: JankStats = get()
@Inject
lateinit var networkMonitor: NetworkMonitor

@ -22,15 +22,12 @@ import com.google.samples.apps.nowinandroid.MainActivityUiState.Loading
import com.google.samples.apps.nowinandroid.MainActivityUiState.Success
import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository
import com.google.samples.apps.nowinandroid.core.model.data.UserData
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import javax.inject.Inject
@HiltViewModel
class MainActivityViewModel @Inject constructor(
class MainActivityViewModel(
userDataRepository: UserDataRepository,
) : ViewModel() {
val uiState: StateFlow<MainActivityUiState> = userDataRepository.userData.map {

@ -17,30 +17,45 @@
package com.google.samples.apps.nowinandroid
import android.app.Application
import coil.ImageLoader
import coil.ImageLoaderFactory
import com.google.samples.apps.nowinandroid.sync.initializers.Sync
import coil3.ImageLoader
import coil3.PlatformContext
import coil3.SingletonImageLoader
import coil3.request.crossfade
import com.google.samples.apps.nowinandroid.core.analytics.di.analyticsModule
import com.google.samples.apps.nowinandroid.core.di.CommonModule
import com.google.samples.apps.nowinandroid.core.di.commonModule
import com.google.samples.apps.nowinandroid.util.ProfileVerifierLogger
import dagger.hilt.android.HiltAndroidApp
import javax.inject.Inject
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger
import org.koin.core.context.startKoin
import org.koin.ksp.generated.module
/**
* [Application] class for NiA
*/
@HiltAndroidApp
class NiaApplication : Application(), ImageLoaderFactory {
@Inject
lateinit var imageLoader: dagger.Lazy<ImageLoader>
class NiaApplication : Application(), SingletonImageLoader.Factory {
@Inject
lateinit var profileVerifierLogger: ProfileVerifierLogger
override fun onCreate() {
super.onCreate()
startKoin {
androidLogger()
androidContext(this@NiaApplication)
val allModules =
modules(
CommonModule().module,
AnalyticModule().module,
)
}
// Initialize Sync; the system responsible for keeping data in the app up to date.
Sync.initialize(context = this)
// Sync.initialize(context = this)
profileVerifierLogger()
}
override fun newImageLoader(): ImageLoader = imageLoader.get()
override fun newImageLoader(context: PlatformContext): ImageLoader{
return ImageLoader.Builder(context)
.crossfade(true)
.build()
}
}

@ -21,15 +21,13 @@ import android.util.Log
import android.view.Window
import androidx.metrics.performance.JankStats
import androidx.metrics.performance.JankStats.OnFrameListener
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityComponent
import org.koin.core.annotation.Factory
import org.koin.core.annotation.Module
import org.koin.core.annotation.Single
@Module
@InstallIn(ActivityComponent::class)
object JankStatsModule {
@Provides
class JankStatsModule {
@Factory
fun providesOnFrameListener(): OnFrameListener = OnFrameListener { frameData ->
// Make sure to only log janky frames.
if (frameData.isJank) {
@ -38,10 +36,10 @@ object JankStatsModule {
}
}
@Provides
@Single
fun providesWindow(activity: Activity): Window = activity.window
@Provides
@Factory
fun providesJankStats(
window: Window,
frameListener: OnFrameListener,

@ -16,17 +16,21 @@
package com.google.samples.apps.nowinandroid.navigation
import androidx.annotation.StringRes
import androidx.compose.ui.graphics.vector.ImageVector
import com.google.samples.apps.nowinandroid.R
import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons
import com.google.samples.apps.nowinandroid.feature.bookmarks.navigation.BookmarksRoute
import com.google.samples.apps.nowinandroid.feature.foryou.navigation.ForYouRoute
import com.google.samples.apps.nowinandroid.feature.interests.navigation.InterestsRoute
import nowinandroid.app.generated.resources.Res
import nowinandroid.feature.bookmarks.generated.resources.feature_bookmarks_title
import nowinandroid.feature.foryou.generated.resources.feature_foryou_title
import nowinandroid.feature.search.generated.resources.feature_search_interests
import org.jetbrains.compose.resources.StringResource
import kotlin.reflect.KClass
import com.google.samples.apps.nowinandroid.feature.bookmarks.R as bookmarksR
import com.google.samples.apps.nowinandroid.feature.foryou.R as forYouR
import com.google.samples.apps.nowinandroid.feature.search.R as searchR
import nowinandroid.feature.bookmarks.generated.resources.Res as bookmarksR
import nowinandroid.feature.foryou.generated.resources.Res as forYouR
import nowinandroid.feature.search.generated.resources.Res as searchR
/**
* Type for the top level destinations in the application. Each of these destinations
@ -36,15 +40,15 @@ import com.google.samples.apps.nowinandroid.feature.search.R as searchR
enum class TopLevelDestination(
val selectedIcon: ImageVector,
val unselectedIcon: ImageVector,
@StringRes val iconTextId: Int,
@StringRes val titleTextId: Int,
val iconTextId: StringResource,
val titleTextId: StringResource,
val route: KClass<*>,
) {
FOR_YOU(
selectedIcon = NiaIcons.Upcoming,
unselectedIcon = NiaIcons.UpcomingBorder,
iconTextId = forYouR.string.feature_foryou_title,
titleTextId = R.string.app_name,
titleTextId = Res.string.app_name,
route = ForYouRoute::class,
),
BOOKMARKS(

@ -20,14 +20,13 @@ import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.navigation.toRoute
import com.google.samples.apps.nowinandroid.feature.interests.navigation.InterestsRoute
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Inject
import org.koin.android.annotation.KoinViewModel
const val TOPIC_ID_KEY = "selectedTopicId"
@HiltViewModel
class Interests2PaneViewModel @Inject constructor(
@KoinViewModel
class Interests2PaneViewModel(
private val savedStateHandle: SavedStateHandle,
) : ViewModel() {

@ -37,7 +37,6 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.NavHost
@ -50,6 +49,7 @@ import com.google.samples.apps.nowinandroid.feature.topic.navigation.TopicRoute
import com.google.samples.apps.nowinandroid.feature.topic.navigation.navigateToTopic
import com.google.samples.apps.nowinandroid.feature.topic.navigation.topicScreen
import kotlinx.serialization.Serializable
import org.koin.compose.viewmodel.koinViewModel
import java.util.UUID
@Serializable internal object TopicPlaceholderRoute
@ -66,7 +66,7 @@ fun NavGraphBuilder.interestsListDetailScreen() {
@Composable
internal fun InterestsListDetailScreen(
viewModel: Interests2PaneViewModel = hiltViewModel(),
viewModel: Interests2PaneViewModel = koinViewModel(),
windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo(),
) {
val selectedTopicId by viewModel.selectedTopicId.collectAsStateWithLifecycle()

@ -22,7 +22,6 @@ import com.google.samples.apps.nowinandroid.core.di.ApplicationScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.guava.await
import kotlinx.coroutines.launch
import javax.inject.Inject
/**
* Logs the app's Baseline Profile Compilation Status using [ProfileVerifier].
@ -48,7 +47,7 @@ import javax.inject.Inject
*
* @see androidx.profileinstaller.ProfileVerifier.CompilationStatus.ResultCode
*/
class ProfileVerifierLogger @Inject constructor(
class ProfileVerifierLogger(
@ApplicationScope private val scope: CoroutineScope,
) {
companion object {

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2023 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<!-- Allow users to distinguish between build variants by having a different background color
for the launcher icon. See https://github.com/android/nowinandroid/pull/989. -->
<color name="ic_launcher_background_tint">#000000</color>
<color name="ic_launcher_foreground_tint">#FF006780</color>
</resources>

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2023 The Android Open Source Project
Copyright 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -15,8 +15,6 @@
limitations under the License.
-->
<resources>
<!-- Allow users to distinguish between build variants by having a different background color
for the launcher icon. See https://github.com/android/nowinandroid/pull/989. -->
<color name="ic_launcher_background_tint">#FFFFFF</color>
<color name="ic_launcher_foreground_tint">#FF006780</color>
<string name="app_name">Now in Android</string>
<string name="not_connected">⚠️ You arent connected to the internet</string>
</resources>

@ -129,6 +129,10 @@ gradlePlugin {
id = "nowinandroid.cmp.feature"
implementationClass = "CmpFeatureConventionPlugin"
}
register("cmpApplication") {
id = "nowinandroid.cmp.application"
implementationClass = "CmpApplicationConventionPlugin"
}
register("koin") {
id = "nowinandroid.di.koin"
implementationClass = "KoinConventionPlugin"

@ -0,0 +1,127 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import com.android.build.api.dsl.ApplicationExtension
import com.android.build.api.variant.ApplicationAndroidComponentsExtension
import com.android.build.gradle.BaseExtension
import com.android.build.gradle.LibraryExtension
import com.google.samples.apps.nowinandroid.configureBadgingTasks
import com.google.samples.apps.nowinandroid.configureGradleManagedDevices
import com.google.samples.apps.nowinandroid.configureKotlinAndroid
import com.google.samples.apps.nowinandroid.configureKotlinMultiplatform
import com.google.samples.apps.nowinandroid.configurePrintApksTask
import com.google.samples.apps.nowinandroid.libs
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.getByType
import org.jetbrains.kotlin.compose.compiler.gradle.ComposeCompilerGradlePluginExtension
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
// Convention plugin for the Compose Multiplatform feature module
class CmpApplicationConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
pluginManager.apply {
apply("com.android.application")
apply("org.jetbrains.kotlin.multiplatform")
apply("org.jetbrains.kotlin.plugin.compose")
apply("org.jetbrains.compose")
// apply("com.dropbox.dependency-guard")
}
configureComposeMultiplatformApp()
extensions.configure<ApplicationExtension> {
configureKotlinAndroid(this)
defaultConfig.targetSdk = 34
@Suppress("UnstableApiUsage")
testOptions.animationsDisabled = true
configureGradleManagedDevices(this)
}
extensions.configure<ApplicationAndroidComponentsExtension> {
configurePrintApksTask(this)
configureBadgingTasks(extensions.getByType<BaseExtension>(), this)
}
dependencies {
add("commonMainImplementation", project(":core:ui"))
add("commonMainImplementation", project(":core:designsystem"))
add("commonMainImplementation", libs.findLibrary("jetbrains.compose.viewmodel").get())
add("commonMainImplementation", libs.findLibrary("jetbrains.compose.navigation").get())
add("commonMainImplementation", libs.findLibrary("koin.compose").get())
add("commonMainImplementation", libs.findLibrary("koin.compose.viewmodel").get())
add("commonMainImplementation", libs.findLibrary("koin.compose.viewmodel.navigation").get())
add("androidMainImplementation", libs.findLibrary("androidx.lifecycle.runtimeCompose").get())
add("androidMainImplementation", libs.findLibrary("androidx.lifecycle.viewModelCompose").get())
add("androidMainImplementation", libs.findLibrary("androidx.tracing.ktx").get())
}
}
}
}
private fun Project.configureComposeMultiplatformApp() {
extensions.configure<KotlinMultiplatformExtension> {
// Enable native group by default
// https://kotlinlang.org/docs/whatsnew1820.html#new-approach-to-source-set-hierarchy
applyDefaultHierarchyTemplate()
// Configure JVM target for Android
androidTarget {
@OptIn(ExperimentalKotlinGradlePluginApi::class)
compilerOptions {
jvmTarget.set(JvmTarget.JVM_11)
}
}
// Add JVM target for desktop
jvm()
// Configure iOS targets
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = "NowInAndroid"
isStatic = true
}
}
// Other targets
macosX64()
macosArm64()
// Suppress 'expect'/'actual' classes are in Beta.
targets.configureEach {
compilations.configureEach {
compilerOptions.configure {
freeCompilerArgs.addAll("-Xexpect-actual-classes")
}
}
}
// Fixes Cannot locate tasks that match ':core:model:testClasses' as task 'testClasses'
// not found in project ':core:model'. Some candidates are: 'jsTestClasses', 'jvmTestClasses'.
project.tasks.create("testClasses") {
dependsOn("allTests")
}
}
}

@ -15,7 +15,6 @@
*/
import com.android.build.gradle.LibraryExtension
import com.google.samples.apps.nowinandroid.configureFlavors
import com.google.samples.apps.nowinandroid.configureGradleManagedDevices
import com.google.samples.apps.nowinandroid.configureKotlinAndroid
import com.google.samples.apps.nowinandroid.configureKotlinMultiplatform
@ -34,7 +33,6 @@ class KmpLibraryConventionPlugin: Plugin<Project> {
extensions.configure<LibraryExtension> {
configureKotlinAndroid(this)
defaultConfig.targetSdk = 34
configureFlavors(this)
configureGradleManagedDevices(this)
// The resource prefix is derived from the module name,
// so resources inside ":core:module1" must be prefixed with "core_module1_"

@ -14,26 +14,13 @@
* limitations under the License.
*/
import com.android.build.api.variant.LibraryAndroidComponentsExtension
import com.android.build.gradle.LibraryExtension
import com.google.samples.apps.nowinandroid.configureFlavors
import com.google.samples.apps.nowinandroid.configureGradleManagedDevices
import com.google.samples.apps.nowinandroid.configureKotlinAndroid
import com.google.samples.apps.nowinandroid.configurePrintApksTask
import com.google.samples.apps.nowinandroid.disableUnnecessaryAndroidTests
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.kotlin
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
class SqlDelightConventionPlugin: Plugin<Project> {
override fun apply(target: Project) {
with(target) {
pluginManager.apply("app.cash.sqldelight")
extensions.configure<KotlinMultiplatformExtension> {
}
}
}
}

@ -21,7 +21,6 @@ import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.withType
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.konan.target.HostManager
@ -30,7 +29,6 @@ import org.jetbrains.kotlin.konan.target.HostManager
* A plugin that applies the Kotlin Multiplatform plugin and configures it for the project.
* https://github.com/cashapp/sqldelight/blob/master/buildLogic/multiplatform-convention/src/main/kotlin/app/cash/sqldelight/multiplatform/MultiplatformConventions.kt
*/
@OptIn(ExperimentalWasmDsl::class)
internal fun Project.configureKotlinMultiplatform() {
extensions.configure<KotlinMultiplatformExtension> {
// Enable native group by default

@ -46,3 +46,8 @@ kotlin {
}
}
}
compose.resources {
publicResClass = true
generateResClass = always
}

@ -56,3 +56,8 @@ kotlin {
}
}
}
compose.resources {
publicResClass = true
generateResClass = always
}

@ -51,3 +51,8 @@ kotlin {
}
}
}
compose.resources {
publicResClass = true
generateResClass = always
}

@ -53,4 +53,5 @@ kotlin {
compose.resources {
publicResClass = true
generateResClass = always
}

@ -49,6 +49,8 @@ kotlin {
}
}
compose.resources {
publicResClass = true
generateResClass = always
}

@ -55,4 +55,5 @@ kotlin {
compose.resources {
publicResClass = true
generateResClass = always
}

@ -97,6 +97,7 @@ androidx-compose-material3-windowSizeClass = { group = "androidx.compose.materia
androidx-compose-runtime = { group = "androidx.compose.runtime", name = "runtime" }
androidx-compose-runtime-tracing = { group = "androidx.compose.runtime", name = "runtime-tracing", version.ref = "androidxComposeRuntimeTracing" }
androidx-compose-ui-test = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-compose-ui-test-android = { group = "androidx.compose.ui", name = "ui-test-junit4-android" }
androidx-compose-ui-testManifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
@ -162,6 +163,7 @@ kotlin-test = { group = "org.jetbrains.kotlin", name = "kotlin-test", version.re
kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" }
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinxCoroutines" }
kotlinx-coroutines-guava = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-guava", version.ref = "kotlinxCoroutines" }
kotlinx-coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-swing", version.ref = "kotlinxCoroutines" }
kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" }
kotlinx-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version.ref = "kotlinxDatetime" }
kotlinx-serialization-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-core", version.ref = "kotlinxSerializationJson" }
@ -276,4 +278,5 @@ nowinandroid-kmp-library = { id = "nowinandroid.kmp.library" }
nowinandroid-kotlin-inject = { id = "nowinandroid.kmp.inject" }
nowinandroid-sqldelight = { id = "nowinandroid.sqldelight" }
nowinandroid-cmp-feature = { id = "nowinandroid.cmp.feature" }
nowinandroid-cmp-application = { id = "nowinandroid.cmp.application" }
nowinandroid-di-koin = { id = "nowinandroid.di.koin" }

Loading…
Cancel
Save