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

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.core.tween
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Menu 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.remember
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier 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.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState 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.NavBarItemData
import com.github.nullptroma.wallenc.presentation.navigation.rememberNavigationState 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.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.SettingsRoute
import com.github.nullptroma.wallenc.presentation.screens.settings.SettingsScreen import com.github.nullptroma.wallenc.presentation.screens.settings.SettingsScreen
import com.github.nullptroma.wallenc.presentation.theme.WallencTheme import com.github.nullptroma.wallenc.presentation.theme.WallencTheme
@@ -46,59 +51,75 @@ fun WallencUi() {
fun WallencNavRoot() { fun WallencNavRoot() {
val navState = rememberNavigationState() 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( mutableMapOf(
MainRoute::class.qualifiedName!! to MainRoute(), MainRoute::class.qualifiedName!! to MainRoute(),
SettingsRoute::class.qualifiedName!! to SettingsRoute("Base settings") SettingsRoute::class.qualifiedName!! to SettingsRoute("Base settings")
) )
} }
// Все пункты меню верхнего уровня
val topLevelNavBarItems = remember { val topLevelNavBarItems = remember {
listOf( mapOf(
NavBarItem("Main", MainRoute::class.qualifiedName!!, Icons.Rounded.Menu), MainRoute::class.qualifiedName!! to NavBarItemData(
NavBarItem("Settings", SettingsRoute::class.qualifiedName!!, Icons.Rounded.Settings) 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 {
val navBackStackEntry by navState.navHostController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
topLevelNavBarItems.forEach {
val routeClassName = it.screenRouteClass
NavigationBarItem( Scaffold(bottomBar = {
icon = { NavigationBar(modifier = Modifier.height(64.dp)) {
Icon( val navBackStackEntry by navState.navHostController.currentBackStackEntryAsState()
it.icon, val currentRoute = navBackStackEntry?.destination?.route
contentDescription = it.name topLevelNavBarItems.forEach {
) val routeClassName = it.key
}, val navBarItemData = it.value
label = { Text(it.name) }, NavigationBarItem(icon = {
selected = currentRoute?.startsWith(routeClassName) == true, if (navBarItemData.icon != null) Icon(
onClick = { navBarItemData.icon,
var route = topLevelScreenRoutes[it.screenRouteClass] contentDescription = stringResource(navBarItemData.nameStringResourceId)
if(route == null)
throw NoSuchElementException("Screen route of type ${it.screenRouteClass} no found")
if(currentRoute?.startsWith(routeClassName) != true)
navState.navigationTo(route)
}
) )
} },
label = { Text(stringResource(navBarItemData.nameStringResourceId)) },
selected = currentRoute?.startsWith(routeClassName) == true,
onClick = {
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]!!) { }) { innerPaddings ->
NavHost(
navState.navHostController,
startDestination = topLevelRoutes[MainRoute::class.qualifiedName]!!
) {
composable<MainRoute>(enterTransition = { composable<MainRoute>(enterTransition = {
fadeIn(tween(200)) fadeIn(tween(200))
}, exitTransition = { }, exitTransition = {
fadeOut(tween(200)) fadeOut(tween(200))
}) { }) {
MainScreen(Modifier.padding(innerPaddings), onSettingsRoute = { settingsRoute -> MainScreen(
topLevelScreenRoutes[settingsRoute::class.qualifiedName!!] = settingsRoute modifier = Modifier.padding(innerPaddings),
navState.navigationTo(settingsRoute) navState = mainNavState,
}) routes = mainScreenRoutes
)
} }
composable<SettingsRoute>(enterTransition = { composable<SettingsRoute>(enterTransition = {
fadeIn(tween(200)) fadeIn(tween(200))

View File

@@ -2,4 +2,4 @@ package com.github.nullptroma.wallenc.presentation.navigation
import androidx.compose.ui.graphics.vector.ImageVector 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 @Serializable
@Parcelize @Parcelize
class MainRoute: ScreenRoute() open class MainRoute: ScreenRoute()

View File

@@ -1,39 +1,108 @@
package com.github.nullptroma.wallenc.presentation.screens.main 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.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.material3.Button 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.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.getValue 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.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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 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 @androidx.compose.runtime.Composable
fun MainScreen(modifier: Modifier = Modifier, fun MainScreen(
viewModel: MainViewModel = hiltViewModel(), modifier: Modifier = Modifier,
onSettingsRoute: (SettingsRoute) -> Unit) { viewModel: MainViewModel = hiltViewModel(),
val state = viewModel.stateFlow navState: NavigationState = rememberNavigationState(),
var text by rememberSaveable { mutableStateOf("") } routes: MutableMap<String, MainRoute> = rememberSaveable {
val focusManager = LocalFocusManager.current mutableMapOf(
Column(modifier = modifier.imePadding().fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center) { LocalVaultRoute::class.qualifiedName!! to LocalVaultRoute(),
TextField(text, onValueChange = { s -> RemoteVaultsRoute::class.qualifiedName!! to RemoteVaultsRoute()
text = s )
}) }
Button( onClick = { ) {
focusManager.clearFocus() val topLevelNavBarItems = remember {
onSettingsRoute(SettingsRoute(text)) mapOf(
}) { LocalVaultRoute::class.qualifiedName!! to NavBarItemData(
Text("Press Me!") 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
)
})
}
}
HorizontalDivider()
}
}) { innerPaddings ->
NavHost(
navState.navHostController,
startDestination = routes[LocalVaultRoute::class.qualifiedName]!!
) {
composable<LocalVaultRoute>(enterTransition = {
fadeIn(tween(200))
}, exitTransition = {
fadeOut(tween(200))
}) {
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 package com.github.nullptroma.wallenc.presentation.screens.main
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.github.nullptroma.wallenc.domain.usecases.TestUseCase
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
@HiltViewModel @HiltViewModel
class MainViewModel @javax.inject.Inject constructor( class MainViewModel @javax.inject.Inject constructor(): ViewModel() {
testUseCase: TestUseCase,
testUseCase2: TestUseCase,
testUseCase3: TestUseCase,
): ViewModel() {
val stateFlow = MainScreenState("${testUseCase3.meta.name}, number ${testUseCase3.id}")
} }

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"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <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> <string name="settings_title">Settings Screen Title!</string>
</resources> </resources>