Skip to content

Instantly share code, notes, and snippets.

@makeevrserg
Created February 3, 2025 09:14
Show Gist options
  • Select an option

  • Save makeevrserg/1e3661ffb13f346a6241476924f68c95 to your computer and use it in GitHub Desktop.

Select an option

Save makeevrserg/1e3661ffb13f346a6241476924f68c95 to your computer and use it in GitHub Desktop.
DeclarativeModalBottomSheet
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.slot.ChildSlot
import com.arkivanov.decompose.value.Value
import com.composables.core.BottomSheetScope
import com.composables.core.DragIndication
import com.composables.core.ModalBottomSheet
import com.composables.core.ModalBottomSheetScope
import com.composables.core.Scrim
import com.composables.core.Sheet
import com.composables.core.SheetDetent
import com.composables.core.rememberModalBottomSheetState
import com.flipperdevices.bsb.core.theme.LocalPallet
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.drop
@Composable
fun BSheetDragIndicator(modifier: Modifier = Modifier) {
Box(
modifier = modifier
.height(5.dp)
.fillMaxWidth(0.3f)
.clip(RoundedCornerShape(100.dp))
.background(LocalPallet.current.transparent.whiteInvert.tertiary.copy(0.1f))
)
}
@Composable
fun ModalBottomSheetScope.BModalBottomSheetContent(
background: Color = Color(0xFF1E1E1E),
content: @Composable BottomSheetScope.() -> Unit
) {
Scrim()
Sheet(
modifier = Modifier
.padding(top = 12.dp)
.statusBarsPadding()
.padding(
WindowInsets.navigationBars.only(WindowInsetsSides.Horizontal).asPaddingValues()
)
.shadow(4.dp, RoundedCornerShape(topStart = 28.dp, topEnd = 28.dp))
.clip(RoundedCornerShape(topStart = 28.dp, topEnd = 28.dp))
.background(background) // todo
.padding(horizontal = 24.dp)
.fillMaxWidth()
.imePadding(),
) {
Column {
DragIndication()
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 24.dp),
contentAlignment = Alignment.Center,
content = { BSheetDragIndicator() }
)
content.invoke(this@Sheet)
}
}
}
private val emptyContent: @Composable ModalBottomSheetScope.() -> Unit = {
BModalBottomSheetContent { Box(Modifier) }
}
@Composable
fun <C : Any> ModalBottomSheetSlot(
initialDetent: SheetDetent = SheetDetent.Companion.Hidden,
instance: C?,
onDismiss: () -> Unit,
content: @Composable ModalBottomSheetScope.(C) -> Unit
) {
val childContent = remember { mutableStateOf(emptyContent) }
val modalSheetState = rememberModalBottomSheetState(
initialDetent = initialDetent
)
LaunchedEffect(modalSheetState) {
snapshotFlow {
modalSheetState.targetDetent == SheetDetent.Companion.Hidden &&
modalSheetState.currentDetent == SheetDetent.Companion.Hidden &&
modalSheetState.isIdle
}
.distinctUntilChanged()
.drop(1)
.collect { isHidden ->
if (isHidden) {
onDismiss()
}
}
}
LaunchedEffect(instance) {
if (instance == null && modalSheetState.currentDetent == SheetDetent.Companion.FullyExpanded) {
modalSheetState.animateTo(SheetDetent.Companion.Hidden)
childContent.value = emptyContent
modalSheetState.jumpTo(SheetDetent.Companion.Hidden)
} else if (instance != null && modalSheetState.currentDetent == SheetDetent.Companion.Hidden) {
modalSheetState.animateTo(SheetDetent.Companion.FullyExpanded)
childContent.value = { content(instance) }
}
}
ModalBottomSheet(
state = modalSheetState,
content = {
childContent.value.invoke(this)
}
)
}
@Composable
fun <C : Any, T : Any> ModalBottomSheetSlot(
initialDetent: SheetDetent = SheetDetent.Companion.Hidden,
slot: ChildSlot<C, T>,
onDismiss: () -> Unit,
content: @Composable ModalBottomSheetScope.(T) -> Unit
) {
ModalBottomSheetSlot(
initialDetent = initialDetent,
instance = slot.child?.instance,
onDismiss = onDismiss,
content = content
)
}
@Composable
fun <C : Any, T : Any> ModalBottomSheetSlot(
initialDetent: SheetDetent = SheetDetent.Companion.Hidden,
slot: Value<ChildSlot<C, T>>,
onDismiss: () -> Unit,
content: @Composable ModalBottomSheetScope.(T) -> Unit
) {
ModalBottomSheetSlot(
initialDetent = initialDetent,
instance = slot.subscribeAsState().value.child?.instance,
onDismiss = onDismiss,
content = content
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment