Change-Id: I68ff2584ec0c5be2209c21d1bec9b71acad6f42dfeature/adaptive_scaffold
parent
2a6fb1ea60
commit
0cd61134e1
@ -1,220 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.samples.apps.nowinandroid.ui
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.NavigationBar
|
||||
import androidx.compose.material3.NavigationBarDefaults
|
||||
import androidx.compose.material3.NavigationBarItem
|
||||
import androidx.compose.material3.NavigationBarItemDefaults
|
||||
import androidx.compose.material3.NavigationDrawerItem
|
||||
import androidx.compose.material3.NavigationDrawerItemDefaults
|
||||
import androidx.compose.material3.NavigationRail
|
||||
import androidx.compose.material3.NavigationRailDefaults
|
||||
import androidx.compose.material3.NavigationRailItem
|
||||
import androidx.compose.material3.NavigationRailItemDefaults
|
||||
import androidx.compose.material3.PermanentDrawerSheet
|
||||
import androidx.compose.material3.PermanentNavigationDrawer
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.ScaffoldDefaults
|
||||
import androidx.compose.material3.contentColorFor
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.movableContentOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.window.layout.WindowMetricsCalculator
|
||||
|
||||
class AdaptiveScaffoldNavigationComponentColors internal constructor(
|
||||
val railContainerColor: Color,
|
||||
val drawerContainerColor: Color,
|
||||
val bottomBarContainerColor: Color,
|
||||
val contentContainerColor: Color,
|
||||
val contentColor: Color,
|
||||
val selectedIconColor: Color,
|
||||
val selectedTextColor: Color,
|
||||
val unselectedIconColor: Color,
|
||||
val unselectedTextColor: Color,
|
||||
val selectedContainerColor: Color,
|
||||
val unselectedContainerColor: Color,
|
||||
)
|
||||
|
||||
object AdaptiveScaffoldNavigationComponentDefaults {
|
||||
@Composable
|
||||
fun colors(
|
||||
railContainerColor: Color = NavigationRailDefaults.ContainerColor,
|
||||
drawerContainerColor: Color = MaterialTheme.colorScheme.surface,
|
||||
bottomBarContainerColor: Color = NavigationBarDefaults.containerColor,
|
||||
contentContainerColor: Color = MaterialTheme.colorScheme.background,
|
||||
contentColor: Color = contentColorFor(contentContainerColor),
|
||||
selectedIconColor: Color = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||
selectedTextColor: Color = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||
unselectedIconColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
unselectedTextColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
selectedContainerColor: Color = MaterialTheme.colorScheme.secondaryContainer,
|
||||
unselectedContainerColor: Color = MaterialTheme.colorScheme.surface,
|
||||
) = AdaptiveScaffoldNavigationComponentColors(
|
||||
railContainerColor = railContainerColor,
|
||||
drawerContainerColor = drawerContainerColor,
|
||||
bottomBarContainerColor = bottomBarContainerColor,
|
||||
contentContainerColor = contentContainerColor,
|
||||
contentColor = contentColor,
|
||||
selectedIconColor = selectedIconColor,
|
||||
selectedTextColor = selectedTextColor,
|
||||
unselectedIconColor = unselectedIconColor,
|
||||
unselectedTextColor = unselectedTextColor,
|
||||
selectedContainerColor = selectedContainerColor,
|
||||
unselectedContainerColor = unselectedContainerColor,
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun <T> AdaptiveScaffold(
|
||||
modifier: Modifier = Modifier,
|
||||
navigationItems: List<T>,
|
||||
navigationItemTitle: @Composable (item: T, isSelected: Boolean) -> Unit,
|
||||
navigationItemIcon: @Composable (item: T, isSelected: Boolean) -> Unit,
|
||||
isItemSelected: @Composable (item: T) -> Boolean,
|
||||
onNavigationItemClick: (item: T) -> Unit,
|
||||
topBar: @Composable () -> Unit = {},
|
||||
snackbarHost: @Composable () -> Unit = {},
|
||||
colors: AdaptiveScaffoldNavigationComponentColors = AdaptiveScaffoldNavigationComponentDefaults.colors(),
|
||||
contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
|
||||
content: @Composable (padding: PaddingValues) -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val metrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context)
|
||||
val widthDp = metrics.bounds.width() / context.resources.displayMetrics.density
|
||||
val movableContent = remember(content) {
|
||||
movableContentOf(content)
|
||||
}
|
||||
|
||||
when {
|
||||
widthDp >= 1240f -> {
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
topBar = topBar,
|
||||
snackbarHost = snackbarHost,
|
||||
containerColor = colors.contentContainerColor,
|
||||
contentColor = colors.contentColor,
|
||||
contentWindowInsets = contentWindowInsets,
|
||||
) { padding ->
|
||||
PermanentNavigationDrawer(
|
||||
drawerContent = {
|
||||
PermanentDrawerSheet(
|
||||
drawerContainerColor = colors.drawerContainerColor,
|
||||
) {
|
||||
navigationItems.forEach { item ->
|
||||
NavigationDrawerItem(
|
||||
label = { navigationItemTitle(item, isItemSelected(item)) },
|
||||
icon = { navigationItemIcon(item, isItemSelected(item)) },
|
||||
selected = isItemSelected(item),
|
||||
onClick = { onNavigationItemClick(item) },
|
||||
colors = NavigationDrawerItemDefaults.colors(
|
||||
selectedContainerColor = colors.selectedContainerColor,
|
||||
unselectedContainerColor = colors.unselectedContainerColor,
|
||||
selectedIconColor = colors.selectedIconColor,
|
||||
unselectedIconColor = colors.unselectedIconColor,
|
||||
selectedTextColor = colors.selectedTextColor,
|
||||
unselectedTextColor = colors.unselectedTextColor,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier.padding(padding),
|
||||
) {
|
||||
movableContent(padding)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
widthDp >= 600f -> {
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
topBar = topBar,
|
||||
snackbarHost = snackbarHost,
|
||||
containerColor = colors.contentContainerColor,
|
||||
contentColor = colors.contentColor,
|
||||
contentWindowInsets = contentWindowInsets,
|
||||
) { padding ->
|
||||
Row {
|
||||
NavigationRail(
|
||||
containerColor = colors.railContainerColor,
|
||||
) {
|
||||
navigationItems.forEach { item ->
|
||||
NavigationRailItem(
|
||||
label = { navigationItemTitle(item, isItemSelected(item)) },
|
||||
icon = { navigationItemIcon(item, isItemSelected(item)) },
|
||||
selected = isItemSelected(item),
|
||||
onClick = { onNavigationItemClick(item) },
|
||||
colors = NavigationRailItemDefaults.colors(
|
||||
indicatorColor = colors.selectedContainerColor,
|
||||
selectedIconColor = colors.selectedIconColor,
|
||||
unselectedIconColor = colors.unselectedIconColor,
|
||||
selectedTextColor = colors.selectedTextColor,
|
||||
unselectedTextColor = colors.unselectedTextColor,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
movableContent(padding)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
snackbarHost = snackbarHost,
|
||||
bottomBar = {
|
||||
NavigationBar(
|
||||
containerColor = colors.bottomBarContainerColor,
|
||||
) {
|
||||
navigationItems.forEach { item ->
|
||||
NavigationBarItem(
|
||||
label = { navigationItemTitle(item, isItemSelected(item)) },
|
||||
icon = { navigationItemIcon(item, isItemSelected(item)) },
|
||||
selected = isItemSelected(item),
|
||||
onClick = { onNavigationItemClick(item) },
|
||||
colors = NavigationBarItemDefaults.colors(
|
||||
indicatorColor = colors.selectedContainerColor,
|
||||
selectedIconColor = colors.selectedIconColor,
|
||||
unselectedIconColor = colors.unselectedIconColor,
|
||||
selectedTextColor = colors.selectedTextColor,
|
||||
unselectedTextColor = colors.unselectedTextColor,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
containerColor = colors.contentContainerColor,
|
||||
contentColor = colors.contentColor,
|
||||
contentWindowInsets = contentWindowInsets,
|
||||
) { padding ->
|
||||
movableContent(padding)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,414 @@
|
||||
/*
|
||||
* Copyright 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.samples.apps.nowinandroid.core.ui
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.slideInHorizontally
|
||||
import androidx.compose.animation.slideOutHorizontally
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.NavigationBar
|
||||
import androidx.compose.material3.NavigationBarDefaults
|
||||
import androidx.compose.material3.NavigationBarItem
|
||||
import androidx.compose.material3.NavigationBarItemDefaults
|
||||
import androidx.compose.material3.NavigationDrawerItem
|
||||
import androidx.compose.material3.NavigationDrawerItemDefaults
|
||||
import androidx.compose.material3.NavigationRail
|
||||
import androidx.compose.material3.NavigationRailDefaults
|
||||
import androidx.compose.material3.NavigationRailItem
|
||||
import androidx.compose.material3.NavigationRailItemDefaults
|
||||
import androidx.compose.material3.PermanentDrawerSheet
|
||||
import androidx.compose.material3.PermanentNavigationDrawer
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.ScaffoldDefaults
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.contentColorFor
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.movableContentOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.window.layout.WindowMetricsCalculator
|
||||
|
||||
class AdaptiveScaffoldNavigationComponentColors internal constructor(
|
||||
val detailsPaneContainerColor: Color,
|
||||
val railContainerColor: Color,
|
||||
val drawerContainerColor: Color,
|
||||
val bottomBarContainerColor: Color,
|
||||
val contentContainerColor: Color,
|
||||
val contentColor: Color,
|
||||
val selectedIconColor: Color,
|
||||
val selectedTextColor: Color,
|
||||
val unselectedIconColor: Color,
|
||||
val unselectedTextColor: Color,
|
||||
val selectedContainerColor: Color,
|
||||
val unselectedContainerColor: Color,
|
||||
)
|
||||
|
||||
object AdaptiveScaffoldNavigationComponentDefaults {
|
||||
@Composable
|
||||
fun colors(
|
||||
detailsPaneContainerColor: Color = NavigationBarDefaults.containerColor,
|
||||
railContainerColor: Color = NavigationRailDefaults.ContainerColor,
|
||||
drawerContainerColor: Color = MaterialTheme.colorScheme.surface,
|
||||
bottomBarContainerColor: Color = NavigationBarDefaults.containerColor,
|
||||
contentContainerColor: Color = MaterialTheme.colorScheme.background,
|
||||
contentColor: Color = contentColorFor(contentContainerColor),
|
||||
selectedIconColor: Color = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||
selectedTextColor: Color = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||
unselectedIconColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
unselectedTextColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
selectedContainerColor: Color = MaterialTheme.colorScheme.secondaryContainer,
|
||||
unselectedContainerColor: Color = MaterialTheme.colorScheme.surface,
|
||||
) = AdaptiveScaffoldNavigationComponentColors(
|
||||
detailsPaneContainerColor = detailsPaneContainerColor,
|
||||
railContainerColor = railContainerColor,
|
||||
drawerContainerColor = drawerContainerColor,
|
||||
bottomBarContainerColor = bottomBarContainerColor,
|
||||
contentContainerColor = contentContainerColor,
|
||||
contentColor = contentColor,
|
||||
selectedIconColor = selectedIconColor,
|
||||
selectedTextColor = selectedTextColor,
|
||||
unselectedIconColor = unselectedIconColor,
|
||||
unselectedTextColor = unselectedTextColor,
|
||||
selectedContainerColor = selectedContainerColor,
|
||||
unselectedContainerColor = unselectedContainerColor,
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun <T> AdaptiveScaffold(
|
||||
modifier: Modifier = Modifier,
|
||||
navigationItems: List<T>,
|
||||
navigationItemTitle: @Composable (item: T, isSelected: Boolean) -> Unit,
|
||||
navigationItemIcon: @Composable (item: T, isSelected: Boolean) -> Unit,
|
||||
isItemSelected: @Composable (item: T) -> Boolean,
|
||||
onNavigationItemClick: (item: T) -> Unit,
|
||||
topBar: @Composable () -> Unit = {},
|
||||
snackbarHost: @Composable () -> Unit = {},
|
||||
colors: AdaptiveScaffoldNavigationComponentColors = AdaptiveScaffoldNavigationComponentDefaults.colors(),
|
||||
contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
|
||||
isDetailsPaneVisible: Boolean = false,
|
||||
detailsPane: @Composable () -> Unit = {},
|
||||
content: @Composable (padding: PaddingValues) -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val metrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context)
|
||||
val widthDp = metrics.bounds.width() / context.resources.displayMetrics.density
|
||||
val movableContent = remember(content) {
|
||||
movableContentOf(content)
|
||||
}
|
||||
|
||||
when {
|
||||
widthDp >= 1240f -> DrawerScaffold(
|
||||
modifier = modifier,
|
||||
navigationItems = navigationItems,
|
||||
navigationItemTitle = navigationItemTitle,
|
||||
navigationItemIcon = navigationItemIcon,
|
||||
isItemSelected = isItemSelected,
|
||||
onNavigationItemClick = onNavigationItemClick,
|
||||
topBar = topBar,
|
||||
snackbarHost = snackbarHost,
|
||||
colors = colors,
|
||||
contentWindowInsets = contentWindowInsets,
|
||||
isDetailsPaneVisible = isDetailsPaneVisible,
|
||||
detailsPane = detailsPane,
|
||||
content = movableContent,
|
||||
)
|
||||
|
||||
widthDp >= 600f -> RailScaffold(
|
||||
modifier = modifier,
|
||||
navigationItems = navigationItems,
|
||||
navigationItemTitle = navigationItemTitle,
|
||||
navigationItemIcon = navigationItemIcon,
|
||||
isItemSelected = isItemSelected,
|
||||
onNavigationItemClick = onNavigationItemClick,
|
||||
topBar = topBar,
|
||||
snackbarHost = snackbarHost,
|
||||
colors = colors,
|
||||
contentWindowInsets = contentWindowInsets,
|
||||
isDetailsPaneVisible = isDetailsPaneVisible,
|
||||
detailsPane = detailsPane,
|
||||
content = movableContent,
|
||||
)
|
||||
|
||||
else -> BottomBarScaffold(
|
||||
modifier = modifier,
|
||||
navigationItems = navigationItems,
|
||||
navigationItemTitle = navigationItemTitle,
|
||||
navigationItemIcon = navigationItemIcon,
|
||||
isItemSelected = isItemSelected,
|
||||
onNavigationItemClick = onNavigationItemClick,
|
||||
topBar = topBar,
|
||||
snackbarHost = snackbarHost,
|
||||
colors = colors,
|
||||
contentWindowInsets = contentWindowInsets,
|
||||
isDetailsPaneVisible = isDetailsPaneVisible,
|
||||
detailsPane = detailsPane,
|
||||
content = movableContent,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun <T> BottomBarScaffold(
|
||||
modifier: Modifier,
|
||||
navigationItems: List<T>,
|
||||
navigationItemTitle: @Composable (item: T, isSelected: Boolean) -> Unit,
|
||||
navigationItemIcon: @Composable (item: T, isSelected: Boolean) -> Unit,
|
||||
isItemSelected: @Composable (item: T) -> Boolean,
|
||||
onNavigationItemClick: (item: T) -> Unit,
|
||||
topBar: @Composable () -> Unit,
|
||||
snackbarHost: @Composable () -> Unit,
|
||||
colors: AdaptiveScaffoldNavigationComponentColors,
|
||||
contentWindowInsets: WindowInsets,
|
||||
isDetailsPaneVisible: Boolean,
|
||||
detailsPane: @Composable () -> Unit,
|
||||
content: @Composable (padding: PaddingValues) -> Unit,
|
||||
) {
|
||||
Box(modifier = modifier) {
|
||||
Scaffold(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
topBar = topBar,
|
||||
snackbarHost = snackbarHost,
|
||||
bottomBar = {
|
||||
NavigationBar(
|
||||
containerColor = colors.bottomBarContainerColor,
|
||||
) {
|
||||
navigationItems.forEach { item ->
|
||||
NavigationBarItem(
|
||||
label = { navigationItemTitle(item, isItemSelected(item)) },
|
||||
icon = { navigationItemIcon(item, isItemSelected(item)) },
|
||||
selected = isItemSelected(item),
|
||||
onClick = { onNavigationItemClick(item) },
|
||||
colors = NavigationBarItemDefaults.colors(
|
||||
indicatorColor = colors.selectedContainerColor,
|
||||
selectedIconColor = colors.selectedIconColor,
|
||||
unselectedIconColor = colors.unselectedIconColor,
|
||||
selectedTextColor = colors.selectedTextColor,
|
||||
unselectedTextColor = colors.unselectedTextColor,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
containerColor = colors.contentContainerColor,
|
||||
contentColor = colors.contentColor,
|
||||
contentWindowInsets = contentWindowInsets,
|
||||
) { padding ->
|
||||
content(padding)
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = isDetailsPaneVisible,
|
||||
enter = slideInHorizontally(initialOffsetX = { it }),
|
||||
exit = slideOutHorizontally(targetOffsetX = { it }),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
Surface(color = colors.detailsPaneContainerColor) {
|
||||
detailsPane()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun <T> RailScaffold(
|
||||
modifier: Modifier,
|
||||
navigationItems: List<T>,
|
||||
navigationItemTitle: @Composable (item: T, isSelected: Boolean) -> Unit,
|
||||
navigationItemIcon: @Composable (item: T, isSelected: Boolean) -> Unit,
|
||||
isItemSelected: @Composable (item: T) -> Boolean,
|
||||
onNavigationItemClick: (item: T) -> Unit,
|
||||
topBar: @Composable () -> Unit,
|
||||
snackbarHost: @Composable () -> Unit,
|
||||
colors: AdaptiveScaffoldNavigationComponentColors,
|
||||
contentWindowInsets: WindowInsets,
|
||||
isDetailsPaneVisible: Boolean,
|
||||
detailsPane: @Composable () -> Unit,
|
||||
content: @Composable (padding: PaddingValues) -> Unit,
|
||||
) {
|
||||
val weight: Float by animateFloatAsState(
|
||||
targetValue = if (isDetailsPaneVisible) 0.5f else 1f,
|
||||
animationSpec = tween(durationMillis = 500),
|
||||
label = "Details Pane",
|
||||
)
|
||||
|
||||
Row(modifier = modifier) {
|
||||
NavigationRail(
|
||||
containerColor = colors.railContainerColor,
|
||||
) {
|
||||
navigationItems.forEach { item ->
|
||||
NavigationRailItem(
|
||||
label = { navigationItemTitle(item, isItemSelected(item)) },
|
||||
icon = { navigationItemIcon(item, isItemSelected(item)) },
|
||||
selected = isItemSelected(item),
|
||||
onClick = { onNavigationItemClick(item) },
|
||||
colors = NavigationRailItemDefaults.colors(
|
||||
indicatorColor = colors.selectedContainerColor,
|
||||
selectedIconColor = colors.selectedIconColor,
|
||||
unselectedIconColor = colors.unselectedIconColor,
|
||||
selectedTextColor = colors.selectedTextColor,
|
||||
unselectedTextColor = colors.unselectedTextColor,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
Scaffold(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.fillMaxWidth(weight),
|
||||
topBar = topBar,
|
||||
snackbarHost = snackbarHost,
|
||||
containerColor = colors.contentContainerColor,
|
||||
contentColor = colors.contentColor,
|
||||
contentWindowInsets = contentWindowInsets,
|
||||
) { padding ->
|
||||
content(padding)
|
||||
}
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.weight(1f),
|
||||
color = colors.detailsPaneContainerColor,
|
||||
) {
|
||||
detailsPane()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun <T> DrawerScaffold(
|
||||
modifier: Modifier,
|
||||
navigationItems: List<T>,
|
||||
navigationItemTitle: @Composable (item: T, isSelected: Boolean) -> Unit,
|
||||
navigationItemIcon: @Composable (item: T, isSelected: Boolean) -> Unit,
|
||||
isItemSelected: @Composable (item: T) -> Boolean,
|
||||
onNavigationItemClick: (item: T) -> Unit,
|
||||
topBar: @Composable () -> Unit,
|
||||
snackbarHost: @Composable () -> Unit,
|
||||
colors: AdaptiveScaffoldNavigationComponentColors,
|
||||
contentWindowInsets: WindowInsets,
|
||||
isDetailsPaneVisible: Boolean,
|
||||
detailsPane: @Composable () -> Unit,
|
||||
content: @Composable (padding: PaddingValues) -> Unit,
|
||||
) {
|
||||
val weight: Float by animateFloatAsState(
|
||||
targetValue = if (isDetailsPaneVisible) 0.5f else 1f,
|
||||
animationSpec = tween(durationMillis = 500),
|
||||
label = "Details Pane",
|
||||
)
|
||||
|
||||
Row(modifier = modifier) {
|
||||
Scaffold(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.fillMaxWidth(weight),
|
||||
topBar = topBar,
|
||||
snackbarHost = snackbarHost,
|
||||
containerColor = colors.contentContainerColor,
|
||||
contentColor = colors.contentColor,
|
||||
contentWindowInsets = contentWindowInsets,
|
||||
) { padding ->
|
||||
PermanentNavigationDrawer(
|
||||
drawerContent = {
|
||||
PermanentDrawerSheet(
|
||||
drawerContainerColor = colors.drawerContainerColor,
|
||||
) {
|
||||
navigationItems.forEach { item ->
|
||||
NavigationDrawerItem(
|
||||
label = { navigationItemTitle(item, isItemSelected(item)) },
|
||||
icon = { navigationItemIcon(item, isItemSelected(item)) },
|
||||
selected = isItemSelected(item),
|
||||
onClick = { onNavigationItemClick(item) },
|
||||
colors = NavigationDrawerItemDefaults.colors(
|
||||
selectedContainerColor = colors.selectedContainerColor,
|
||||
unselectedContainerColor = colors.unselectedContainerColor,
|
||||
selectedIconColor = colors.selectedIconColor,
|
||||
unselectedIconColor = colors.unselectedIconColor,
|
||||
selectedTextColor = colors.selectedTextColor,
|
||||
unselectedTextColor = colors.unselectedTextColor,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier.padding(padding),
|
||||
) {
|
||||
content(padding)
|
||||
}
|
||||
}
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.weight(1f),
|
||||
color = colors.detailsPaneContainerColor,
|
||||
) {
|
||||
detailsPane()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun <T> AdaptiveScaffold(
|
||||
navigator: AdaptiveScaffoldNavigator,
|
||||
modifier: Modifier = Modifier,
|
||||
navigationItems: List<T>,
|
||||
navigationItemTitle: @Composable (item: T, isSelected: Boolean) -> Unit,
|
||||
navigationItemIcon: @Composable (item: T, isSelected: Boolean) -> Unit,
|
||||
isItemSelected: @Composable (item: T) -> Boolean,
|
||||
onNavigationItemClick: (item: T) -> Unit,
|
||||
topBar: @Composable () -> Unit = {},
|
||||
snackbarHost: @Composable () -> Unit = {},
|
||||
colors: AdaptiveScaffoldNavigationComponentColors = AdaptiveScaffoldNavigationComponentDefaults.colors(),
|
||||
contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
|
||||
content: @Composable (padding: PaddingValues) -> Unit,
|
||||
) {
|
||||
val isDetailsPaneVisible by navigator.isVisible.collectAsState(initial = false)
|
||||
AdaptiveScaffold(
|
||||
modifier = modifier,
|
||||
navigationItems = navigationItems,
|
||||
navigationItemTitle = navigationItemTitle,
|
||||
navigationItemIcon = navigationItemIcon,
|
||||
isItemSelected = isItemSelected,
|
||||
onNavigationItemClick = onNavigationItemClick,
|
||||
topBar = topBar,
|
||||
snackbarHost = snackbarHost,
|
||||
colors = colors,
|
||||
contentWindowInsets = contentWindowInsets,
|
||||
isDetailsPaneVisible = isDetailsPaneVisible,
|
||||
detailsPane = navigator.detailsPaneContent,
|
||||
content = content,
|
||||
)
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.samples.apps.nowinandroid.core.ui
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ScaffoldDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.saveable.rememberSaveableStateHolder
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.navigation.FloatingWindow
|
||||
import androidx.navigation.NamedNavArgument
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
import androidx.navigation.NavDeepLink
|
||||
import androidx.navigation.NavDestination
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import androidx.navigation.Navigator
|
||||
import androidx.navigation.NavigatorState
|
||||
import androidx.navigation.compose.LocalOwnersProvider
|
||||
import androidx.navigation.get
|
||||
import com.google.samples.apps.nowinandroid.core.ui.AdaptiveScaffoldNavigator.Destination
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
@Navigator.Name("AdaptiveScaffoldNavigator")
|
||||
class AdaptiveScaffoldNavigator : Navigator<Destination>() {
|
||||
|
||||
private var attached = MutableStateFlow(false)
|
||||
|
||||
private val backStack: Flow<List<NavBackStackEntry>> = attached.flatMapLatest {
|
||||
if (it) {
|
||||
state.backStack
|
||||
} else {
|
||||
MutableStateFlow(emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
val isVisible = backStack.map { it.isNotEmpty() }
|
||||
|
||||
val detailsPaneContent: @Composable () -> Unit = {
|
||||
val saveableStateHolder = rememberSaveableStateHolder()
|
||||
val currentBackStackEntry: NavBackStackEntry? by produceState<NavBackStackEntry?>(
|
||||
initialValue = null,
|
||||
key1 = backStack,
|
||||
) {
|
||||
backStack.map { backstack ->
|
||||
backstack.lastOrNull()
|
||||
}.collect { backStackEntry ->
|
||||
value = backStackEntry
|
||||
}
|
||||
}
|
||||
val backStackEntry = currentBackStackEntry
|
||||
backStackEntry?.LocalOwnersProvider(saveableStateHolder) {
|
||||
val destination = (backStackEntry.destination as? Destination)
|
||||
destination?.content?.invoke(backStackEntry)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAttach(state: NavigatorState) {
|
||||
super.onAttach(state)
|
||||
attached.value = true
|
||||
}
|
||||
|
||||
override fun navigate(
|
||||
entries: List<NavBackStackEntry>,
|
||||
navOptions: NavOptions?,
|
||||
navigatorExtras: Extras?,
|
||||
) {
|
||||
entries.forEach { entry ->
|
||||
state.pushWithTransition(entry)
|
||||
}
|
||||
}
|
||||
|
||||
override fun popBackStack(popUpTo: NavBackStackEntry, savedState: Boolean) {
|
||||
state.popWithTransition(popUpTo, savedState)
|
||||
}
|
||||
|
||||
@NavDestination.ClassType(Composable::class)
|
||||
class Destination(
|
||||
navigator: AdaptiveScaffoldNavigator,
|
||||
internal val content: @Composable (NavBackStackEntry) -> Unit,
|
||||
) : NavDestination(navigator), FloatingWindow
|
||||
|
||||
override fun createDestination(): Destination = Destination(
|
||||
this,
|
||||
) { /* no-op */ }
|
||||
}
|
||||
|
||||
fun NavGraphBuilder.detailsPane(
|
||||
route: String,
|
||||
arguments: List<NamedNavArgument> = emptyList(),
|
||||
deepLinks: List<NavDeepLink> = emptyList(),
|
||||
content: @Composable (backstackEntry: NavBackStackEntry) -> Unit,
|
||||
) {
|
||||
addDestination(
|
||||
Destination(
|
||||
provider[AdaptiveScaffoldNavigator::class],
|
||||
content,
|
||||
).apply {
|
||||
this.route = route
|
||||
arguments.forEach { (argumentName, argument) ->
|
||||
addArgument(argumentName, argument)
|
||||
}
|
||||
deepLinks.forEach { deepLink ->
|
||||
addDeepLink(deepLink)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
Loading…
Reference in new issue