add hamburger icon to rail
This commit is contained in:
@@ -7,7 +7,7 @@ plugins {
|
||||
alias(libs.plugins.convention.ktor.client)
|
||||
}
|
||||
|
||||
val appVersion = "1.26.4"
|
||||
val appVersion = "1.26.5"
|
||||
|
||||
android {
|
||||
buildFeatures { buildConfig = true }
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M120,720L120,640L840,640L840,720L120,720ZM120,520L120,440L840,440L840,520L120,520ZM120,320L120,240L840,240L840,320L120,320Z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M120,720L120,640L640,640L640,720L120,720ZM784,680L584,480L784,280L840,336L696,480L840,624L784,680ZM120,520L120,440L520,440L520,520L120,520ZM120,320L120,240L640,240L640,320L120,320Z" />
|
||||
</vector>
|
||||
@@ -1,52 +1,22 @@
|
||||
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
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.NavigationBar
|
||||
import androidx.compose.material3.NavigationBarDefaults
|
||||
import androidx.compose.material3.NavigationBarItem
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
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.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
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.unit.dp
|
||||
import ru.shadowsparky.ui.nav.Route
|
||||
|
||||
@Immutable
|
||||
@@ -141,128 +111,3 @@ fun <T : Route> AppScaffold(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun <T : Route> DynamicNavigationScaffold(
|
||||
modifier: Modifier,
|
||||
navigationSuiteType: NavigationSuiteType,
|
||||
navigationEntries: List<NavigationEntry<T>>?,
|
||||
currentRoute: T?,
|
||||
onSelectNavigation: ((T) -> Unit)?,
|
||||
scrollBehavior: TopAppBarScrollBehavior?,
|
||||
appBar: @Composable (TopAppBarScrollBehavior?) -> Unit,
|
||||
snackbarState: SnackbarHostState?,
|
||||
content: @Composable (PaddingValues) -> Unit,
|
||||
) {
|
||||
val entries = navigationEntries.orEmpty()
|
||||
val railState = rememberWideNavigationRailState(WideNavigationRailValue.Collapsed)
|
||||
val isExpanded = railState.targetValue == WideNavigationRailValue.Expanded
|
||||
var railHasFocus by remember { mutableStateOf(false) }
|
||||
LaunchedEffect(railHasFocus) {
|
||||
if (railHasFocus) railState.expand() else railState.collapse()
|
||||
}
|
||||
NavigationSuiteScaffoldLayout(
|
||||
navigationSuiteType = navigationSuiteType,
|
||||
navigationSuite = {
|
||||
WideNavigationRail(
|
||||
state = railState,
|
||||
modifier = Modifier.onFocusChanged { railHasFocus = it.hasFocus }.focusGroup()
|
||||
) {
|
||||
entries.forEach { entry ->
|
||||
WideNavigationRailItem(
|
||||
railExpanded = isExpanded,
|
||||
selected = currentRoute == entry.route,
|
||||
onClick = { onSelectNavigation?.invoke(entry.route) },
|
||||
icon = { Icon(entry.painter, entry.title) },
|
||||
label = {
|
||||
if (isExpanded) Text(entry.title)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
Scaffold(
|
||||
modifier = if (scrollBehavior != null) {
|
||||
modifier.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||
} else {
|
||||
modifier
|
||||
},
|
||||
topBar = { appBar(scrollBehavior) },
|
||||
snackbarHost = {
|
||||
snackbarState?.let { SnackbarHost(it) }
|
||||
},
|
||||
content = content
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun <T : Route> BottomBarScaffold(
|
||||
modifier: Modifier,
|
||||
navigationEntries: List<NavigationEntry<T>>?,
|
||||
currentRoute: T?,
|
||||
appBar: @Composable (scroll: TopAppBarScrollBehavior?) -> Unit,
|
||||
content: @Composable (padding: PaddingValues) -> Unit,
|
||||
scrollBehavior: TopAppBarScrollBehavior?,
|
||||
onSelectNavigation: ((T) -> Unit)?,
|
||||
snackbarState: SnackbarHostState?
|
||||
) {
|
||||
Scaffold(
|
||||
bottomBar = { BottomBar(navigationEntries, currentRoute, onSelectNavigation) },
|
||||
topBar = { appBar(scrollBehavior) },
|
||||
modifier = if (scrollBehavior != null) {
|
||||
modifier.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||
} else {
|
||||
modifier
|
||||
}, snackbarHost = {
|
||||
snackbarState?.let { SnackbarHost(it) }
|
||||
},
|
||||
content = content
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun <T : Route> BottomBar(
|
||||
navigationEntries: List<NavigationEntry<T>>?,
|
||||
currentRoute: T?,
|
||||
onSelectNavigation: ((T) -> Unit)?
|
||||
) {
|
||||
navigationEntries?.let { entries ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(
|
||||
Brush.verticalGradient(
|
||||
listOf(Color.Transparent, MaterialTheme.colorScheme.surfaceContainer)
|
||||
)
|
||||
)
|
||||
.padding(8.dp)
|
||||
.windowInsetsPadding(WindowInsets.navigationBars)
|
||||
) {
|
||||
NavigationBar(
|
||||
containerColor = navBarContentColor(),
|
||||
modifier = Modifier.clip(RoundedCornerShape(24.dp)).blurEffect()
|
||||
) {
|
||||
entries.forEach { entry ->
|
||||
NavigationBarItem(
|
||||
selected = currentRoute == entry.route,
|
||||
onClick = { onSelectNavigation?.invoke(entry.route) },
|
||||
icon = { Icon(entry.painter, contentDescription = entry.title) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun navBarContentColor(): Color {
|
||||
return if (isBlurEnabled()) {
|
||||
MaterialTheme.colorScheme.surfaceContainerHigh.copy(alpha = 0.5f)
|
||||
} else {
|
||||
NavigationBarDefaults.containerColor
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
package ru.shadowsparky.ui
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.NavigationBar
|
||||
import androidx.compose.material3.NavigationBarDefaults
|
||||
import androidx.compose.material3.NavigationBarItem
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.unit.dp
|
||||
import ru.shadowsparky.ui.nav.Route
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
internal fun <T : Route> BottomBarScaffold(
|
||||
modifier: Modifier,
|
||||
navigationEntries: List<NavigationEntry<T>>?,
|
||||
currentRoute: T?,
|
||||
appBar: @Composable (scroll: TopAppBarScrollBehavior?) -> Unit,
|
||||
content: @Composable (padding: PaddingValues) -> Unit,
|
||||
scrollBehavior: TopAppBarScrollBehavior?,
|
||||
onSelectNavigation: ((T) -> Unit)?,
|
||||
snackbarState: SnackbarHostState?
|
||||
) {
|
||||
Scaffold(
|
||||
bottomBar = { BottomBar(navigationEntries, currentRoute, onSelectNavigation) },
|
||||
topBar = { appBar(scrollBehavior) },
|
||||
modifier = if (scrollBehavior != null) {
|
||||
modifier.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||
} else {
|
||||
modifier
|
||||
}, snackbarHost = {
|
||||
snackbarState?.let { SnackbarHost(it) }
|
||||
},
|
||||
content = content
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun <T : Route> BottomBar(
|
||||
navigationEntries: List<NavigationEntry<T>>?,
|
||||
currentRoute: T?,
|
||||
onSelectNavigation: ((T) -> Unit)?
|
||||
) {
|
||||
navigationEntries?.let { entries ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(
|
||||
Brush.verticalGradient(
|
||||
listOf(Color.Transparent, MaterialTheme.colorScheme.surfaceContainer)
|
||||
)
|
||||
)
|
||||
.padding(8.dp)
|
||||
.windowInsetsPadding(WindowInsets.navigationBars)
|
||||
) {
|
||||
NavigationBar(
|
||||
containerColor = navBarContentColor(),
|
||||
modifier = Modifier.clip(RoundedCornerShape(24.dp)).blurEffect()
|
||||
) {
|
||||
entries.forEach { entry ->
|
||||
NavigationBarItem(
|
||||
selected = currentRoute == entry.route,
|
||||
onClick = { onSelectNavigation?.invoke(entry.route) },
|
||||
icon = { Icon(entry.painter, contentDescription = entry.title) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun navBarContentColor(): Color {
|
||||
return if (isBlurEnabled()) {
|
||||
MaterialTheme.colorScheme.surfaceContainerHigh.copy(alpha = 0.5f)
|
||||
} else {
|
||||
NavigationBarDefaults.containerColor
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package ru.shadowsparky.ui
|
||||
|
||||
import androiddev.libs.ui.generated.resources.Res
|
||||
import androiddev.libs.ui.generated.resources.menu
|
||||
import androiddev.libs.ui.generated.resources.menu_open
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.material3.WideNavigationRail
|
||||
import androidx.compose.material3.WideNavigationRailItem
|
||||
import androidx.compose.material3.WideNavigationRailState
|
||||
import androidx.compose.material3.WideNavigationRailValue
|
||||
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.LaunchedEffect
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
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.input.nestedscroll.nestedScroll
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import ru.shadowsparky.ui.nav.Route
|
||||
|
||||
internal val LocalWideNavigationRailState = compositionLocalOf<WideNavigationRailState?> { null }
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
internal fun <T : Route> DynamicNavigationScaffold(
|
||||
modifier: Modifier,
|
||||
navigationSuiteType: NavigationSuiteType,
|
||||
navigationEntries: List<NavigationEntry<T>>?,
|
||||
currentRoute: T?,
|
||||
onSelectNavigation: ((T) -> Unit)?,
|
||||
scrollBehavior: TopAppBarScrollBehavior?,
|
||||
appBar: @Composable (TopAppBarScrollBehavior?) -> Unit,
|
||||
snackbarState: SnackbarHostState?,
|
||||
content: @Composable (PaddingValues) -> Unit,
|
||||
) {
|
||||
val entries = navigationEntries.orEmpty()
|
||||
val railState = LocalWideNavigationRailState.current
|
||||
?: rememberWideNavigationRailState(WideNavigationRailValue.Collapsed)
|
||||
var isExpanded by remember {
|
||||
mutableStateOf(railState.targetValue == WideNavigationRailValue.Expanded)
|
||||
}
|
||||
LaunchedEffect(isExpanded) {
|
||||
if (!isExpanded) railState.collapse() else railState.expand()
|
||||
}
|
||||
NavigationSuiteScaffoldLayout(
|
||||
navigationSuiteType = navigationSuiteType,
|
||||
navigationSuite = {
|
||||
WideNavigationRail(state = railState) {
|
||||
val resId = if (isExpanded) Res.drawable.menu_open else Res.drawable.menu
|
||||
WideNavigationRailItem(
|
||||
false,
|
||||
onClick = { isExpanded = !isExpanded },
|
||||
icon = { Icon(painterResource(resId), null) },
|
||||
label = {},
|
||||
railExpanded = isExpanded
|
||||
)
|
||||
entries.forEach { entry ->
|
||||
WideNavigationRailItem(
|
||||
railExpanded = isExpanded,
|
||||
selected = currentRoute == entry.route,
|
||||
onClick = { onSelectNavigation?.invoke(entry.route) },
|
||||
icon = { Icon(entry.painter, entry.title) },
|
||||
label = { if (isExpanded) Text(entry.title) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
Scaffold(
|
||||
modifier = if (scrollBehavior != null) {
|
||||
modifier.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||
} else {
|
||||
modifier
|
||||
},
|
||||
topBar = { appBar(scrollBehavior) },
|
||||
snackbarHost = { snackbarState?.let { SnackbarHost(it) } },
|
||||
content = content
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -104,21 +104,14 @@ fun ExpressiveLazyColumn(
|
||||
verticalArrangement = verticalArrangement
|
||||
) {
|
||||
scope.sections.forEachIndexed { sectionIndex, section ->
|
||||
|
||||
if (sectionIndex != 0 && sectionSpacing > 0.dp) {
|
||||
item(key = "sectionSpacer_$sectionIndex") {
|
||||
Spacer(
|
||||
Modifier
|
||||
.animateItem()
|
||||
.height(sectionSpacing)
|
||||
)
|
||||
Spacer(Modifier.animateItem().height(sectionSpacing))
|
||||
}
|
||||
}
|
||||
|
||||
section.nodes.forEachIndexed { index, node ->
|
||||
val topRadius = if (index == 0) roundedMax else roundedMin
|
||||
val bottomRadius = if (index == section.nodes.lastIndex) roundedMax else roundedMin
|
||||
|
||||
item(key = node.key, contentType = node.contentType) {
|
||||
ExpressiveCard(
|
||||
topRadius = if (reverseLayout) bottomRadius else topRadius,
|
||||
|
||||
@@ -2,13 +2,17 @@ package ru.shadowsparky.ui.nav
|
||||
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.MaterialExpressiveTheme
|
||||
import androidx.compose.material3.WideNavigationRailValue
|
||||
import androidx.compose.material3.rememberWideNavigationRailState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import com.arkivanov.decompose.ExperimentalDecomposeApi
|
||||
import com.arkivanov.decompose.extensions.compose.stack.Children
|
||||
import com.arkivanov.decompose.extensions.compose.stack.animation.fade
|
||||
import com.arkivanov.decompose.extensions.compose.stack.animation.predictiveback.androidPredictiveBackAnimatableV2
|
||||
import com.arkivanov.decompose.extensions.compose.stack.animation.predictiveback.predictiveBackAnimation
|
||||
import com.arkivanov.decompose.extensions.compose.stack.animation.stackAnimation
|
||||
import ru.shadowsparky.ui.LocalWideNavigationRailState
|
||||
import ru.shadowsparky.ui.theme.getColorScheme
|
||||
|
||||
@OptIn(ExperimentalDecomposeApi::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||
@@ -17,17 +21,20 @@ fun Nav(
|
||||
rootComponent: RootComponent<*>,
|
||||
process: @Composable (child: Child, route: Route) -> Unit
|
||||
) {
|
||||
MaterialExpressiveTheme(getColorScheme()) {
|
||||
Children(
|
||||
stack = rootComponent.valueChildStack,
|
||||
animation = predictiveBackAnimation(
|
||||
backHandler = rootComponent.backHandler,
|
||||
fallbackAnimation = stackAnimation(fade()),
|
||||
onBack = rootComponent::onBack,
|
||||
selector = { backEvent, _, _ -> androidPredictiveBackAnimatableV2(backEvent) },
|
||||
),
|
||||
) {
|
||||
process(it.instance, it.configuration as Route)
|
||||
val railState = rememberWideNavigationRailState(WideNavigationRailValue.Collapsed)
|
||||
CompositionLocalProvider(LocalWideNavigationRailState provides railState) {
|
||||
MaterialExpressiveTheme(getColorScheme()) {
|
||||
Children(
|
||||
stack = rootComponent.valueChildStack,
|
||||
animation = predictiveBackAnimation(
|
||||
backHandler = rootComponent.backHandler,
|
||||
fallbackAnimation = stackAnimation(fade()),
|
||||
onBack = rootComponent::onBack,
|
||||
selector = { backEvent, _, _ -> androidPredictiveBackAnimatableV2(backEvent) },
|
||||
),
|
||||
) {
|
||||
process(it.instance, it.configuration as Route)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user