Заложена навигация с передачей параметров и сохранением состояний

This commit is contained in:
Roman Pytkov
2024-11-10 00:09:40 +03:00
parent 7a9aee46a6
commit e8e170ebcd
10 changed files with 137 additions and 11 deletions

View File

@@ -12,6 +12,7 @@
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.Wallenc" android:theme="@style/Theme.Wallenc"
android:enableOnBackInvokedCallback="true"
tools:targetApi="31"> tools:targetApi="31">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"

View File

@@ -5,6 +5,7 @@ coreKtx = "1.15.0"
junit = "4.13.2" junit = "4.13.2"
junitVersion = "1.2.1" junitVersion = "1.2.1"
espressoCore = "3.6.1" espressoCore = "3.6.1"
kotlinReflect = "2.0.21"
kotlinxCoroutinesCore = "1.9.0" kotlinxCoroutinesCore = "1.9.0"
kotlinxSerializationJson = "1.7.3" kotlinxSerializationJson = "1.7.3"
lifecycleRuntimeKtx = "2.8.7" lifecycleRuntimeKtx = "2.8.7"
@@ -23,6 +24,7 @@ material = "1.12.0"
runtimeAndroid = "1.7.5" runtimeAndroid = "1.7.5"
[libraries] [libraries]
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlinReflect" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
navigation = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation" } navigation = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation" }

View File

@@ -4,6 +4,7 @@ plugins {
alias(libs.plugins.compose.compiler) alias(libs.plugins.compose.compiler)
alias(libs.plugins.dagger.hilt) alias(libs.plugins.dagger.hilt)
alias(libs.plugins.jetbrains.kotlin.serialization) alias(libs.plugins.jetbrains.kotlin.serialization)
id("kotlin-parcelize")
alias(libs.plugins.ksp) alias(libs.plugins.ksp)
} }
@@ -54,6 +55,7 @@ dependencies {
ksp(libs.dagger.hilt.compiler) ksp(libs.dagger.hilt.compiler)
implementation(libs.kotlinx.serialization.json) implementation(libs.kotlinx.serialization.json)
implementation(libs.kotlin.reflect)
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.lifecycle.runtime.ktx)

View File

