diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index fdbae43f9..803bb7f78 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -92,6 +92,8 @@ kotlin {
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material)
+ implementation(compose.material3)
+ implementation(compose.material3AdaptiveNavigationSuite)
implementation(compose.ui)
implementation(compose.components.resources)
implementation(compose.components.uiToolingPreview)
@@ -100,6 +102,7 @@ kotlin {
implementation(libs.coil.core)
implementation(libs.coil.compose)
implementation(libs.kotlinx.serialization.json)
+ implementation(libs.jetbrains.compose.material3.adaptive.navigation)
}
androidMain.dependencies {
diff --git a/app/src/androidInstrumentedTest/kotlin/com/google/samples/apps/nowinandroid/ui/NavigationTest.kt b/app/src/androidInstrumentedTest/kotlin/com/google/samples/apps/nowinandroid/ui/NavigationTest.kt
index 76f48e55b..59b031f7a 100644
--- a/app/src/androidInstrumentedTest/kotlin/com/google/samples/apps/nowinandroid/ui/NavigationTest.kt
+++ b/app/src/androidInstrumentedTest/kotlin/com/google/samples/apps/nowinandroid/ui/NavigationTest.kt
@@ -60,8 +60,8 @@ class NavigationTest {
/**
* Manages the components' state and is used to perform injection on your test
*/
- @get:Rule(order = 0)
- val hiltRule = HiltAndroidRule(this)
+// @get:Rule(order = 0)
+// val hiltRule = HiltAndroidRule(this)
/**
* Grant [android.Manifest.permission.POST_NOTIFICATIONS] permission.
@@ -89,8 +89,8 @@ class NavigationTest {
private val brand by composeTestRule.stringResource(SettingsR.string.feature_settings_brand_android)
private val ok by composeTestRule.stringResource(SettingsR.string.feature_settings_dismiss_dialog_button_text)
- @Before
- fun setup() = hiltRule.inject()
+// @Before
+// fun setup() = hiltRule.inject()
@Test
fun firstScreen_isForYou() {
diff --git a/app/src/androidMain/AndroidManifest.xml b/app/src/androidMain/AndroidManifest.xml
index 5b22f9865..b08932274 100644
--- a/app/src/androidMain/AndroidManifest.xml
+++ b/app/src/androidMain/AndroidManifest.xml
@@ -19,15 +19,6 @@
-
-
-
-
-
{
add("commonMainImplementation", project(":core:designsystem"))
add("commonMainImplementation", libs.findLibrary("jetbrains.compose.viewmodel").get())
add("commonMainImplementation", libs.findLibrary("jetbrains.compose.navigation").get())
+ "commonMainImplementation"(platform(libs.findLibrary("koin.bom").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())
diff --git a/core/analytics/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/analytics/di/AnalyticsModule.kt b/core/analytics/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/analytics/di/AnalyticsModule.kt
index 6147fe6d3..c8bba7d0f 100644
--- a/core/analytics/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/analytics/di/AnalyticsModule.kt
+++ b/core/analytics/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/analytics/di/AnalyticsModule.kt
@@ -16,10 +16,14 @@
package com.google.samples.apps.nowinandroid.core.analytics.di
+import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsHelper
import com.google.samples.apps.nowinandroid.core.analytics.StubAnalyticsHelper
-import org.koin.core.module.dsl.singleOf
-import org.koin.dsl.module
+import org.koin.core.annotation.Module
+import org.koin.core.annotation.Single
-val analyticsModule = module {
- singleOf(::StubAnalyticsHelper)
+
+@Module
+class AnalyticsModule {
+ @Single
+ fun providesAnalyticsHelper(): AnalyticsHelper = StubAnalyticsHelper()
}
diff --git a/core/data/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/data/di/DataModule.kt b/core/data/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/data/di/DataModule.kt
index 4a5c7a729..503d9f118 100644
--- a/core/data/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/data/di/DataModule.kt
+++ b/core/data/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/data/di/DataModule.kt
@@ -24,15 +24,17 @@ import com.google.samples.apps.nowinandroid.core.data.repository.OfflineFirstTop
import com.google.samples.apps.nowinandroid.core.data.repository.OfflineFirstUserDataRepository
import org.koin.core.annotation.ComponentScan
import org.koin.core.annotation.Module
+import org.koin.core.module.dsl.singleOf
import org.koin.core.qualifier.named
import org.koin.dsl.module
import org.koin.ksp.generated.module
internal val repositoryModule = module {
- single { OfflineFirstTopicsRepository(get(), get()) }
- single { OfflineFirstNewsRepository(get(), get(), get(), get(), get()) }
- single { OfflineFirstUserDataRepository(get(), get()) }
- single { DefaultRecentSearchRepository(get()) }
+ singleOf(::OfflineFirstTopicsRepository)
+ singleOf(::OfflineFirstNewsRepository)
+ singleOf(::OfflineFirstUserDataRepository)
+ singleOf(::DefaultRecentSearchRepository)
+ singleOf(::CompositeUserNewsResourceRepository)
single {
DefaultSearchContentsRepository(
get(),
@@ -42,7 +44,6 @@ internal val repositoryModule = module {
get(named("IoDispatcher")),
)
}
- single { CompositeUserNewsResourceRepository(get(), get()) }
}
internal val networkMonitorModule = module {
@@ -59,7 +60,7 @@ internal val timeZoneMonitorProviderModule = module {
}
}
-val dataModule = listOf(
+fun dataModule() = listOf(
DataModule().module,
repositoryModule,
networkMonitorModule,
diff --git a/core/database/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/database/di/DatabaseModule.kt b/core/database/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/database/di/DatabaseModule.kt
index fa663504f..64787b67e 100644
--- a/core/database/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/database/di/DatabaseModule.kt
+++ b/core/database/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/database/di/DatabaseModule.kt
@@ -60,7 +60,8 @@ internal val daoModule = module {
}
}
-val databaseModule = listOf(
+fun databaseModule() = listOf(
+ driverModule,
DatabaseModule().module,
daoModule,
)
diff --git a/core/datastore/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/di/SettingsComponent.kt b/core/datastore/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/di/DataStoreModule.kt
similarity index 96%
rename from core/datastore/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/di/SettingsComponent.kt
rename to core/datastore/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/di/DataStoreModule.kt
index b4c44afc4..5e552754d 100644
--- a/core/datastore/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/di/SettingsComponent.kt
+++ b/core/datastore/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/datastore/di/DataStoreModule.kt
@@ -21,7 +21,7 @@ import com.russhwolf.settings.Settings
import org.koin.core.qualifier.named
import org.koin.dsl.module
-val settingsModule = module {
+fun dataStoreModule() = module {
single { Settings() }
single {
NiaPreferencesDataSource(
diff --git a/core/designsystem/build.gradle.kts b/core/designsystem/build.gradle.kts
index c03901676..3667533ed 100644
--- a/core/designsystem/build.gradle.kts
+++ b/core/designsystem/build.gradle.kts
@@ -72,16 +72,19 @@ kotlin {
implementation(projects.core.testing)
}
commonMain.dependencies {
- implementation(libs.coil.compose)
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material)
implementation(compose.material3)
+ implementation(compose.material3AdaptiveNavigationSuite)
implementation(compose.materialIconsExtended)
implementation(compose.ui)
implementation(compose.uiUtil)
implementation(compose.components.resources)
implementation(compose.components.uiToolingPreview)
+ implementation(libs.coil.compose)
+ implementation(libs.jetbrains.compose.material3.adaptive)
+ implementation(libs.jetbrains.compose.material3.adaptive.layout)
}
}
}
diff --git a/core/designsystem/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/Navigation.kt b/core/designsystem/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/Navigation.kt
index 59f4f48a2..60be62688 100644
--- a/core/designsystem/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/Navigation.kt
+++ b/core/designsystem/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/Navigation.kt
@@ -23,10 +23,20 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.NavigationBarItemDefaults
+import androidx.compose.material3.NavigationDrawerItemDefaults
import androidx.compose.material3.NavigationRail
import androidx.compose.material3.NavigationRailItem
import androidx.compose.material3.NavigationRailItemDefaults
import androidx.compose.material3.Text
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.material3.adaptive.WindowAdaptiveInfo
+import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
+import androidx.compose.material3.adaptive.navigationsuite.ExperimentalMaterial3AdaptiveNavigationSuiteApi
+import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteDefaults
+import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteItemColors
+import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold
+import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldDefaults
+import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScope
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@@ -165,6 +175,96 @@ fun NiaNavigationRail(
)
}
+/**
+ * Now in Android navigation suite scaffold with item and content slots.
+ * Wraps Material 3 [NavigationSuiteScaffold].
+ *
+ * @param modifier Modifier to be applied to the navigation suite scaffold.
+ * @param navigationSuiteItems A slot to display multiple items via [NiaNavigationSuiteScope].
+ * @param windowAdaptiveInfo The window adaptive info.
+ * @param content The app content inside the scaffold.
+ */
+@Composable
+fun NiaNavigationSuiteScaffold(
+ navigationSuiteItems: NiaNavigationSuiteScope.() -> Unit,
+ modifier: Modifier = Modifier,
+ windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo(),
+ content: @Composable () -> Unit,
+) {
+ val layoutType = NavigationSuiteScaffoldDefaults
+ .calculateFromAdaptiveInfo(windowAdaptiveInfo)
+ val navigationSuiteItemColors = NavigationSuiteItemColors(
+ navigationBarItemColors = NavigationBarItemDefaults.colors(
+ selectedIconColor = NiaNavigationDefaults.navigationSelectedItemColor(),
+ unselectedIconColor = NiaNavigationDefaults.navigationContentColor(),
+ selectedTextColor = NiaNavigationDefaults.navigationSelectedItemColor(),
+ unselectedTextColor = NiaNavigationDefaults.navigationContentColor(),
+ indicatorColor = NiaNavigationDefaults.navigationIndicatorColor(),
+ ),
+ navigationRailItemColors = NavigationRailItemDefaults.colors(
+ selectedIconColor = NiaNavigationDefaults.navigationSelectedItemColor(),
+ unselectedIconColor = NiaNavigationDefaults.navigationContentColor(),
+ selectedTextColor = NiaNavigationDefaults.navigationSelectedItemColor(),
+ unselectedTextColor = NiaNavigationDefaults.navigationContentColor(),
+ indicatorColor = NiaNavigationDefaults.navigationIndicatorColor(),
+ ),
+ navigationDrawerItemColors = NavigationDrawerItemDefaults.colors(
+ selectedIconColor = NiaNavigationDefaults.navigationSelectedItemColor(),
+ unselectedIconColor = NiaNavigationDefaults.navigationContentColor(),
+ selectedTextColor = NiaNavigationDefaults.navigationSelectedItemColor(),
+ unselectedTextColor = NiaNavigationDefaults.navigationContentColor(),
+ ),
+ )
+
+ NavigationSuiteScaffold(
+ navigationSuiteItems = {
+ NiaNavigationSuiteScope(
+ navigationSuiteScope = this,
+ navigationSuiteItemColors = navigationSuiteItemColors,
+ ).run(navigationSuiteItems)
+ },
+ layoutType = layoutType,
+ containerColor = Color.Transparent,
+ navigationSuiteColors = NavigationSuiteDefaults.colors(
+ navigationBarContentColor = NiaNavigationDefaults.navigationContentColor(),
+ navigationRailContainerColor = Color.Transparent,
+ ),
+ modifier = modifier,
+ ) {
+ content()
+ }
+}
+
+/**
+ * A wrapper around [NavigationSuiteScope] to declare navigation items.
+ */
+class NiaNavigationSuiteScope internal constructor(
+ private val navigationSuiteScope: NavigationSuiteScope,
+ private val navigationSuiteItemColors: NavigationSuiteItemColors,
+) {
+ fun item(
+ selected: Boolean,
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ icon: @Composable () -> Unit,
+ selectedIcon: @Composable () -> Unit = icon,
+ label: @Composable (() -> Unit)? = null,
+ ) = navigationSuiteScope.item(
+ selected = selected,
+ onClick = onClick,
+ icon = {
+ if (selected) {
+ selectedIcon()
+ } else {
+ icon()
+ }
+ },
+ label = label,
+ colors = navigationSuiteItemColors,
+ modifier = modifier,
+ )
+}
+
@ThemePreviews
@Composable
fun NiaNavigationBarPreview() {
diff --git a/core/domain/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/domain/di/DomainModule.kt b/core/domain/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/domain/di/DomainModule.kt
index 0bd8c81d3..a898b2ae4 100644
--- a/core/domain/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/domain/di/DomainModule.kt
+++ b/core/domain/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/domain/di/DomainModule.kt
@@ -18,11 +18,6 @@ package com.google.samples.apps.nowinandroid.core.domain.di
import org.koin.core.annotation.ComponentScan
import org.koin.core.annotation.Module
-import org.koin.ksp.generated.module
-
-val domainModule = listOf(
- DomainModule().module,
-)
@Module
@ComponentScan
diff --git a/core/network/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/network/di/NetworkModule.kt b/core/network/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/network/di/NetworkModule.kt
index 0763c2d54..d9006fe1b 100644
--- a/core/network/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/network/di/NetworkModule.kt
+++ b/core/network/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/network/di/NetworkModule.kt
@@ -60,7 +60,7 @@ internal val ktorfitModule = module {
singleOf(::RetrofitNiaNetwork)
}
-val networkModule = listOf(NetworkModule().module, jsonModule, ktorfitModule)
+fun networkModule() = listOf(NetworkModule().module, jsonModule, ktorfitModule)
@Module
@ComponentScan
diff --git a/core/notifications/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/notifications/di/NotificationsModule.kt b/core/notifications/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/notifications/di/NotificationsModule.kt
index e08fa5ae0..d0d97fe5d 100644
--- a/core/notifications/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/notifications/di/NotificationsModule.kt
+++ b/core/notifications/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/core/notifications/di/NotificationsModule.kt
@@ -19,7 +19,7 @@ package com.google.samples.apps.nowinandroid.core.notifications.di
import com.google.samples.apps.nowinandroid.core.notifications.NoOpNotifier
import org.koin.dsl.module
-val notificationModule = module {
+fun notificationModule() = module {
// TODO replace with a real implementation
single { NoOpNotifier() }
}
diff --git a/feature/bookmarks/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/di/BookmarksModule.kt b/feature/bookmarks/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/di/BookmarksModule.kt
new file mode 100644
index 000000000..cffa93ce5
--- /dev/null
+++ b/feature/bookmarks/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/di/BookmarksModule.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+package com.google.samples.apps.nowinandroid.feature.bookmarks.di
+
+import com.google.samples.apps.nowinandroid.feature.bookmarks.BookmarksViewModel
+import org.koin.core.module.dsl.viewModel
+import org.koin.core.module.dsl.viewModelOf
+import org.koin.dsl.module
+
+val bookmarksModule = module {
+ viewModel { BookmarksViewModel(get(), get()) }
+}
+
diff --git a/feature/settings/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/feature/settings/di/SettingModule.kt b/feature/settings/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/feature/settings/di/SettingsModule.kt
similarity index 96%
rename from feature/settings/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/feature/settings/di/SettingModule.kt
rename to feature/settings/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/feature/settings/di/SettingsModule.kt
index f047a65fd..387094915 100644
--- a/feature/settings/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/feature/settings/di/SettingModule.kt
+++ b/feature/settings/src/commonMain/kotlin/com/google/samples/apps/nowinandroid/feature/settings/di/SettingsModule.kt
@@ -20,7 +20,7 @@ import com.google.samples.apps.nowinandroid.feature.settings.SettingsViewModel
import org.koin.core.module.dsl.viewModel
import org.koin.dsl.module
-val settingModule = module {
+val settingsModule = module {
viewModel {
SettingsViewModel(get())
}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index df8a7cf9b..071ef717c 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -4,20 +4,20 @@ androidDesugarJdkLibs = "2.1.2"
# AGP and tools should be updated together
androidGradlePlugin = "8.7.1"
androidTools = "31.7.1"
-androidxActivity = "1.9.2"
+androidxActivity = "1.9.3"
androidxAppCompat = "1.7.0"
androidxBrowser = "1.8.0"
-androidxComposeBom = "2024.09.03"
-androidxComposeRuntimeTracing = "1.7.3"
+androidxComposeBom = "2024.10.00"
+androidxComposeRuntimeTracing = "1.7.4"
androidxCore = "1.13.1"
androidxCoreSplashscreen = "1.0.1"
androidxDataStore = "1.1.1"
androidxEspresso = "3.6.1"
androidxHiltNavigationCompose = "1.2.0"
androidxLifecycle = "2.8.6"
-androidxMacroBenchmark = "1.3.2"
+androidxMacroBenchmark = "1.3.3"
androidxMetrics = "1.0.0-beta01"
-androidxNavigation = "2.8.2"
+androidxNavigation = "2.8.3"
androidxProfileinstaller = "1.4.1"
androidxTestCore = "1.6.1"
androidxTestExt = "1.2.1"
@@ -44,6 +44,7 @@ kotlinxCoroutines = "1.9.0"
kotlinxDatetime = "0.6.1"
kotlinxSerializationJson = "1.7.3"
ksp = "2.0.21-1.0.25"
+material3adaptive = "1.0.0-alpha03"
moduleGraph = "2.7.1"
okhttp = "4.12.0"
protobuf = "4.28.2"
@@ -60,8 +61,8 @@ androidx-constraintlayout = "2.1.4"
androidx-espresso-core = "3.6.1"
androidx-material = "1.12.0"
androidx-test-junit = "1.2.1"
-compose-ui-tooling = "1.7.3"
-compose-plugin = "1.7.0-rc01"
+compose-ui-tooling = "1.7.4"
+compose-plugin = "1.7.0"
sqldelight = "2.0.2"
kotlinInject = '0.7.2'
multiplatform-settings = "1.2.0"
@@ -222,6 +223,9 @@ ktorfit-converters-call = { group = "de.jensklingenberg.ktorfit", name = "ktorfi
buildkonfig-gradlePlugin = { group = "com.codingfeline.buildkonfig", name = "buildkonfig-gradle-plugin", version.ref = "buildKonfig" }
jetbrains-compose-viewmodel = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycle-viewmodel-compose" }
jetbrains-compose-navigation = { group = "org.jetbrains.androidx.navigation", name = "navigation-compose", version.ref = "navigation-compose" }
+jetbrains-compose-material3-adaptive = { group = "org.jetbrains.compose.material3.adaptive", name = "adaptive", version.ref = "material3adaptive" }
+jetbrains-compose-material3-adaptive-layout = { group = "org.jetbrains.compose.material3.adaptive", name = "adaptive-layout", version.ref = "material3adaptive" }
+jetbrains-compose-material3-adaptive-navigation = { group = "org.jetbrains.compose.material3.adaptive", name = "adaptive-navigation", version.ref = "material3adaptive" }
# Dependencies of the included build-logic
android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" }