new: integrate navigation
This commit is contained in:
@@ -48,4 +48,10 @@ If you face any issues, please report them on [YouTrack](https://youtrack.jetbra
|
|||||||
## Dependency injection
|
## Dependency injection
|
||||||
|
|
||||||
For dependency injection, we use [Koin](https://insert-koin.io/). To know more about the integration with Compose,
|
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).
|
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).
|
||||||
@@ -9,11 +9,13 @@ androidxCore = "1.18.0"
|
|||||||
androidxEspresso = "3.7.0"
|
androidxEspresso = "3.7.0"
|
||||||
androidxLifecycle = "2.11.0-beta01"
|
androidxLifecycle = "2.11.0-beta01"
|
||||||
androidxTestExt = "1.3.0"
|
androidxTestExt = "1.3.0"
|
||||||
|
navigation3 = "1.1.1"
|
||||||
composeMultiplatform = "1.11.0"
|
composeMultiplatform = "1.11.0"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
kotlin = "2.3.21"
|
kotlin = "2.3.21"
|
||||||
kotlinWrappers = "2026.5.5"
|
kotlinWrappers = "2026.5.5"
|
||||||
kotlinxCoroutines = "1.11.0"
|
kotlinxCoroutines = "1.11.0"
|
||||||
|
kotlinSerialization = "2.4.0"
|
||||||
material3 = "1.11.0-alpha07"
|
material3 = "1.11.0-alpha07"
|
||||||
koin = "4.2.1"
|
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-compose-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel" }
|
||||||
koin-android = { module = "io.insert-koin:koin-android" }
|
koin-android = { module = "io.insert-koin:koin-android" }
|
||||||
kotlinx-coroutinesSwing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinxCoroutines" }
|
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" }
|
wrappers-browser = { module = "org.jetbrains.kotlin-wrappers:kotlin-browser", version.ref = "kotlinWrappers" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
@@ -51,4 +54,5 @@ androidMultiplatformLibrary = { id = "com.android.kotlin.multiplatform.library",
|
|||||||
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "composeMultiplatform" }
|
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "composeMultiplatform" }
|
||||||
composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||||
kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
||||||
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
|
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
|
||||||
|
kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlinSerialization"}
|
||||||
@@ -6,6 +6,7 @@ plugins {
|
|||||||
alias(libs.plugins.androidMultiplatformLibrary)
|
alias(libs.plugins.androidMultiplatformLibrary)
|
||||||
alias(libs.plugins.composeMultiplatform)
|
alias(libs.plugins.composeMultiplatform)
|
||||||
alias(libs.plugins.composeCompiler)
|
alias(libs.plugins.composeCompiler)
|
||||||
|
alias(libs.plugins.kotlinSerialization)
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
@@ -33,11 +34,11 @@ kotlin {
|
|||||||
androidLibrary {
|
androidLibrary {
|
||||||
namespace = "fr.ajaury.gwenedeg.shared"
|
namespace = "fr.ajaury.gwenedeg.shared"
|
||||||
compileSdk =
|
compileSdk =
|
||||||
libs.versions.android.compileSdk
|
libs.versions.androidCompileSdk
|
||||||
.get()
|
.get()
|
||||||
.toInt()
|
.toInt()
|
||||||
minSdk =
|
minSdk =
|
||||||
libs.versions.android.minSdk
|
libs.versions.androidMinSdk
|
||||||
.get()
|
.get()
|
||||||
.toInt()
|
.toInt()
|
||||||
|
|
||||||
@@ -66,6 +67,9 @@ kotlin {
|
|||||||
implementation(libs.androidx.lifecycle.viewmodelCompose)
|
implementation(libs.androidx.lifecycle.viewmodelCompose)
|
||||||
implementation(libs.androidx.lifecycle.runtimeCompose)
|
implementation(libs.androidx.lifecycle.runtimeCompose)
|
||||||
|
|
||||||
|
// Navigation
|
||||||
|
implementation(libs.navigation3)
|
||||||
|
|
||||||
// DI
|
// DI
|
||||||
implementation(project.dependencies.platform(libs.koin.bom))
|
implementation(project.dependencies.platform(libs.koin.bom))
|
||||||
implementation(libs.koin.core)
|
implementation(libs.koin.core)
|
||||||
|
|||||||
@@ -2,8 +2,15 @@ package fr.ajaury.gwenedeg
|
|||||||
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
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.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.di.appModule
|
||||||
|
import fr.ajaury.gwenedeg.navigation.Route
|
||||||
|
import fr.ajaury.gwenedeg.player.ui.PlayerScreen
|
||||||
import fr.ajaury.gwenedeg.records.ui.RecordsScreen
|
import fr.ajaury.gwenedeg.records.ui.RecordsScreen
|
||||||
import org.koin.compose.KoinApplication
|
import org.koin.compose.KoinApplication
|
||||||
import org.koin.dsl.koinConfiguration
|
import org.koin.dsl.koinConfiguration
|
||||||
@@ -16,8 +23,33 @@ fun App() {
|
|||||||
modules(appModule)
|
modules(appModule)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
val serializer = SnapshotStateListSerializer<Route>()
|
||||||
|
val backStack: MutableList<Route> = rememberSerializable(serializer = serializer) {
|
||||||
|
mutableStateListOf(Route.Records)
|
||||||
|
}
|
||||||
|
|
||||||
MaterialTheme {
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package fr.ajaury.gwenedeg.records.ui
|
package fr.ajaury.gwenedeg.records.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -8,9 +9,15 @@ import androidx.compose.ui.unit.dp
|
|||||||
import fr.ajaury.gwenedeg.records.domain.Record
|
import fr.ajaury.gwenedeg.records.domain.Record
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RecordView(record: Record) {
|
fun RecordView(
|
||||||
|
record: Record,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = record.title,
|
text = record.title,
|
||||||
modifier = Modifier.padding(16.dp),
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.clickable { onClick() }
|
||||||
|
.padding(16.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,16 +19,23 @@ import fr.ajaury.gwenedeg.records.ui.viewmodel.RecordsViewModel
|
|||||||
import org.koin.compose.viewmodel.koinViewModel
|
import org.koin.compose.viewmodel.koinViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RecordsScreen(viewModel: RecordsViewModel = koinViewModel()) {
|
fun RecordsScreen(
|
||||||
|
viewModel: RecordsViewModel = koinViewModel(),
|
||||||
|
onRecordClick: (Record) -> Unit,
|
||||||
|
) {
|
||||||
val records by viewModel.uiState.collectAsStateWithLifecycle()
|
val records by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
RecordsScreen(
|
RecordsScreen(
|
||||||
records = records,
|
records = records,
|
||||||
|
onRecordClick = onRecordClick,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RecordsScreen(records: List<Record>) {
|
fun RecordsScreen(
|
||||||
|
records: List<Record>,
|
||||||
|
onRecordClick: (Record) -> Unit,
|
||||||
|
) {
|
||||||
Scaffold { innerPadding ->
|
Scaffold { innerPadding ->
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier =
|
modifier =
|
||||||
@@ -40,6 +47,7 @@ fun RecordsScreen(records: List<Record>) {
|
|||||||
itemsIndexed(records) { index, item ->
|
itemsIndexed(records) { index, item ->
|
||||||
RecordView(
|
RecordView(
|
||||||
record = item,
|
record = item,
|
||||||
|
onClick = { onRecordClick(item) },
|
||||||
)
|
)
|
||||||
|
|
||||||
if (index < records.lastIndex) {
|
if (index < records.lastIndex) {
|
||||||
|
|||||||
Reference in New Issue
Block a user