-
-
Save racka98/22ea7a16b2570ca9353d2ffe5586fd39 to your computer and use it in GitHub Desktop.
Put this in its own Gradle module to have ModalBottomSheet in Material3 (or even without Material Design at all).
This file contains 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
// Uses refreshVersions, the easiest way to add KotlinX and AndroidX libs, AND keep them up to date. | |
// See https://github.com/jmfayard/refreshVersions | |
import de.fayard.refreshVersions.core.versionFor | |
plugins { | |
kotlin("android") | |
id("com.android.library") | |
} | |
android { | |
namespace = "com.louiscad.splitties.ui.bottomsheet" | |
buildFeatures.compose = true | |
composeOptions { | |
kotlinCompilerExtensionVersion = versionFor(AndroidX.compose.compiler) | |
} | |
//region Boilerplate (put it in a precompiled script if possible) | |
compileSdk = 33 | |
defaultConfig { | |
minSdk = 23 | |
} | |
buildTypes { | |
getByName("release").isMinifyEnabled = false | |
} | |
compileOptions { | |
sourceCompatibility = JavaVersion.VERSION_1_8 | |
targetCompatibility = JavaVersion.VERSION_1_8 | |
} | |
//endregion | |
} | |
dependencies { | |
api(AndroidX.compose.runtime) | |
api(AndroidX.compose.ui) | |
implementation(AndroidX.compose.material) | |
implementation(AndroidX.activity.compose) | |
} |
This file contains 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
package com.louiscad.splitties.eap.bottomsheet | |
import android.app.Activity | |
import android.view.ViewGroup | |
import androidx.activity.compose.BackHandler | |
import androidx.compose.material.ExperimentalMaterialApi | |
import androidx.compose.material.ModalBottomSheetLayout | |
import androidx.compose.runtime.Composable | |
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.rememberCoroutineScope | |
import androidx.compose.runtime.setValue | |
import androidx.compose.ui.graphics.Color | |
import androidx.compose.ui.platform.ComposeView | |
import androidx.compose.ui.platform.LocalContext | |
import kotlinx.coroutines.* | |
@OptIn(ExperimentalMaterialApi::class) | |
@Composable | |
fun ModalBottomSheet( | |
modalState: ModalState = rememberModalState().also { | |
rememberCoroutineScope().launch { it.show() } | |
}, | |
content: @Composable () -> Unit | |
) { | |
val context = LocalContext.current | |
val contentView: ViewGroup = remember { | |
val activity = context as Activity | |
activity.findViewById(android.R.id.content) as ViewGroup | |
} | |
val coroutineScope = rememberCoroutineScope() | |
val compositionContext = rememberCompositionContext() | |
val internalState: ModalStateImpl | |
val state = when (modalState) { | |
is ModalStateImpl -> { | |
internalState = modalState | |
modalState._state | |
} | |
} | |
val shouldBeVisible = | |
modalState.isAtLeastPartiallyVisible || internalState.hasPendingShowRequests | |
var lastComposeView: ComposeView? by remember { mutableStateOf(null) } | |
val composeView = remember(shouldBeVisible, compositionContext) { | |
lastComposeView?.let { contentView.removeView(it) } | |
val newView: ComposeView? = when { | |
shouldBeVisible -> ComposeView(contentView.context).also { | |
it.setParentCompositionContext(compositionContext) | |
contentView.addView(it) | |
} | |
else -> { | |
null | |
} | |
} | |
lastComposeView = newView | |
newView | |
} | |
DisposableEffect(composeView, content as Any) { | |
composeView?.setContent { | |
ModalBottomSheetLayout( | |
sheetBackgroundColor = Color.Transparent, | |
sheetState = state, | |
sheetContent = { | |
content() | |
} | |
) {} | |
} | |
onDispose {} | |
} | |
DisposableEffect(Unit) { | |
onDispose { composeView?.let { contentView.removeView(it) } } | |
} | |
BackHandler(enabled = state.isShownOrShowing) { | |
coroutineScope.launch { modalState.hide() } | |
} | |
} |
This file contains 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
package com.louiscad.splitties.eap.bottomsheet | |
import androidx.compose.material.ExperimentalMaterialApi | |
import androidx.compose.material.ModalBottomSheetState | |
import androidx.compose.material.ModalBottomSheetValue | |
@ExperimentalMaterialApi | |
val ModalBottomSheetState.isAtLeastPartiallyVisible: Boolean | |
get() = progress.from != ModalBottomSheetValue.Hidden || | |
progress.to != ModalBottomSheetValue.Hidden | |
@ExperimentalMaterialApi | |
val ModalBottomSheetState.isShownOrShowing: Boolean | |
get() = targetValue != ModalBottomSheetValue.Hidden | |
@ExperimentalMaterialApi | |
val ModalBottomSheetState.isHiddenOrHiding: Boolean | |
get() = targetValue == ModalBottomSheetValue.Hidden |
This file contains 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
package com.louiscad.splitties.eap.bottomsheet | |
import androidx.compose.material.ExperimentalMaterialApi | |
import androidx.compose.material.ModalBottomSheetState | |
import androidx.compose.material.ModalBottomSheetValue | |
import androidx.compose.material.rememberModalBottomSheetState | |
import androidx.compose.runtime.Composable | |
import androidx.compose.runtime.Stable | |
import androidx.compose.runtime.derivedStateOf | |
import androidx.compose.runtime.getValue | |
import androidx.compose.runtime.mutableStateOf | |
import androidx.compose.runtime.remember | |
import androidx.compose.runtime.setValue | |
@OptIn(ExperimentalMaterialApi::class) | |
@Composable | |
fun rememberModalState(): ModalState { | |
val bottomSheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden) | |
return remember(bottomSheetState) { ModalStateImpl(bottomSheetState) } | |
} | |
@Stable | |
sealed interface ModalState { | |
val isVisible: Boolean | |
val isAtLeastPartiallyVisible: Boolean | |
val isShownOrShowing: Boolean | |
val isHiddenOrHiding: Boolean | |
suspend fun hide() | |
suspend fun show() | |
} | |
@Stable | |
@ExperimentalMaterialApi | |
internal class ModalStateImpl( | |
val _state: ModalBottomSheetState | |
) : ModalState { | |
override val isVisible: Boolean get() = _state.isVisible | |
override val isAtLeastPartiallyVisible: Boolean get() = _state.isAtLeastPartiallyVisible | |
override val isShownOrShowing: Boolean get() = _state.isShownOrShowing | |
override val isHiddenOrHiding: Boolean get() = _state.isHiddenOrHiding | |
override suspend fun hide() = _state.hide() | |
override suspend fun show() { | |
try { | |
showRequestsPendingCount++ | |
_state.show() | |
} finally { | |
showRequestsPendingCount-- | |
} | |
} | |
override fun toString(): String = buildString { | |
appendLine("isVisible: " + _state.isVisible.toString()) | |
appendLine("currentValue: " + _state.currentValue.toString()) | |
appendLine("targetValue: " + _state.targetValue.toString()) | |
appendLine("isAnimationRunning: " + _state.isAnimationRunning.toString()) | |
} | |
var showRequestsPendingCount by mutableStateOf(0) | |
val hasPendingShowRequests by derivedStateOf { showRequestsPendingCount > 0 } | |
} |
This file contains 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
package com.louiscad.splitties.eap.bottomsheet.example | |
@Composable | |
fun BottomSheetExample() { | |
val modalState = rememberModalState() | |
val coroutineScope = rememberCoroutineScope() | |
Button( | |
onClick = { coroutineScope.launch { modalState.show() } } | |
) { | |
Text("Open the bottom sheet!") | |
} | |
ModalBottomSheet(modalState = modalState) { | |
Surface { | |
Text("Your content here") | |
Button( | |
onClick = { coroutineScope.launch { modalState.hide() } | |
) { Text("Dismiss it!") } | |
Spacer(Modifier.navigationBarsPadding()) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment