Created
April 29, 2022 22:43
-
-
Save tadfisher/f10639b8dbf2cb96ea5cd448b5c48660 to your computer and use it in GitHub Desktop.
BottomSheetHost
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
object BottomSheetDefaults { | |
@Stable | |
val Elevation = 16.dp | |
@Stable | |
val OuterPadding: PaddingValues = PaddingValues(top = 48.dp) | |
@Stable | |
val ContentPadding: PaddingValues | |
@Composable | |
get() = WindowInsets.navigationBars.union(WindowInsets.ime) | |
.asPaddingValues() | |
.plus( | |
start = 16.dp, | |
top = 24.dp, | |
end = 16.dp, | |
bottom = 16.dp | |
) | |
@Stable | |
val Shape: Shape | |
@Composable | |
get() = MaterialTheme.shapes.large.copy( | |
bottomStart = ZeroCornerSize, | |
bottomEnd = ZeroCornerSize | |
) | |
} | |
interface BottomSheetScope : ColumnScope { | |
val bottomSheetState: ModalBottomSheetState | |
} | |
@Stable | |
internal class BottomSheetScopeImpl( | |
override val bottomSheetState: ModalBottomSheetState, | |
private val columnScope: ColumnScope | |
) : BottomSheetScope, ColumnScope by columnScope | |
@Stable | |
internal class BottomSheetData( | |
val state: ModalBottomSheetState, | |
shape: Shape, | |
elevation: Dp, | |
backgroundColor: Color, | |
contentColor: Color, | |
scrimColor: Color | |
) { | |
var shape: Shape by mutableStateOf(shape) | |
var elevation: Dp by mutableStateOf(elevation) | |
var backgroundColor: Color by mutableStateOf(backgroundColor) | |
var contentColor: Color by mutableStateOf(contentColor) | |
var scrimColor: Color by mutableStateOf(scrimColor) | |
var onDismiss: () -> Unit = {} | |
var content: @Composable BottomSheetScope.() -> Unit = {} | |
val isVisible by derivedStateOf { | |
state.currentValue != ModalBottomSheetValue.Hidden || | |
state.targetValue != ModalBottomSheetValue.Hidden | |
} | |
suspend fun dismiss() { | |
state.hide() | |
} | |
} | |
@Stable | |
class BottomSheetHostState{ | |
internal var currentBottomSheet by mutableStateOf<BottomSheetData?>(null) | |
private set | |
internal suspend fun show(bottomSheet: BottomSheetData) { | |
try { | |
hide() | |
} finally { | |
currentBottomSheet = bottomSheet | |
bottomSheet.state.show() | |
} | |
} | |
internal suspend fun hide() { | |
val bottomSheet = currentBottomSheet ?: return | |
try { | |
if (bottomSheet.isVisible) { | |
bottomSheet.state.hide() | |
} | |
} finally { | |
currentBottomSheet = null | |
} | |
} | |
} | |
val LocalBottomSheetHostState = staticCompositionLocalOf<BottomSheetHostState> { | |
noLocalProvidedFor("LocalBottomSheetHostState") | |
} | |
@Composable | |
fun BottomSheetHost( | |
state: BottomSheetHostState = remember { BottomSheetHostState() }, | |
modifier: Modifier = Modifier, | |
content: @Composable () -> Unit | |
) { | |
Box(modifier.fillMaxSize()) { | |
CompositionLocalProvider(LocalBottomSheetHostState provides state) { | |
content() | |
} | |
val bottomSheet = state.currentBottomSheet | |
if (bottomSheet != null) { | |
ModalBottomSheetLayout( | |
sheetContent = { | |
bottomSheet.content(BottomSheetScopeImpl(bottomSheet.state, this)) | |
}, | |
sheetState = bottomSheet.state, | |
sheetShape = bottomSheet.shape, | |
sheetElevation = bottomSheet.elevation, | |
sheetBackgroundColor = bottomSheet.backgroundColor, | |
sheetContentColor = bottomSheet.contentColor, | |
scrimColor = bottomSheet.scrimColor, | |
content = {} | |
) | |
var wasShown by remember { mutableStateOf(false) } | |
DisposableEffect(bottomSheet.isVisible) { | |
if (bottomSheet.isVisible) { | |
wasShown = true | |
} else if (wasShown) { | |
bottomSheet.onDismiss() | |
} | |
onDispose { } | |
} | |
} | |
} | |
} | |
@Composable | |
fun BottomSheet( | |
state: ModalBottomSheetState = rememberModalBottomSheetState( | |
initialValue = ModalBottomSheetValue.Hidden, | |
skipHalfExpanded = true, | |
), | |
onDismiss: () -> Unit = { }, | |
hostState: BottomSheetHostState = LocalBottomSheetHostState.current, | |
shape: Shape = MaterialTheme.shapes.large, | |
elevation: Dp = ModalBottomSheetDefaults.Elevation, | |
backgroundColor: Color = MaterialTheme.colors.surface, | |
contentColor: Color = MaterialTheme.colors.onSurface, | |
scrimColor: Color = ScrimDefaults.Color, | |
content: @Composable BottomSheetScope.() -> Unit | |
) { | |
val coroutineScope = rememberCoroutineScope() | |
val movableContent = remember(content as Any) { | |
movableContentWithReceiverOf(content) | |
} | |
val currentOnDismiss by rememberUpdatedState(onDismiss) | |
val bottomSheetData = remember { | |
BottomSheetData( | |
state = state, | |
shape = shape, | |
elevation = elevation, | |
backgroundColor = backgroundColor, | |
contentColor = contentColor, | |
scrimColor = scrimColor, | |
) | |
} | |
SideEffect { | |
bottomSheetData.shape = shape | |
bottomSheetData.elevation = elevation | |
bottomSheetData.backgroundColor = backgroundColor | |
bottomSheetData.contentColor = contentColor | |
bottomSheetData.scrimColor = scrimColor | |
bottomSheetData.onDismiss = currentOnDismiss | |
bottomSheetData.content = movableContent | |
} | |
BackHandler(enabled = bottomSheetData.isVisible) { | |
coroutineScope.launch { | |
state.hide() | |
} | |
} | |
LaunchedEffect(hostState, bottomSheetData) { | |
hostState.show(bottomSheetData) | |
try { | |
awaitCancellation() | |
} finally { | |
withContext(NonCancellable) { | |
hostState.hide() | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Composable | |
fun Scrim( | |
modifier: Modifier = Modifier, | |
visible: Boolean = true, | |
clickLabel: String? = null, | |
onClick: (() -> Unit)? = null, | |
color: Color = ScrimDefaults.Color, | |
animationSpec: FiniteAnimationSpec<Float> = tween() | |
) { | |
val clickModifier = if (visible && onClick != null) { | |
Modifier | |
.pointerInput(onClick) { | |
detectTapGestures { onClick() } | |
} | |
.semantics(mergeDescendants = true) { | |
onClick(clickLabel) { | |
onClick() | |
true | |
} | |
} | |
} else { | |
Modifier | |
} | |
val visibleState = remember { MutableTransitionState(false) } | |
visibleState.targetState = visible | |
val transition = updateTransition(visibleState, "ScrimVisibility") | |
val alpha by transition.animateFloat( | |
transitionSpec = { animationSpec }, | |
label = "ScrimAlpha" | |
) { isVisible -> if (isVisible) 1f else 0f } | |
Canvas( | |
modifier | |
.fillMaxSize() | |
.then(clickModifier) | |
) { | |
drawRect(color = color, alpha = alpha) | |
} | |
} | |
object ScrimDefaults { | |
val Color: Color | |
@Composable | |
get() { | |
val colors = MaterialTheme.colors | |
return if (colors.isLight) { | |
colors.onSurface.copy(alpha = ContentAlpha.medium) | |
} else { | |
colors.surface.copy(alpha = ContentAlpha.high) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment