🛠️ New CustomModalDrawer
* Follows Material3 * Does not require to wrap screen's content
This commit is contained in:
parent
57cbdfd057
commit
1c23694d6d
3 changed files with 246 additions and 0 deletions
|
|
@ -0,0 +1,140 @@
|
||||||
|
package ua.acclorite.book_story.presentation.core.components.custom_drawer
|
||||||
|
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.compose.animation.core.animateDpAsState
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.animation.slideInHorizontally
|
||||||
|
import androidx.compose.animation.slideOutHorizontally
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.gestures.detectHorizontalDragGestures
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.offset
|
||||||
|
import androidx.compose.foundation.layout.systemBarsPadding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.foundation.shape.CornerSize
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.unit.IntOffset
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import ua.acclorite.book_story.presentation.core.components.CustomAnimatedVisibility
|
||||||
|
import ua.acclorite.book_story.presentation.core.components.CustomLazyColumn
|
||||||
|
import ua.acclorite.book_story.presentation.core.util.noRippleClickable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom Modal Drawer.
|
||||||
|
* Follows Material3 guidelines. Does not require to wrap screen's content and works independently.
|
||||||
|
* Do not wrap in if(show) { ... }, use [show].
|
||||||
|
* Should be placed at the bottom of the composable to be shown at top.
|
||||||
|
*
|
||||||
|
* @param show Whether should be shown. Animates enter and exit.
|
||||||
|
* @param startIndex Start index of the item to show when opening drawer.
|
||||||
|
* @param onDismissRequest Dismiss callback. Called when dragging, pressing back or clicking outside drawer.
|
||||||
|
* @param header Header of the drawer. Shown at the top.
|
||||||
|
* @param content Content inside drawer.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun CustomModalDrawer(
|
||||||
|
show: Boolean,
|
||||||
|
startIndex: Int = 0,
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
header: @Composable () -> Unit = {},
|
||||||
|
content: LazyListScope.() -> Unit
|
||||||
|
) {
|
||||||
|
val density = LocalDensity.current
|
||||||
|
val offset = remember { mutableFloatStateOf(0f) }
|
||||||
|
val offsetDp = remember {
|
||||||
|
derivedStateOf {
|
||||||
|
with(density) { offset.floatValue.toDp() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val animatedScrimColor = animateFloatAsState(
|
||||||
|
targetValue = if (show && offsetDp.value > (-60).dp) 0.35f else 0f
|
||||||
|
)
|
||||||
|
val animatedOffset = animateDpAsState(
|
||||||
|
targetValue = offsetDp.value
|
||||||
|
)
|
||||||
|
|
||||||
|
LaunchedEffect(show) {
|
||||||
|
if (show) offset.floatValue = 0f
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(Color.Black.copy(animatedScrimColor.value))
|
||||||
|
.then(
|
||||||
|
if (!show) Modifier
|
||||||
|
else Modifier.noRippleClickable(onClick = onDismissRequest)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
CustomAnimatedVisibility(
|
||||||
|
visible = show,
|
||||||
|
enter = slideInHorizontally { (-it + with(density) { -20.dp.toPx() }).toInt() },
|
||||||
|
exit = slideOutHorizontally { (-it + with(density) { -20.dp.toPx() }).toInt() }
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.offset {
|
||||||
|
IntOffset(
|
||||||
|
x = animatedOffset.value
|
||||||
|
.toPx()
|
||||||
|
.toInt(),
|
||||||
|
y = 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.fillMaxHeight()
|
||||||
|
.width(360.dp)
|
||||||
|
.clip(
|
||||||
|
MaterialTheme.shapes.large.copy(
|
||||||
|
topStart = CornerSize(0.dp),
|
||||||
|
bottomStart = CornerSize(0.dp)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.background(MaterialTheme.colorScheme.surfaceContainerLow)
|
||||||
|
.noRippleClickable {}
|
||||||
|
.pointerInput(Unit) {
|
||||||
|
detectHorizontalDragGestures(
|
||||||
|
onDragStart = { offset.floatValue = 0f },
|
||||||
|
onDragEnd = {
|
||||||
|
if (offset.floatValue.toDp() < (-60).dp) onDismissRequest()
|
||||||
|
else offset.floatValue = 0f
|
||||||
|
}
|
||||||
|
) { _, dragAmount ->
|
||||||
|
offset.floatValue = (offset.floatValue + dragAmount)
|
||||||
|
.coerceAtMost(0f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.systemBarsPadding()
|
||||||
|
) {
|
||||||
|
header()
|
||||||
|
CustomLazyColumn(
|
||||||
|
state = rememberLazyListState(startIndex),
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentPadding = PaddingValues(vertical = 9.dp)
|
||||||
|
) {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BackHandler {
|
||||||
|
onDismissRequest()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
package ua.acclorite.book_story.presentation.core.components.custom_drawer
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.RowScope
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material3.LocalContentColor
|
||||||
|
import androidx.compose.material3.LocalTextStyle
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import ua.acclorite.book_story.domain.util.Selected
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom Modal Drawer Selectable Item.
|
||||||
|
* Can be selected.
|
||||||
|
*
|
||||||
|
* @param modifier Modifier to be applied.
|
||||||
|
* @param selected Whether this item is selected.
|
||||||
|
* @param enabled Whether this item is enabled.
|
||||||
|
* @param onClick OnClick callback.
|
||||||
|
* @param content Content of the item.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun CustomModalDrawerSelectableItem(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
selected: Selected,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
content: @Composable RowScope.() -> Unit
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = modifier
|
||||||
|
.padding(horizontal = 8.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(
|
||||||
|
if (selected) MaterialTheme.colorScheme.secondaryContainer
|
||||||
|
else Color.Transparent
|
||||||
|
)
|
||||||
|
.clickable(enabled = enabled) {
|
||||||
|
onClick()
|
||||||
|
}
|
||||||
|
.padding(horizontal = 18.dp, vertical = 18.dp)
|
||||||
|
) {
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalContentColor provides if (selected) MaterialTheme.colorScheme.onSecondaryContainer
|
||||||
|
else MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
LocalTextStyle provides MaterialTheme.typography.labelLarge
|
||||||
|
) {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
package ua.acclorite.book_story.presentation.core.components.custom_drawer
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom Modal Drawer Title Item.
|
||||||
|
* Usually displayed at the top of the drawer.
|
||||||
|
*
|
||||||
|
* @param modifier Modifier to be applied.
|
||||||
|
* @param title Title to be shown.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun CustomModalDrawerTitleItem(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
title: String
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(MaterialTheme.colorScheme.surfaceContainerLow)
|
||||||
|
.padding(horizontal = 18.dp)
|
||||||
|
) {
|
||||||
|
Spacer(modifier = Modifier.height(9.dp))
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
modifier = Modifier.padding(horizontal = 18.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(9.dp))
|
||||||
|
HorizontalDivider()
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue