Skip to content

Instantly share code, notes, and snippets.

@ArthurNagy
Created November 28, 2022 12:41
Show Gist options
  • Save ArthurNagy/8099e8e04db5f786df28957f7ee653f8 to your computer and use it in GitHub Desktop.
Save ArthurNagy/8099e8e04db5f786df28957f7ee653f8 to your computer and use it in GitHub Desktop.
Compose modal bottom sheet
import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.view.View
import androidx.appcompat.view.ContextThemeWrapper
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionContext
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCompositionContext
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.platform.AbstractComposeView
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalView
import androidx.lifecycle.ViewTreeLifecycleOwner
import androidx.lifecycle.ViewTreeViewModelStoreOwner
import androidx.savedstate.findViewTreeSavedStateRegistryOwner
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
@Composable
fun BottomSheetDialog(
onDismissRequest: () -> Unit,
content: @Composable () -> Unit
) {
val view = LocalView.current
val density = LocalDensity.current
val composition = rememberCompositionContext()
val currentContent by rememberUpdatedState(content)
val bottomSheetDialog = remember(view, density) {
BottomSheetDialogWrapper(
onDismissRequest = onDismissRequest,
composeView = view
).apply {
setContent(composition) {
BottomSheetDialogLayout {
currentContent()
}
}
}
}
DisposableEffect(bottomSheetDialog) {
bottomSheetDialog.show()
onDispose {
bottomSheetDialog.dismiss()
bottomSheetDialog.disposeComposition()
}
}
}
private class BottomSheetDialogLayout @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : AbstractComposeView(context, attrs, defStyleAttr) {
private var content: @Composable () -> Unit by mutableStateOf({})
fun setContent(parent: CompositionContext, content: @Composable () -> Unit) {
setParentCompositionContext(parent = parent)
this.content = content
createComposition()
}
@Composable
override fun Content() {
content()
}
}
private class BottomSheetDialogWrapper(
composeView: View,
private val onDismissRequest: () -> Unit,
) : BottomSheetDialog(ContextThemeWrapper(composeView.context, R.style.BottomSheetDialogTheme)) {
private val bottomSheetDialogLayout: BottomSheetDialogLayout = BottomSheetDialogLayout(context)
init {
setContentView(bottomSheetDialogLayout)
setCancelable(true)
ViewTreeLifecycleOwner.set(bottomSheetDialogLayout, ViewTreeLifecycleOwner.get(composeView))
ViewTreeViewModelStoreOwner.set(bottomSheetDialogLayout, ViewTreeViewModelStoreOwner.get(composeView))
bottomSheetDialogLayout.setViewTreeSavedStateRegistryOwner(composeView.findViewTreeSavedStateRegistryOwner())
setOnCancelListener {
onDismissRequest()
}
behavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
override fun onStateChanged(bottomSheet: View, newState: Int) {
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
onDismissRequest()
}
}
override fun onSlide(bottomSheet: View, slideOffset: Float) = Unit
})
}
fun setContent(parentComposition: CompositionContext, children: @Composable () -> Unit) {
bottomSheetDialogLayout.setContent(parentComposition, children)
}
fun disposeComposition() {
bottomSheetDialogLayout.disposeComposition()
}
override fun onBackPressed() {
super.onBackPressed()
onDismissRequest()
}
}
@Composable
private fun BottomSheetDialogLayout(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Layout(
content = content,
modifier = modifier
) { measurables, constraints ->
val placeables = measurables.map { it.measure(constraints) }
val width = placeables.maxByOrNull { it.width }?.width ?: constraints.minWidth
val height = placeables.maxByOrNull { it.height }?.height ?: constraints.minHeight
layout(width, height) {
placeables.forEach { it.placeRelative(0, 0) }
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="BottomSheetDialogTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="bottomSheetDialogTheme">@style/ThemeOverlay.MaterialComponents.BottomSheetDialog</item>
</style>
</resources>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment