diff --git a/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/AppScaffold.kt b/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/AppScaffold.kt index 64e90cc..c13ca5c 100644 --- a/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/AppScaffold.kt +++ b/libs/ui/src/commonMain/kotlin/ru/shadowsparky/ui/AppScaffold.kt @@ -1,6 +1,7 @@ package ru.shadowsparky.ui import androidx.compose.foundation.background +import androidx.compose.foundation.focusGroup import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets @@ -23,16 +24,27 @@ import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarScrollBehavior +import androidx.compose.material3.WideNavigationRail +import androidx.compose.material3.WideNavigationRailItem +import androidx.compose.material3.WideNavigationRailValue import androidx.compose.material3.adaptive.currentWindowAdaptiveInfoV2 +import androidx.compose.material3.adaptive.navigationsuite.NavigationSuite import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteItem -import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldDefaults +import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldLayout import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteType +import androidx.compose.material3.rememberWideNavigationRailState import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.Immutable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter @@ -84,11 +96,13 @@ fun AppScaffold( ) { val navigationSuiteType = when { navigationEntries.isNullOrEmpty() -> NavigationSuiteType.None - else -> { NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(currentWindowAdaptiveInfoV2()) } + else -> NavigationSuiteScaffoldDefaults.navigationSuiteType(currentWindowAdaptiveInfoV2()) } val blurState = rememberBlurState() CompositionLocalProvider(LocalBlurState provides blurState) { when (navigationSuiteType) { + NavigationSuiteType.ShortNavigationBarCompact, + NavigationSuiteType.ShortNavigationBarMedium, NavigationSuiteType.NavigationBar -> { BottomBarScaffold( modifier, @@ -118,6 +132,7 @@ fun AppScaffold( } } +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable private fun DynamicNavigationScaffold( modifier: Modifier, @@ -126,28 +141,67 @@ private fun DynamicNavigationScaffold( currentRoute: T?, onSelectNavigation: ((T) -> Unit)?, scrollBehavior: TopAppBarScrollBehavior?, - appBar: @Composable ((TopAppBarScrollBehavior?) -> Unit), + appBar: @Composable (TopAppBarScrollBehavior?) -> Unit, snackbarState: SnackbarHostState?, - content: @Composable ((PaddingValues) -> Unit) + content: @Composable (PaddingValues) -> Unit, ) { - NavigationSuiteScaffold( + val entries = navigationEntries.orEmpty() + val isWideRail = navigationSuiteType == NavigationSuiteType.WideNavigationRailCollapsed || + navigationSuiteType == NavigationSuiteType.WideNavigationRailExpanded + val railState = rememberWideNavigationRailState( + initialValue = WideNavigationRailValue.Collapsed + ) + var railHasFocus by remember { mutableStateOf(false) } + LaunchedEffect(railHasFocus) { + if (railHasFocus) railState.expand() else railState.collapse() + } + NavigationSuiteScaffoldLayout( navigationSuiteType = navigationSuiteType, - navigationItems = { - navigationEntries.orEmpty().forEach { entry -> - NavigationSuiteItem( - navigationSuiteType = navigationSuiteType, - selected = currentRoute == entry.route, - onClick = { onSelectNavigation?.invoke(entry.route) }, - icon = { Icon(entry.painter, entry.title) }, - label = { - when (navigationSuiteType) { - NavigationSuiteType.NavigationDrawer -> Text(entry.title) - } - }, - modifier = Modifier.focusBorder(false, CircleShape) - ) + navigationSuite = { + if (isWideRail) { + WideNavigationRail( + state = railState, + modifier = Modifier.onFocusChanged { railHasFocus = it.hasFocus } + .focusGroup(), + ) { + entries.forEach { entry -> + WideNavigationRailItem( + railExpanded = + railState.targetValue == WideNavigationRailValue.Expanded, + selected = currentRoute == entry.route, + onClick = { + onSelectNavigation?.invoke(entry.route) + }, + icon = { + Icon(entry.painter, entry.title) + }, + label = { Text(entry.title) }, + modifier = Modifier.focusBorder(false, CircleShape) + ) + } + } + } else { + NavigationSuite(navigationSuiteType = navigationSuiteType) { + entries.forEach { entry -> + NavigationSuiteItem( + navigationSuiteType = navigationSuiteType, + selected = currentRoute == entry.route, + onClick = { + onSelectNavigation?.invoke(entry.route) + }, + icon = { + Icon( + painter = entry.painter, + contentDescription = entry.title, + ) + }, + label = { Text(entry.title) }, + modifier = Modifier.focusBorder(false, CircleShape) + ) + } + } } - } + }, ) { Scaffold( modifier = if (scrollBehavior != null) { @@ -156,7 +210,9 @@ private fun DynamicNavigationScaffold( modifier }, topBar = { appBar(scrollBehavior) }, - snackbarHost = { snackbarState?.let { SnackbarHost(it) } }, + snackbarHost = { + snackbarState?.let { SnackbarHost(it) } + }, content = content ) }