@@ -1,14 +1,30 @@
package com.github.nullptroma.wallenc.presentation package com.github.nullptroma.wallenc.presentation
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Menu
import androidx.compose.material.icons.rounded.Settings
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.toRoute import androidx.navigation.toRoute
import com.github.nullptroma.wallenc.presentation.navigation.NavBarItem
import com.github.nullptroma.wallenc.presentation.navigation.rememberNavigationState
import com.github.nullptroma.wallenc.presentation.screens.main.MainRoute import com.github.nullptroma.wallenc.presentation.screens.main.MainRoute
import com.github.nullptroma.wallenc.presentation.screens.main.MainScreen import com.github.nullptroma.wallenc.presentation.screens.main.MainScreen
import com.github.nullptroma.wallenc.presentation.screens.settings.SettingsRoute import com.github.nullptroma.wallenc.presentation.screens.settings.SettingsRoute
@@ -25,17 +41,70 @@ fun WallencUi() {
} }
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun WallencNavRoot() { fun WallencNavRoot() {
val navController = rememberNavController() val navState = rememberNavigationState()
Scaffold { innerPaddings ->
NavHost(navController, startDestination = MainRoute()) { val topLevelScreenRoutes = rememberSaveable {
composable<MainRoute> { mutableMapOf(
MainRoute::class.qualifiedName!! to MainRoute(),
SettingsRoute::class.qualifiedName!! to SettingsRoute("Base settings")
)
}
// Все пункты меню верхнего уровня
val topLevelNavBarItems = remember {
listOf(
NavBarItem("Main", MainRoute::class.qualifiedName!!, Icons.Rounded.Menu),
NavBarItem("Settings", SettingsRoute::class.qualifiedName!!, Icons.Rounded.Settings)
)
}
Scaffold(
bottomBar = {
NavigationBar {
val navBackStackEntry by navState.navHostController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
topLevelNavBarItems.forEach {
val routeClassName = it.screenRouteClass
NavigationBarItem(
icon = {
Icon(
it.icon,
contentDescription = it.name
)
},
label = { Text(it.name) },
selected = currentRoute?.startsWith(routeClassName) == true,
onClick = {
var route = topLevelScreenRoutes[it.screenRouteClass]
if(route == null)
throw NoSuchElementException("Screen route of type ${it.screenRouteClass} no found")
if(currentRoute?.startsWith(routeClassName) != true)
navState.navigationTo(route)
}
)
}
}
}) { innerPaddings ->
NavHost(navState.navHostController, startDestination = topLevelScreenRoutes[MainRoute::class.qualifiedName]!!) {
composable<MainRoute>(enterTransition = {
fadeIn(tween(200))
}, exitTransition = {
fadeOut(tween(200))
}) {
MainScreen(Modifier.padding(innerPaddings), onSettingsRoute = { settingsRoute -> MainScreen(Modifier.padding(innerPaddings), onSettingsRoute = { settingsRoute ->
navController.navigate(settingsRoute) topLevelScreenRoutes[settingsRoute::class.qualifiedName!!] = settingsRoute
navState.navigationTo(settingsRoute)
}) })
} }
composable<SettingsRoute> { composable<SettingsRoute>(enterTransition = {
fadeIn(tween(200))
}, exitTransition = {
fadeOut(tween(200))
}) {
val route: SettingsRoute = it.toRoute() val route: SettingsRoute = it.toRoute()
SettingsScreen(Modifier.padding(innerPaddings), route.text) SettingsScreen(Modifier.padding(innerPaddings), route.text)
} }

View File

@@ -0,0 +1,5 @@
package com.github.nullptroma.wallenc.presentation.navigation
import androidx.compose.ui.graphics.vector.ImageVector
data class NavBarItem(val name: String, val screenRouteClass: String, val icon: ImageVector)

View File

@@ -0,0 +1,29 @@
package com.github.nullptroma.wallenc.presentation.navigation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
import com.github.nullptroma.wallenc.presentation.screens.ScreenRoute
class NavigationState(
val navHostController: NavHostController
) {
fun navigationTo(route: ScreenRoute) {
navHostController.navigate(route) {
popUpTo(navHostController.graph.findStartDestination().id)
launchSingleTop = true
restoreState = true
}
}
}
@Composable
fun rememberNavigationState(
navHostController: NavHostController? = null
): NavigationState {
val controller = navHostController ?: rememberNavController()
return remember { NavigationState(controller) }
}

View File

@@ -0,0 +1,7 @@
package com.github.nullptroma.wallenc.presentation.screens
import android.os.Parcelable
import kotlinx.serialization.Serializable
@Serializable
abstract class ScreenRoute() : Parcelable

View File

@@ -1,5 +1,9 @@
package com.github.nullptroma.wallenc.presentation.screens.main package com.github.nullptroma.wallenc.presentation.screens.main
import com.github.nullptroma.wallenc.presentation.screens.ScreenRoute
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@Serializable class MainRoute @Serializable
@Parcelize
class MainRoute: ScreenRoute()

View File

@@ -9,10 +9,11 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TextField import androidx.compose.material3.TextField
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalFocusManager
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import com.github.nullptroma.wallenc.presentation.screens.settings.SettingsRoute import com.github.nullptroma.wallenc.presentation.screens.settings.SettingsRoute
@@ -22,12 +23,14 @@ fun MainScreen(modifier: Modifier = Modifier,
viewModel: MainViewModel = hiltViewModel(), viewModel: MainViewModel = hiltViewModel(),
onSettingsRoute: (SettingsRoute) -> Unit) { onSettingsRoute: (SettingsRoute) -> Unit) {
val state = viewModel.stateFlow val state = viewModel.stateFlow
var text by remember { mutableStateOf("") } var text by rememberSaveable { mutableStateOf("") }
val focusManager = LocalFocusManager.current
Column(modifier = modifier.imePadding().fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center) { Column(modifier = modifier.imePadding().fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center) {
TextField(text, onValueChange = { s -> TextField(text, onValueChange = { s ->
text = s text = s
}) })
Button( onClick = { Button( onClick = {
focusManager.clearFocus()
onSettingsRoute(SettingsRoute(text)) onSettingsRoute(SettingsRoute(text))
}) { }) {
Text("Press Me!") Text("Press Me!")

View File

@@ -1,5 +1,9 @@
package com.github.nullptroma.wallenc.presentation.screens.settings package com.github.nullptroma.wallenc.presentation.screens.settings
import com.github.nullptroma.wallenc.presentation.screens.ScreenRoute
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@Serializable class SettingsRoute(val text: String) @Serializable
@Parcelize
class SettingsRoute(val text: String): ScreenRoute()