Обновлена навигация

This commit is contained in:
Roman Pytkov
2024-11-23 10:56:35 +03:00
parent e8e170ebcd
commit bd0183da79
14 changed files with 234 additions and 70 deletions

View File

@@ -3,6 +3,7 @@ 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.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Menu
@@ -19,14 +20,18 @@ 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.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.toRoute
import com.github.nullptroma.wallenc.presentation.navigation.NavBarItem
import com.github.nullptroma.wallenc.presentation.navigation.NavBarItemData
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.MainScreen
import com.github.nullptroma.wallenc.presentation.screens.main.screens.local.vault.LocalVaultRoute
import com.github.nullptroma.wallenc.presentation.screens.main.screens.remotes.RemoteVaultsRoute
import com.github.nullptroma.wallenc.presentation.screens.settings.SettingsRoute
import com.github.nullptroma.wallenc.presentation.screens.settings.SettingsScreen
import com.github.nullptroma.wallenc.presentation.theme.WallencTheme
@@ -46,59 +51,75 @@ fun WallencUi() {
fun WallencNavRoot() {
val navState = rememberNavigationState()
val topLevelScreenRoutes = rememberSaveable {
val mainNavState = rememberNavigationState()
val mainScreenRoutes = rememberSaveable {
mutableMapOf(
LocalVaultRoute::class.qualifiedName!! to LocalVaultRoute(),
RemoteVaultsRoute::class.qualifiedName!! to RemoteVaultsRoute()
)
}
val topLevelRoutes = rememberSaveable {
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)
mapOf(
MainRoute::class.qualifiedName!! to NavBarItemData(
R.string.nav_label_main, MainRoute::class.qualifiedName!!, Icons.Rounded.Menu
),
SettingsRoute::class.qualifiedName!! to NavBarItemData(
R.string.nav_label_settings,
SettingsRoute::class.qualifiedName!!,
Icons.Rounded.Settings
)
)
}
Scaffold(
bottomBar = {
NavigationBar {
Scaffold(bottomBar = {
NavigationBar(modifier = Modifier.height(64.dp)) {
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
val routeClassName = it.key
val navBarItemData = it.value
NavigationBarItem(icon = {
if (navBarItemData.icon != null) Icon(
navBarItemData.icon,
contentDescription = stringResource(navBarItemData.nameStringResourceId)
)
},
label = { Text(it.name) },
label = { Text(stringResource(navBarItemData.nameStringResourceId)) },
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)
}
var route = topLevelRoutes[navBarItemData.screenRouteClass]
if (route == null)
throw NullPointerException("Route $route not found")
if (currentRoute?.startsWith(routeClassName) != true) navState.navigationTo(
route
)
})
}
}
}) { innerPaddings ->
NavHost(navState.navHostController, startDestination = topLevelScreenRoutes[MainRoute::class.qualifiedName]!!) {
NavHost(
navState.navHostController,
startDestination = topLevelRoutes[MainRoute::class.qualifiedName]!!
) {
composable<MainRoute>(enterTransition = {
fadeIn(tween(200))
}, exitTransition = {
fadeOut(tween(200))
}) {
MainScreen(Modifier.padding(innerPaddings), onSettingsRoute = { settingsRoute ->
topLevelScreenRoutes[settingsRoute::class.qualifiedName!!] = settingsRoute
navState.navigationTo(settingsRoute)
})
MainScreen(
modifier = Modifier.padding(innerPaddings),
navState = mainNavState,
routes = mainScreenRoutes
)
}
composable<SettingsRoute>(enterTransition = {
fadeIn(tween(200))

View File

@@ -2,4 +2,4 @@ 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)
data class NavBarItemData(val nameStringResourceId: Int, val screenRouteClass: String, val icon: ImageVector?)

View File

@@ -6,4 +6,4 @@ import kotlinx.serialization.Serializable
@Serializable
@Parcelize
class MainRoute: ScreenRoute()
open class MainRoute: ScreenRoute()

View File

@@ -1,39 +1,108 @@
package com.github.nullptroma.wallenc.presentation.screens.main
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.imePadding
import androidx.compose.material3.Button
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.github.nullptroma.wallenc.presentation.screens.settings.SettingsRoute
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import com.github.nullptroma.wallenc.presentation.R
import com.github.nullptroma.wallenc.presentation.navigation.NavBarItemData
import com.github.nullptroma.wallenc.presentation.navigation.NavigationState
import com.github.nullptroma.wallenc.presentation.navigation.rememberNavigationState
import com.github.nullptroma.wallenc.presentation.screens.main.screens.local.vault.LocalVaultRoute
import com.github.nullptroma.wallenc.presentation.screens.main.screens.local.vault.LocalVaultScreen
import com.github.nullptroma.wallenc.presentation.screens.main.screens.remotes.RemoteVaultsRoute
import com.github.nullptroma.wallenc.presentation.screens.main.screens.remotes.RemoteVaultsScreen
@OptIn(ExperimentalMaterial3Api::class)
@androidx.compose.runtime.Composable
fun MainScreen(modifier: Modifier = Modifier,
fun MainScreen(
modifier: Modifier = Modifier,
viewModel: MainViewModel = hiltViewModel(),
onSettingsRoute: (SettingsRoute) -> Unit) {
val state = viewModel.stateFlow
var text by rememberSaveable { mutableStateOf("") }
val focusManager = LocalFocusManager.current
Column(modifier = modifier.imePadding().fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center) {
TextField(text, onValueChange = { s ->
text = s
navState: NavigationState = rememberNavigationState(),
routes: MutableMap<String, MainRoute> = rememberSaveable {
mutableMapOf(
LocalVaultRoute::class.qualifiedName!! to LocalVaultRoute(),
RemoteVaultsRoute::class.qualifiedName!! to RemoteVaultsRoute()
)
}
) {
val topLevelNavBarItems = remember {
mapOf(
LocalVaultRoute::class.qualifiedName!! to NavBarItemData(
R.string.nav_label_local_vault, LocalVaultRoute::class.qualifiedName!!, null
),
RemoteVaultsRoute::class.qualifiedName!! to NavBarItemData(
R.string.nav_label_remote_vaults, RemoteVaultsRoute::class.qualifiedName!!, null
)
)
}
Scaffold(modifier = modifier, contentWindowInsets = WindowInsets(0.dp), bottomBar = {
Column {
NavigationBar(modifier = Modifier.height(48.dp)) {
val navBackStackEntry by navState.navHostController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
topLevelNavBarItems.forEach {
val routeClassName = it.key
val navBarItemData = it.value
NavigationBarItem(modifier = Modifier
.weight(1f)
.fillMaxHeight(),
icon = { Text(stringResource(navBarItemData.nameStringResourceId)) },
selected = currentRoute?.startsWith(routeClassName) == true,
onClick = {
var route = routes[navBarItemData.screenRouteClass]
if (route == null)
throw NullPointerException("Route $route not found")
if (currentRoute?.startsWith(routeClassName) != true) navState.navigationTo(
route
)
})
Button( onClick = {
focusManager.clearFocus()
onSettingsRoute(SettingsRoute(text))
}
}
HorizontalDivider()
}
}) { innerPaddings ->
NavHost(
navState.navHostController,
startDestination = routes[LocalVaultRoute::class.qualifiedName]!!
) {
composable<LocalVaultRoute>(enterTransition = {
fadeIn(tween(200))
}, exitTransition = {
fadeOut(tween(200))
}) {
Text("Press Me!")
LocalVaultScreen(modifier = Modifier.padding(innerPaddings))
}
composable<RemoteVaultsRoute>(enterTransition = {
fadeIn(tween(200))
}, exitTransition = {
fadeOut(tween(200))
}) {
RemoteVaultsScreen(modifier = Modifier.padding(innerPaddings))
}
}
}
}

View File

@@ -1,14 +1,9 @@
package com.github.nullptroma.wallenc.presentation.screens.main
import androidx.lifecycle.ViewModel
import com.github.nullptroma.wallenc.domain.usecases.TestUseCase
import dagger.hilt.android.lifecycle.HiltViewModel
@HiltViewModel
class MainViewModel @javax.inject.Inject constructor(
testUseCase: TestUseCase,
testUseCase2: TestUseCase,
testUseCase3: TestUseCase,
): ViewModel() {
val stateFlow = MainScreenState("${testUseCase3.meta.name}, number ${testUseCase3.id}")
class MainViewModel @javax.inject.Inject constructor(): ViewModel() {
}

View File

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

View File

@@ -0,0 +1,15 @@
package com.github.nullptroma.wallenc.presentation.screens.main.screens.local.vault
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.hilt.navigation.compose.hiltViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LocalVaultScreen(modifier: Modifier = Modifier,
viewModel: LocalVaultViewModel = hiltViewModel()) {
Text("Local vault screen")
}

View File

@@ -0,0 +1,3 @@
package com.github.nullptroma.wallenc.presentation.screens.main.screens.local.vault
data class LocalVaultScreenState(val value: String)

View File

@@ -0,0 +1,10 @@
package com.github.nullptroma.wallenc.presentation.screens.main.screens.local.vault
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@HiltViewModel
class LocalVaultViewModel @Inject constructor(): ViewModel() {
}

View File

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

View File

@@ -0,0 +1,15 @@
package com.github.nullptroma.wallenc.presentation.screens.main.screens.remotes
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.hilt.navigation.compose.hiltViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RemoteVaultsScreen(modifier: Modifier = Modifier,
viewModel: RemoteVaultsViewModel = hiltViewModel()) {
Text("Remote vault screen")
}

View File

@@ -0,0 +1,3 @@
package com.github.nullptroma.wallenc.presentation.screens.main.screens.remotes
data class RemoteVaultsScreenState(val value: String)

View File

@@ -0,0 +1,10 @@
package com.github.nullptroma.wallenc.presentation.screens.main.screens.remotes
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@HiltViewModel
class RemoteVaultsViewModel @Inject constructor(): ViewModel() {
}

View File

@@ -1,4 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="nav_label_local_vault">Local</string>
<string name="nav_label_remote_vaults">Remotes</string>
<string name="nav_label_main">Main</string>
<string name="nav_label_settings">Settings</string>
<string name="settings_title">Settings Screen Title!</string>
</resources>