use experimental rail

This commit is contained in:
2026-06-22 18:27:27 +03:00
parent 7a254a5085
commit 420dc4e044
6 changed files with 150 additions and 157 deletions
@@ -39,3 +39,7 @@ fun createNavigationList(): List<NavigationEntry<Route>> {
) )
return list return list
} }
fun List<NavigationEntry<*>>.findTitle(route: Route): String? {
return firstOrNull { it.route == route }?.title
}
@@ -1,147 +1,96 @@
package ru.shadowsparky.vbox.shared.presentation package ru.shadowsparky.vbox.shared.presentation
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.LargeTopAppBar
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
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.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.text.input.TextFieldValue
import ru.shadowsparky.ui.BaseScreen import ru.shadowsparky.ui.BaseScreen
import ru.shadowsparky.ui.LocalBlurState import ru.shadowsparky.ui.LocalBlurState
import ru.shadowsparky.ui.USE_EXPERIMENTAL_RAIL
import ru.shadowsparky.ui.applyBlurEffect import ru.shadowsparky.ui.applyBlurEffect
import ru.shadowsparky.ui.components.isTv
import ru.shadowsparky.ui.currentWindowType import ru.shadowsparky.ui.currentWindowType
import ru.shadowsparky.ui.isCompact import ru.shadowsparky.ui.isCompact
import ru.shadowsparky.ui.isExpanded
import ru.shadowsparky.ui.rememberBlurState import ru.shadowsparky.ui.rememberBlurState
import ru.shadowsparky.ui.theme.ComponentDefaults import ru.shadowsparky.ui.theme.ComponentDefaults
import ru.shadowsparky.vbox.shared.presentation.list.VideoContent import ru.shadowsparky.vbox.shared.presentation.list.VideoContent
import ru.shadowsparky.vbox.shared.presentation.nav.Child import ru.shadowsparky.vbox.shared.presentation.nav.Child
import ru.shadowsparky.vbox.shared.presentation.nav.Route import ru.shadowsparky.vbox.shared.presentation.nav.Route
import ru.shadowsparky.vbox.shared.presentation.saved.SavedMovieContent import ru.shadowsparky.vbox.shared.presentation.saved.SavedMovieScreen
import ru.shadowsparky.vbox.shared.presentation.search.SearchAppBar import ru.shadowsparky.vbox.shared.presentation.search.SearchScreen
import ru.shadowsparky.vbox.shared.presentation.search.SearchVideoContent
import ru.shadowsparky.vbox.shared.presentation.settings.SettingsContent import ru.shadowsparky.vbox.shared.presentation.settings.SettingsContent
@Composable @Composable
fun MainScreen( fun MainScreen(route: Route, child: Child, rootComponent: RootComponent) {
route: Route, when (child) {
child: Child, is Child.SearchChild -> SearchScreen(child.component, route) { rootComponent.nav(it, true) }
rootComponent: RootComponent is Child.SavedMovieChild -> SavedMovieScreen(child.component, route) { rootComponent.navReplace(it) }
) { else -> MainScreenInternal(route, child) { rootComponent.navReplace(it) }
if (child is Child.SearchChild) {
SearchScreenInternal(child, route, rootComponent)
} else {
MainScreenInternal(route, child, rootComponent)
} }
} }
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun SearchScreenInternal( fun BaseMainScreen(
child: Child.SearchChild, appBar: @Composable () -> Unit,
route: Route, route: Route,
rootComponent: RootComponent, scrollBehavior: TopAppBarScrollBehavior,
onNavigate: (Route) -> Unit,
content: @Composable (PaddingValues) -> Unit
) { ) {
val component = child.component
var textFieldValue by rememberSaveable(stateSaver = TextFieldValue.Saver) {
mutableStateOf(TextFieldValue())
}
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
val requester by remember { mutableStateOf(FocusRequester()) }
val windowType = currentWindowType()
val searchAppBar: (@Composable () -> Unit) = {
SearchAppBar(
textFieldValue = textFieldValue,
onDone = { component.done(textFieldValue.text) },
onValueChange = { textFieldValue = it },
focusRequester = requester
)
}
val blurState = rememberBlurState() val blurState = rememberBlurState()
CompositionLocalProvider(LocalBlurState provides blurState) { CompositionLocalProvider(LocalBlurState provides blurState) {
BaseScreen( BaseScreen(
appBar = { appBar = { appBar() },
if (windowType.isCompact || !USE_EXPERIMENTAL_RAIL) searchAppBar()
},
currentRoute = route, currentRoute = route,
onSelectNavigation = { rootComponent.nav(it, true) }, onSelectNavigation = { onNavigate(it) },
navigationEntries = createNavigationList(), navigationEntries = createNavigationList(),
colorScheme = getColorScheme(), colorScheme = getColorScheme(),
scrollBehavior = scrollBehavior scrollBehavior = scrollBehavior
) { ) { padding ->
val content: @Composable () -> Unit = { content(padding)
SearchVideoContent(
child.component,
it,
requester
) { textFieldValue = it }
}
if (windowType.isExpanded && USE_EXPERIMENTAL_RAIL) {
Column {
searchAppBar()
content()
}
} else {
content()
}
} }
} }
} }
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun MainScreenInternal(route: Route, child: Child, rootComponent: RootComponent) { private fun MainScreenInternal(route: Route, child: Child, onNavigate: (Route) -> Unit) {
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
val navList = createNavigationList() val navList = createNavigationList()
val windowType = currentWindowType() val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
val blurState = rememberBlurState() BaseMainScreen(
CompositionLocalProvider(LocalBlurState provides blurState) {
BaseScreen(
appBar = { appBar = {
if (windowType.isCompact || !USE_EXPERIMENTAL_RAIL) { navList.findTitle(route)?.let { title ->
navList.firstOrNull { it.route == route }?.title?.let { if (currentWindowType().isCompact) {
if (isTv()) { LargeTopAppBar(
TopAppBar( title = { Text(title) },
title = { Text(it) },
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
modifier = Modifier.applyBlurEffect(blurState), modifier = Modifier.applyBlurEffect(LocalBlurState.current),
colors = ComponentDefaults.topAppBarColors() colors = ComponentDefaults.topAppBarColors()
) )
} else { } else {
LargeTopAppBar( TopAppBar(
title = { Text(it) }, title = { Text(title) },
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
modifier = Modifier.applyBlurEffect(blurState), modifier = Modifier.applyBlurEffect(LocalBlurState.current),
colors = ComponentDefaults.topAppBarColors() colors = ComponentDefaults.topAppBarColors()
) )
} }
} }
}
}, },
currentRoute = route, route = route,
onSelectNavigation = { rootComponent.navReplace(it) }, scrollBehavior = scrollBehavior,
navigationEntries = navList, onNavigate = onNavigate,
colorScheme = getColorScheme(), content = { padding ->
scrollBehavior = scrollBehavior
) { padding ->
when (child) { when (child) {
is Child.VideosChild -> VideoContent(child.component, padding) is Child.VideosChild -> VideoContent(child.component, padding)
is Child.SettingsChild -> SettingsContent(child.component, padding) is Child.SettingsChild -> SettingsContent(child.component, padding)
is Child.SavedMovieChild -> SavedMovieContent(child.component, padding)
else -> {} else -> {}
} }
} }
} )
} }
@@ -10,7 +10,6 @@ import androidx.compose.foundation.border
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.aspectRatio
@@ -42,7 +41,6 @@ import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
@@ -119,7 +117,6 @@ fun VideoScreenContent(
emptyResource: StringResource, emptyResource: StringResource,
onRefresh: () -> Unit, onRefresh: () -> Unit,
onShowOverview: (Long) -> Unit, onShowOverview: (Long) -> Unit,
extraFab: (@Composable BoxScope.() -> Unit)? = null,
onLoad: () -> Unit onLoad: () -> Unit
) { ) {
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@@ -130,7 +127,7 @@ fun VideoScreenContent(
onRefresh() onRefresh()
} }
} else if (items != null) { } else if (items != null) {
VideosInfo(items = items, paddingValues, emptyResource, extraFab) { VideosInfo(items = items, paddingValues, emptyResource) {
onShowOverview(it.id) onShowOverview(it.id)
} }
} else { } else {
@@ -143,16 +140,11 @@ fun VideosInfo(
items: List<VideoItem>, items: List<VideoItem>,
paddingValues: PaddingValues, paddingValues: PaddingValues,
emptyResource: StringResource, emptyResource: StringResource,
extraFab: (@Composable BoxScope.() -> Unit)? = null,
onClick: (item: VideoItem) -> Unit onClick: (item: VideoItem) -> Unit
) { ) {
val blur = LocalBlurState.current val blur = LocalBlurState.current
val gridState = rememberLazyGridState() val gridState = rememberLazyGridState()
val density = LocalDensity.current
val layoutDirection = LocalLayoutDirection.current val layoutDirection = LocalLayoutDirection.current
var fabHeight by remember { mutableStateOf(0.dp) }
Box(modifier = Modifier.fillMaxSize()) { Box(modifier = Modifier.fillMaxSize()) {
if (items.isNotEmpty()) { if (items.isNotEmpty()) {
LazyVerticalGrid( LazyVerticalGrid(
@@ -164,7 +156,7 @@ fun VideosInfo(
start = paddingValues.calculateStartPadding(layoutDirection), start = paddingValues.calculateStartPadding(layoutDirection),
top = paddingValues.calculateTopPadding(), top = paddingValues.calculateTopPadding(),
end = paddingValues.calculateEndPadding(layoutDirection), end = paddingValues.calculateEndPadding(layoutDirection),
bottom = paddingValues.calculateBottomPadding() + (fabHeight / 2) bottom = paddingValues.calculateBottomPadding()
), ),
state = gridState state = gridState
) { ) {
@@ -179,19 +171,6 @@ fun VideosInfo(
} else { } else {
VideosNotFound(emptyResource) VideosNotFound(emptyResource)
} }
val rootBoxScope = this
extraFab?.let {
Box(
modifier = Modifier.align(Alignment.BottomEnd)
.onGloballyPositioned { coordinates ->
fabHeight = with(density) {
coordinates.size.height.toDp()
}
}
) { it(rootBoxScope) }
}
} }
} }
@@ -9,18 +9,19 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Checkbox import androidx.compose.material3.Checkbox
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -28,19 +29,24 @@ 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.LocalLayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.resources.stringResource
import ru.shadowsparky.ui.components.LazyColumn import ru.shadowsparky.ui.components.LazyColumn
import ru.shadowsparky.ui.theme.ComponentDefaults
import ru.shadowsparky.vbox.shared.domain.tag.TagInfo import ru.shadowsparky.vbox.shared.domain.tag.TagInfo
import ru.shadowsparky.vbox.shared.presentation.BaseMainScreen
import ru.shadowsparky.vbox.shared.presentation.createNavigationList
import ru.shadowsparky.vbox.shared.presentation.findTitle
import ru.shadowsparky.vbox.shared.presentation.list.VideoScreenContent import ru.shadowsparky.vbox.shared.presentation.list.VideoScreenContent
import ru.shadowsparky.vbox.shared.presentation.nav.Route
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun SavedMovieContent(comp: SavedMovieScreenComponent, paddingValues: PaddingValues) { fun SavedMovieScreen(comp: SavedMovieScreenComponent, route: Route, onNavigate: (Route) -> Unit) {
val items by comp.all.collectAsStateWithLifecycle(null)
val tags by comp.userTags.collectAsStateWithLifecycle(null) val tags by comp.userTags.collectAsStateWithLifecycle(null)
val navList = createNavigationList()
var filterBottomSheetVisible by rememberSaveable { mutableStateOf(false) } var filterBottomSheetVisible by rememberSaveable { mutableStateOf(false) }
if (filterBottomSheetVisible) { if (filterBottomSheetVisible) {
FilterBottomSheet( FilterBottomSheet(
@@ -50,6 +56,33 @@ fun SavedMovieContent(comp: SavedMovieScreenComponent, paddingValues: PaddingVal
onFilterBy = { comp.filterByTag(it) } onFilterBy = { comp.filterByTag(it) }
) )
} }
BaseMainScreen(
appBar = {
TopAppBar(
title = { navList.findTitle(route)?.let { Text(it) } },
actions = {
IconButton(
onClick = { filterBottomSheetVisible = true }
) {
Icon(
painterResource(Res.drawable.filter_list),
contentDescription = null
)
}
},
colors = ComponentDefaults.topAppBarColors()
)
},
route = route,
scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(),
onNavigate = onNavigate,
content = { SavedMovieContent(comp, it) }
)
}
@Composable
private fun SavedMovieContent(comp: SavedMovieScreenComponent, paddingValues: PaddingValues) {
val items by comp.all.collectAsStateWithLifecycle(null)
VideoScreenContent( VideoScreenContent(
paddingValues, paddingValues,
null, null,
@@ -57,23 +90,7 @@ fun SavedMovieContent(comp: SavedMovieScreenComponent, paddingValues: PaddingVal
onRefresh = {}, onRefresh = {},
onShowOverview = { comp.showOverview(it) }, onShowOverview = { comp.showOverview(it) },
emptyResource = Res.string.saved_movies_not_found, emptyResource = Res.string.saved_movies_not_found,
onLoad = {}, onLoad = {}
extraFab = {
if (tags?.isNotEmpty() == true) {
FloatingActionButton(
onClick = { filterBottomSheetVisible = true },
modifier = Modifier.padding(
end = paddingValues.calculateEndPadding(LocalLayoutDirection.current) + 4.dp,
bottom = paddingValues.calculateBottomPadding() + 4.dp
)
) {
Icon(
painterResource(Res.drawable.filter_list),
contentDescription = null
)
}
}
}
) )
} }
@@ -35,13 +35,17 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TextField import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldColors import androidx.compose.material3.TextFieldColors
import androidx.compose.material3.TextFieldDefaults import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberSearchBarState import androidx.compose.material3.rememberSearchBarState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.runtime.withFrameNanos import androidx.compose.runtime.withFrameNanos
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -62,7 +66,6 @@ import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import kotlinx.coroutines.delay
import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.resources.stringResource
import ru.shadowsparky.ui.LocalBlurState import ru.shadowsparky.ui.LocalBlurState
@@ -71,7 +74,42 @@ import ru.shadowsparky.ui.applyBlurSource
import ru.shadowsparky.ui.components.BackButton import ru.shadowsparky.ui.components.BackButton
import ru.shadowsparky.ui.components.ExpressiveLazyColumn import ru.shadowsparky.ui.components.ExpressiveLazyColumn
import ru.shadowsparky.ui.theme.ComponentDefaults import ru.shadowsparky.ui.theme.ComponentDefaults
import ru.shadowsparky.vbox.shared.presentation.BaseMainScreen
import ru.shadowsparky.vbox.shared.presentation.imePaddingInternal import ru.shadowsparky.vbox.shared.presentation.imePaddingInternal
import ru.shadowsparky.vbox.shared.presentation.nav.Route
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SearchScreen(
component: SearchScreenComponent,
route: Route,
onNavigate: (Route) -> Unit
) {
val requester by remember { mutableStateOf(FocusRequester()) }
var textFieldValue by rememberSaveable(stateSaver = TextFieldValue.Saver) {
mutableStateOf(TextFieldValue())
}
BaseMainScreen(
appBar = {
SearchAppBar(
textFieldValue = textFieldValue,
onDone = { component.done(textFieldValue.text) },
onValueChange = { textFieldValue = it },
focusRequester = requester
)
},
content = {
SearchVideoContent(
component,
it,
requester
) { textFieldValue = it }
},
route = route,
scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(),
onNavigate = onNavigate
)
}
@OptIn(ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
@@ -103,7 +141,7 @@ fun SearchVideoContent(
Text( Text(
text = stringResource(Res.string.search_empty_now), text = stringResource(Res.string.search_empty_now),
style = MaterialTheme.typography.titleMedium, style = MaterialTheme.typography.titleMedium,
modifier = Modifier.clickable { }, modifier = Modifier.focusRequester(focusRequester).clickable {},
textAlign = TextAlign.Center textAlign = TextAlign.Center
) )
} }
@@ -3,12 +3,12 @@ package ru.shadowsparky.ui
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.width
import androidx.compose.material3.ColorScheme import androidx.compose.material3.ColorScheme
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
@@ -29,12 +29,13 @@ import androidx.compose.runtime.Immutable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import ru.shadowsparky.ui.nav.Route import ru.shadowsparky.ui.nav.Route
import ru.shadowsparky.ui.theme.ComponentDefaults import ru.shadowsparky.ui.theme.ComponentDefaults
import ru.shadowsparky.ui.theme.getColorScheme import ru.shadowsparky.ui.theme.getColorScheme
const val USE_EXPERIMENTAL_RAIL = false const val USE_EXPERIMENTAL_RAIL = true
@Immutable @Immutable
data class NavigationEntry<out T : Route>( data class NavigationEntry<out T : Route>(
@@ -125,9 +126,7 @@ private fun <T : Route> BottomBar(
NavigationBarItem( NavigationBarItem(
currentRoute == entry.route, currentRoute == entry.route,
onClick = { onSelectNavigation?.invoke(entry.route) }, onClick = { onSelectNavigation?.invoke(entry.route) },
icon = { icon = { Icon(entry.painter, contentDescription = entry.title) },
Icon(entry.painter, contentDescription = entry.title)
},
label = { Text(entry.title) } label = { Text(entry.title) }
) )
} }
@@ -144,23 +143,30 @@ private fun <T : Route> ExpandedContent(
onSelectNavigation: ((T) -> Unit)?, onSelectNavigation: ((T) -> Unit)?,
content: @Composable ((PaddingValues) -> Unit) content: @Composable ((PaddingValues) -> Unit)
) { ) {
Row(Modifier.fillMaxSize().padding(values)) { val topPadding = values.calculateTopPadding()
Spacer(Modifier.width(4.dp)) val bottomPadding = values.calculateBottomPadding()
NavigationRail { val startPadding = values.calculateStartPadding(LayoutDirection.Ltr)
val endPadding = values.calculateEndPadding(LayoutDirection.Ltr)
Row(modifier = Modifier.fillMaxSize().padding(top = topPadding)) {
NavigationRail(modifier = Modifier.padding(horizontal = 4.dp)) {
navigationEntries.forEach { entry -> navigationEntries.forEach { entry ->
NavigationRailItem( NavigationRailItem(
selected = currentRoute == entry.route, selected = currentRoute == entry.route,
onClick = { onSelectNavigation?.invoke(entry.route) }, onClick = { onSelectNavigation?.invoke(entry.route) },
icon = { icon = { Icon(entry.painter, contentDescription = entry.title) },
Icon(entry.painter, contentDescription = entry.title)
},
label = { Text(entry.title) } label = { Text(entry.title) }
) )
} }
} }
Spacer(Modifier.width(4.dp)) Box(modifier = Modifier.weight(1f)) {
Box(Modifier.weight(1f)) { content(
content(PaddingValues(0.dp)) PaddingValues(
start = startPadding,
top = 0.dp,
end = endPadding,
bottom = bottomPadding
)
)
} }
} }
} }