From 1c23694d6ddf25d426c167ae074d4c0a14f18493 Mon Sep 17 00:00:00 2001 From: Acclorite <140836141+Acclorite@users.noreply.github.com> Date: Sun, 15 Sep 2024 15:36:24 +0300 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20New=20CustomModalDrawer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Follows Material3 * Does not require to wrap screen's content --- .../custom_drawer/CustomModalDrawer.kt | 140 ++++++++++++++++++ .../CustomModalDrawerSelectableItem.kt | 61 ++++++++ .../CustomModalDrawerTitleItem.kt | 45 ++++++ 3 files changed, 246 insertions(+) create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/core/components/custom_drawer/CustomModalDrawer.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/core/components/custom_drawer/CustomModalDrawerSelectableItem.kt create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/core/components/custom_drawer/CustomModalDrawerTitleItem.kt diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/core/components/custom_drawer/CustomModalDrawer.kt b/app/src/main/java/ua/acclorite/book_story/presentation/core/components/custom_drawer/CustomModalDrawer.kt new file mode 100644 index 00000000..19fd8f20 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/presentation/core/components/custom_drawer/CustomModalDrawer.kt @@ -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() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/core/components/custom_drawer/CustomModalDrawerSelectableItem.kt b/app/src/main/java/ua/acclorite/book_story/presentation/core/components/custom_drawer/CustomModalDrawerSelectableItem.kt new file mode 100644 index 00000000..ba86c762 --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/presentation/core/components/custom_drawer/CustomModalDrawerSelectableItem.kt @@ -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() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/core/components/custom_drawer/CustomModalDrawerTitleItem.kt b/app/src/main/java/ua/acclorite/book_story/presentation/core/components/custom_drawer/CustomModalDrawerTitleItem.kt new file mode 100644 index 00000000..d602fe4e --- /dev/null +++ b/app/src/main/java/ua/acclorite/book_story/presentation/core/components/custom_drawer/CustomModalDrawerTitleItem.kt @@ -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)) + } +} \ No newline at end of file