🛠️ 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