diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b07533105..e2ea8de3d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -79,6 +79,7 @@ dependencies { implementation(projects.core.designsystem) implementation(projects.core.data) implementation(projects.core.model) + implementation(projects.core.navigation) implementation(projects.core.analytics) implementation(projects.sync.work) diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/di/BackStackProvider.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/di/BackStackProvider.kt new file mode 100644 index 000000000..877e93910 --- /dev/null +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/di/BackStackProvider.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2025 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.di + +import com.google.samples.apps.nowinandroid.core.navigation.NiaBackStack +import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import javax.inject.Singleton +import dagger.hilt.components.SingletonComponent + +@Module +@InstallIn(SingletonComponent::class) +object NiaAppNavigation { + @Provides + @Singleton + fun provideNiaBackStack(): NiaBackStack = + NiaBackStack(startKey = TopLevelDestination.FOR_YOU) +} diff --git a/build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt index 1ab3a2ca0..3deb87ad7 100644 --- a/build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt @@ -37,7 +37,7 @@ class AndroidApplicationConventionPlugin : Plugin { extensions.configure { configureKotlinAndroid(this) - defaultConfig.targetSdk = 35 + defaultConfig.targetSdk = 36 @Suppress("UnstableApiUsage") testOptions.animationsDisabled = true configureGradleManagedDevices(this) diff --git a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt index 3fe727410..b677d1da2 100644 --- a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt @@ -37,7 +37,7 @@ class AndroidLibraryConventionPlugin : Plugin { extensions.configure { configureKotlinAndroid(this) - defaultConfig.targetSdk = 35 + defaultConfig.targetSdk = 36 defaultConfig.testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testOptions.animationsDisabled = true configureFlavors(this) diff --git a/build-logic/convention/src/main/kotlin/AndroidTestConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidTestConventionPlugin.kt index 67933f77d..6e01b203e 100644 --- a/build-logic/convention/src/main/kotlin/AndroidTestConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidTestConventionPlugin.kt @@ -30,7 +30,7 @@ class AndroidTestConventionPlugin : Plugin { extensions.configure { configureKotlinAndroid(this) - defaultConfig.targetSdk = 35 + defaultConfig.targetSdk = 36 configureGradleManagedDevices(this) } } diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinAndroid.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinAndroid.kt index 5d396d2a4..79be8122f 100644 --- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinAndroid.kt +++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinAndroid.kt @@ -35,7 +35,7 @@ internal fun Project.configureKotlinAndroid( commonExtension: CommonExtension<*, *, *, *, *, *>, ) { commonExtension.apply { - compileSdk = 35 + compileSdk = 36 defaultConfig { minSdk = 21 diff --git a/core/navigation/.gitignore b/core/navigation/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/core/navigation/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/navigation/build.gradle.kts b/core/navigation/build.gradle.kts new file mode 100644 index 000000000..3ee05ccb8 --- /dev/null +++ b/core/navigation/build.gradle.kts @@ -0,0 +1,8 @@ +plugins { + alias(libs.plugins.nowinandroid.jvm.library) + alias(libs.plugins.nowinandroid.hilt) +} + +dependencies { + implementation(libs.androidx.navigation3.runtime) +} diff --git a/core/navigation/src/main/kotlin/com/google/samples/apps/nowinandroid/core/navigation/NiaBackStack.kt b/core/navigation/src/main/kotlin/com/google/samples/apps/nowinandroid/core/navigation/NiaBackStack.kt new file mode 100644 index 000000000..824ee3205 --- /dev/null +++ b/core/navigation/src/main/kotlin/com/google/samples/apps/nowinandroid/core/navigation/NiaBackStack.kt @@ -0,0 +1,77 @@ +/* + * Copyright 2025 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.core.navigation + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshots.SnapshotStateList +import javax.inject.Inject +import kotlin.collections.remove + +class NiaBackStack @Inject constructor( + startKey: Any, +) { + val backStack = mutableStateListOf(startKey) + + // Maintain a stack for each top level route + private var topLevelStacks : LinkedHashMap> = linkedMapOf( + startKey to mutableStateListOf(startKey) + ) + + // Expose the current top level route for consumers + var topLevelKey by mutableStateOf(startKey) + private set + + private fun updateBackStack() = + backStack.apply { + clear() + addAll(topLevelStacks.flatMap { it.value }) + } + + fun navigateToTopLevelDestination(key: Any){ + // If the top level doesn't exist, add it + if (topLevelStacks[key] == null){ + topLevelStacks.put(key, mutableStateListOf(key)) + } else { + // Otherwise just move it to the end of the stacks + topLevelStacks.apply { + remove(key)?.let { + put(key, it) + } + } + } + topLevelKey = key + updateBackStack() + } + + fun navigate(key: Any){ + println("cfok navigate $key") + topLevelStacks[topLevelKey]?.add(key) + updateBackStack() + } + + fun removeLast(){ + val removedKey = topLevelStacks[topLevelKey]?.removeLastOrNull() + // If the removed key was a top level key, remove the associated top level stack + topLevelStacks.remove(removedKey) + topLevelKey = topLevelStacks.keys.last() + updateBackStack() + } + +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 81f12c480..250f2e28e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,8 +2,8 @@ accompanist = "0.37.0" androidDesugarJdkLibs = "2.1.4" # AGP and tools should be updated together -androidGradlePlugin = "8.9.0" -androidTools = "31.9.0" +androidGradlePlugin = "8.9.3" +androidTools = "31.9.3" androidxActivity = "1.9.3" androidxAppCompat = "1.7.0" androidxBrowser = "1.8.0" @@ -21,6 +21,7 @@ androidxLintGradle = "1.0.0-alpha03" androidxMacroBenchmark = "1.3.4" androidxMetrics = "1.0.0-beta01" androidxNavigation = "2.8.5" +androidxNavigation3 = "1.0.0-alpha03" androidxProfileinstaller = "1.4.1" androidxTestCore = "1.7.0-rc01" androidxTestExt = "1.3.0-rc01" @@ -99,6 +100,8 @@ androidx-lint-gradle = { group = "androidx.lint", name = "lint-gradle", version. androidx-metrics = { group = "androidx.metrics", name = "metrics-performance", version.ref = "androidxMetrics" } androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "androidxNavigation" } androidx-navigation-testing = { group = "androidx.navigation", name = "navigation-testing", version.ref = "androidxNavigation" } +androidx-navigation3-runtime = { group = "androidx.navigation3", name = "navigation3-runtime", version.ref = "androidxNavigation3" } +androidx-navigation3-ui = { group = "androidx.navigation3", name = "navigation3-ui", version.ref = "androidxNavigation3" } androidx-profileinstaller = { group = "androidx.profileinstaller", name = "profileinstaller", version.ref = "androidxProfileinstaller" } androidx-test-core = { group = "androidx.test", name = "core", version.ref = "androidxTestCore" } androidx-test-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "androidxEspresso" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 1d19c11c8..03adf46ee 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -59,6 +59,7 @@ include(":core:datastore-test") include(":core:designsystem") include(":core:domain") include(":core:model") +include(":core:navigation") include(":core:network") include(":core:notifications") include(":core:screenshot-testing")