feat: basic nav3

This commit is contained in:
Cilly Leang 2026-01-23 21:24:53 +11:00
parent 2cb53ee962
commit 1b3b465112
Signed by: cilly
GPG key ID: 6500251E087653C9
8 changed files with 152 additions and 42 deletions

View file

@ -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
}

View file

@ -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)

View file

@ -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() {

View file

@ -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<Route.Login> {
Login(
onSuccess = { token ->
backStack.add(Route.Sample(token))
}
)
}
entry<Route.Sample> { key ->
Sample(key.token)
}
}
)
}
}

View file

@ -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")
}
}
}

View file

@ -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")
}
}
}
}

View file

@ -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)
}

View file

@ -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" }