diff --git a/README.md b/README.md index 7c966b7..c27c3a8 100644 --- a/README.md +++ b/README.md @@ -48,4 +48,10 @@ If you face any issues, please report them on [YouTrack](https://youtrack.jetbra ## Dependency injection For dependency injection, we use [Koin](https://insert-koin.io/). To know more about the integration with Compose, -please refer to the [Koin Compose documentation](https://insert-koin.io/docs/reference/koin-compose/compose). \ No newline at end of file +please refer to the [Koin Compose documentation](https://insert-koin.io/docs/reference/koin-compose/compose). + +## Navigation + +For navigation, Navigation3 is used: https://kotlinlang.org/docs/multiplatform/compose-navigation-3.html + +For implementation details, please refer to the [Navigation3 Android documentation](https://developer.android.com/guide/navigation/navigation-3/basics). \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9471ea4..5e8374e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,11 +9,13 @@ androidxCore = "1.18.0" androidxEspresso = "3.7.0" androidxLifecycle = "2.11.0-beta01" androidxTestExt = "1.3.0" +navigation3 = "1.1.1" composeMultiplatform = "1.11.0" junit = "4.13.2" kotlin = "2.3.21" kotlinWrappers = "2026.5.5" kotlinxCoroutines = "1.11.0" +kotlinSerialization = "2.4.0" material3 = "1.11.0-alpha07" koin = "4.2.1" @@ -43,6 +45,7 @@ koin-compose = { module = "io.insert-koin:koin-compose" } koin-compose-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel" } koin-android = { module = "io.insert-koin:koin-android" } kotlinx-coroutinesSwing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinxCoroutines" } +navigation3 = { module = "org.jetbrains.androidx.navigation3:navigation3-ui", version.ref = "navigation3" } wrappers-browser = { module = "org.jetbrains.kotlin-wrappers:kotlin-browser", version.ref = "kotlinWrappers" } [plugins] @@ -51,4 +54,5 @@ androidMultiplatformLibrary = { id = "com.android.kotlin.multiplatform.library", composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "composeMultiplatform" } composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } -kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } \ No newline at end of file +kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } +kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlinSerialization"} \ No newline at end of file diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index dad8825..404853b 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -6,6 +6,7 @@ plugins { alias(libs.plugins.androidMultiplatformLibrary) alias(libs.plugins.composeMultiplatform) alias(libs.plugins.composeCompiler) + alias(libs.plugins.kotlinSerialization) } kotlin { @@ -33,11 +34,11 @@ kotlin { androidLibrary { namespace = "fr.ajaury.gwenedeg.shared" compileSdk = - libs.versions.android.compileSdk + libs.versions.androidCompileSdk .get() .toInt() minSdk = - libs.versions.android.minSdk + libs.versions.androidMinSdk .get() .toInt() @@ -66,6 +67,9 @@ kotlin { implementation(libs.androidx.lifecycle.viewmodelCompose) implementation(libs.androidx.lifecycle.runtimeCompose) + // Navigation + implementation(libs.navigation3) + // DI implementation(project.dependencies.platform(libs.koin.bom)) implementation(libs.koin.core) diff --git a/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/App.kt b/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/App.kt index 4f21146..8b78f58 100644 --- a/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/App.kt +++ b/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/App.kt @@ -2,8 +2,15 @@ package fr.ajaury.gwenedeg import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.saveable.rememberSerializable import androidx.compose.ui.tooling.preview.Preview +import androidx.navigation3.runtime.NavEntry +import androidx.navigation3.ui.NavDisplay +import androidx.savedstate.compose.serialization.serializers.SnapshotStateListSerializer import fr.ajaury.gwenedeg.di.appModule +import fr.ajaury.gwenedeg.navigation.Route +import fr.ajaury.gwenedeg.player.ui.PlayerScreen import fr.ajaury.gwenedeg.records.ui.RecordsScreen import org.koin.compose.KoinApplication import org.koin.dsl.koinConfiguration @@ -16,8 +23,33 @@ fun App() { modules(appModule) }, ) { + val serializer = SnapshotStateListSerializer() + val backStack: MutableList = rememberSerializable(serializer = serializer) { + mutableStateListOf(Route.Records) + } + MaterialTheme { - RecordsScreen() + NavDisplay( + backStack = backStack, + onBack = { backStack.removeLastOrNull() }, + entryProvider = { key -> + when (key) { + is Route.Records -> NavEntry(key) { + RecordsScreen( + onRecordClick = { record -> + backStack.add(Route.Player(record.title)) + }, + ) + } + + is Route.Player -> NavEntry(key) { + PlayerScreen( + recordTitle = key.title, + ) + } + } + }, + ) } } } diff --git a/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/navigation/Route.kt b/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/navigation/Route.kt new file mode 100644 index 0000000..d348f96 --- /dev/null +++ b/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/navigation/Route.kt @@ -0,0 +1,15 @@ +package fr.ajaury.gwenedeg.navigation + +import androidx.navigation3.runtime.NavKey +import kotlinx.serialization.Serializable + +@Serializable +sealed interface Route : NavKey { + @Serializable + data object Records : Route + + @Serializable + data class Player( + val title: String, + ) : Route +} diff --git a/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/ui/PlayerScreen.kt b/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/ui/PlayerScreen.kt new file mode 100644 index 0000000..73fd4a1 --- /dev/null +++ b/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/ui/PlayerScreen.kt @@ -0,0 +1,33 @@ +package fr.ajaury.gwenedeg.player.ui + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import org.jetbrains.compose.resources.ExperimentalResourceApi + +@OptIn(ExperimentalResourceApi::class) +@Composable +fun PlayerScreen(recordTitle: String) { + Scaffold { innerPadding -> + Box( + modifier = Modifier + .padding(innerPadding) + .fillMaxSize(), + contentAlignment = Alignment.Center, + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text( + text = "Playing: $recordTitle", + style = MaterialTheme.typography.headlineMedium, + ) + } + } + } +} diff --git a/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/records/ui/RecordView.kt b/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/records/ui/RecordView.kt index 418ce44..01cdb5f 100644 --- a/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/records/ui/RecordView.kt +++ b/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/records/ui/RecordView.kt @@ -1,5 +1,6 @@ package fr.ajaury.gwenedeg.records.ui +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.padding import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -8,9 +9,15 @@ import androidx.compose.ui.unit.dp import fr.ajaury.gwenedeg.records.domain.Record @Composable -fun RecordView(record: Record) { +fun RecordView( + record: Record, + onClick: () -> Unit, +) { Text( text = record.title, - modifier = Modifier.padding(16.dp), + modifier = + Modifier + .clickable { onClick() } + .padding(16.dp), ) } diff --git a/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/records/ui/RecordsScreen.kt b/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/records/ui/RecordsScreen.kt index 5ed0a8e..9211510 100644 --- a/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/records/ui/RecordsScreen.kt +++ b/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/records/ui/RecordsScreen.kt @@ -19,16 +19,23 @@ import fr.ajaury.gwenedeg.records.ui.viewmodel.RecordsViewModel import org.koin.compose.viewmodel.koinViewModel @Composable -fun RecordsScreen(viewModel: RecordsViewModel = koinViewModel()) { +fun RecordsScreen( + viewModel: RecordsViewModel = koinViewModel(), + onRecordClick: (Record) -> Unit, +) { val records by viewModel.uiState.collectAsStateWithLifecycle() RecordsScreen( records = records, + onRecordClick = onRecordClick, ) } @Composable -fun RecordsScreen(records: List) { +fun RecordsScreen( + records: List, + onRecordClick: (Record) -> Unit, +) { Scaffold { innerPadding -> LazyColumn( modifier = @@ -40,6 +47,7 @@ fun RecordsScreen(records: List) { itemsIndexed(records) { index, item -> RecordView( record = item, + onClick = { onRecordClick(item) }, ) if (index < records.lastIndex) {