diff --git a/build.gradle.kts b/build.gradle.kts index afc18e7..f5ebb6b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,5 +7,6 @@ plugins { alias(libs.plugins.composeMultiplatform) apply false alias(libs.plugins.composeCompiler) apply false alias(libs.plugins.kotlinMultiplatform) apply false + alias(libs.plugins.kotlinSerialization) apply false alias(libs.plugins.metro) apply false } diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 9035653..44ec581 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -3,6 +3,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.kotlinMultiplatform) + alias(libs.plugins.kotlinSerialization) alias(libs.plugins.androidApplication) alias(libs.plugins.composeMultiplatform) alias(libs.plugins.composeCompiler) @@ -34,6 +35,11 @@ kotlin { implementation(libs.compose.uiToolingPreview) implementation(libs.androidx.lifecycle.viewmodelCompose) implementation(libs.androidx.lifecycle.runtimeCompose) + + implementation(libs.androidx.lifecycle.viewmodel.nav3) + implementation(libs.androidx.nav3.ui) + implementation(libs.compose.material3.adaptive) + implementation(libs.compose.material3.adaptive.nav3) } commonTest.dependencies { implementation(libs.kotlin.test) diff --git a/composeApp/src/androidMain/kotlin/moe/lava/neon/MainActivity.kt b/composeApp/src/androidMain/kotlin/moe/lava/neon/MainActivity.kt index 45b77df..5c350b6 100644 --- a/composeApp/src/androidMain/kotlin/moe/lava/neon/MainActivity.kt +++ b/composeApp/src/androidMain/kotlin/moe/lava/neon/MainActivity.kt @@ -6,6 +6,7 @@ import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview +import androidx.navigation3.ui.defaultPredictivePopTransitionSpec import moe.lava.neon.ui.App class MainActivity : ComponentActivity() { diff --git a/composeApp/src/commonMain/kotlin/moe/lava/neon/ui/App.kt b/composeApp/src/commonMain/kotlin/moe/lava/neon/ui/App.kt index 0fd5fcf..8e5465b 100644 --- a/composeApp/src/commonMain/kotlin/moe/lava/neon/ui/App.kt +++ b/composeApp/src/commonMain/kotlin/moe/lava/neon/ui/App.kt @@ -1,48 +1,54 @@ package moe.lava.neon.ui -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.safeContentPadding -import androidx.compose.material3.Button import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import moe.lava.neon.resources.Res -import moe.lava.neon.resources.compose_multiplatform -import org.jetbrains.compose.resources.painterResource +import androidx.compose.runtime.Composable +import androidx.navigation3.runtime.NavKey +import androidx.navigation3.runtime.entryProvider +import androidx.navigation3.runtime.rememberNavBackStack +import androidx.navigation3.ui.NavDisplay +import androidx.savedstate.serialization.SavedStateConfiguration +import kotlinx.serialization.Serializable +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.modules.polymorphic +import moe.lava.neon.ui.screens.Login +import moe.lava.neon.ui.screens.Sample -@Composable -@Preview -fun App() { - MaterialTheme { - var showContent by remember { mutableStateOf(false) } - Column( - modifier = Modifier - .background(MaterialTheme.colorScheme.primaryContainer) - .safeContentPadding() - .fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Button(onClick = { showContent = !showContent }) { - Text("Click me!") - } - AnimatedVisibility(showContent) { - val greeting = remember { Greeting().greet() } - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Image(painterResource(Res.drawable.compose_multiplatform), null) - Text("Compose: $greeting") - } - } +private object Route { + @Serializable + data object Login : NavKey + + @Serializable + data class Sample(val token: String) : NavKey +} + +private val config = SavedStateConfiguration { + serializersModule = SerializersModule { + polymorphic(NavKey::class) { + subclass(Route.Login::class, Route.Login.serializer()) + subclass(Route.Sample::class, Route.Sample.serializer()) } } } + +@Composable +fun App() { + MaterialTheme { + val backStack = rememberNavBackStack(config, Route.Login) + NavDisplay( + backStack = backStack, + onBack = { backStack.removeLastOrNull() }, + entryProvider = entryProvider { + entry { + Login( + onSuccess = { token -> + backStack.add(Route.Sample(token)) + } + ) + } + entry { key -> + Sample(key.token) + } + } + ) + } +} diff --git a/composeApp/src/commonMain/kotlin/moe/lava/neon/ui/screens/Login.kt b/composeApp/src/commonMain/kotlin/moe/lava/neon/ui/screens/Login.kt new file mode 100644 index 0000000..83ae255 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/moe/lava/neon/ui/screens/Login.kt @@ -0,0 +1,38 @@ +package moe.lava.neon.ui.screens + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.safeContentPadding +import androidx.compose.material3.Button +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier + +@Composable +fun Login( + onSuccess: (token: String) -> Unit, +) { + Column( + modifier = Modifier +// .background(MaterialTheme.colorScheme.primaryContainer) + .safeContentPadding() + .fillMaxSize() + ) { + Text("Login!") + + var token by rememberSaveable { mutableStateOf("") } + OutlinedTextField( + value = token, + onValueChange = { token = it }, + label = { Text("Enter token") }, + ) + Button(onClick = { onSuccess(token) }) { + Text("Submit") + } + } +} diff --git a/composeApp/src/commonMain/kotlin/moe/lava/neon/ui/screens/Sample.kt b/composeApp/src/commonMain/kotlin/moe/lava/neon/ui/screens/Sample.kt new file mode 100644 index 0000000..8a7b4a9 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/moe/lava/neon/ui/screens/Sample.kt @@ -0,0 +1,50 @@ +package moe.lava.neon.ui.screens + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.safeContentPadding +import androidx.compose.material3.Button +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import moe.lava.neon.resources.Res +import moe.lava.neon.resources.compose_multiplatform +import moe.lava.neon.ui.Greeting +import org.jetbrains.compose.resources.painterResource + +@Composable +fun Sample(token: String) { + var showContent by remember { mutableStateOf(false) } + Column( + modifier = Modifier + .background(MaterialTheme.colorScheme.primaryContainer) + .safeContentPadding() + .fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Button(onClick = { showContent = !showContent }) { + Text("Click me!") + } + AnimatedVisibility(showContent) { + val greeting = remember { Greeting().greet() } + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Image(painterResource(Res.drawable.compose_multiplatform), null) + Text("Compose: $greeting") + Text("Passed token: $token") + } + } + } +} diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 686cc11..7e6b87f 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -3,6 +3,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.androidLibrary) alias(libs.plugins.kotlinMultiplatform) + alias(libs.plugins.kotlinSerialization) alias(libs.plugins.metro) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 91746aa..a37d305 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,8 @@ androidx-activity = "1.12.2" androidx-appcompat = "1.7.1" androidx-core = "1.17.0" androidx-espresso = "3.7.0" -androidx-lifecycle = "2.9.6" +androidx-lifecycle = "2.10.0-alpha07" +androidx-nav3 = "1.0.0-alpha06" androidx-testExt = "1.3.0" composeHotReload = "1.0.0" composeMultiplatform = "1.10.0" @@ -16,6 +17,7 @@ junit = "4.13.2" kotlin = "2.3.0" kotlinx-coroutines = "1.10.2" material3 = "1.10.0-alpha05" +material3-adaptive = "1.3.0-alpha03" metro = "0.10.0" [libraries] @@ -30,9 +32,13 @@ androidx-activity-compose = { module = "androidx.activity:activity-compose", ver compose-uiTooling = { module = "org.jetbrains.compose.ui:ui-tooling", version.ref = "composeMultiplatform" } androidx-lifecycle-viewmodelCompose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" } androidx-lifecycle-runtimeCompose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle" } +androidx-lifecycle-viewmodel-nav3 = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-navigation3", version.ref = "androidx-lifecycle" } +androidx-nav3-ui = { module = "org.jetbrains.androidx.navigation3:navigation3-ui", version.ref = "androidx-nav3" } compose-runtime = { module = "org.jetbrains.compose.runtime:runtime", version.ref = "composeMultiplatform" } compose-foundation = { module = "org.jetbrains.compose.foundation:foundation", version.ref = "composeMultiplatform" } compose-material3 = { module = "org.jetbrains.compose.material3:material3", version.ref = "material3" } +compose-material3-adaptive = { module = "org.jetbrains.compose.material3.adaptive:adaptive", version.ref = "material3-adaptive" } +compose-material3-adaptive-nav3 = { module = "org.jetbrains.compose.material3.adaptive:adaptive-navigation3", version.ref = "material3-adaptive" } compose-ui = { module = "org.jetbrains.compose.ui:ui", version.ref = "composeMultiplatform" } compose-components-resources = { module = "org.jetbrains.compose.components:components-resources", version.ref = "composeMultiplatform" } compose-uiToolingPreview = { module = "org.jetbrains.compose.ui:ui-tooling-preview", version.ref = "composeMultiplatform" } @@ -45,4 +51,5 @@ composeHotReload = { id = "org.jetbrains.compose.hot-reload", version.ref = "com composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "composeMultiplatform" } composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } +kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } metro = { id = "dev.zacsweers.metro", version.ref = "metro" }