Create a bottom sheet composable

This commit is contained in:
Suphon Thanakornpakapong
2021-05-18 21:18:37 +07:00
parent 1e60d5a9de
commit 685682a737
4 changed files with 149 additions and 19 deletions
@@ -31,9 +31,7 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import app.lawnchair.ui.preferences.about.aboutGraph
import app.lawnchair.ui.preferences.components.PreferenceCategoryList
import app.lawnchair.ui.preferences.components.SystemUi
import app.lawnchair.ui.preferences.components.TopBar
import app.lawnchair.ui.preferences.components.*
import app.lawnchair.util.Meta
import app.lawnchair.util.pageMeta
import app.lawnchair.util.preferences.getMajorVersion
@@ -121,24 +119,26 @@ fun Preferences(interactor: PreferenceInteractor = viewModel<PreferenceViewModel
val navController = rememberNavController()
SystemUi()
Surface(color = MaterialTheme.colors.background) {
CompositionLocalProvider(
LocalNavController provides navController,
LocalPreferenceInteractor provides interactor,
) {
NavHost(navController = navController, startDestination = "preferences") {
composable(route = Routes.PREFERENCES) {
pageMeta.provide(Meta(title = stringResource(id = R.string.settings)))
PreferenceCategoryList(navController)
ProvidePortalNode {
Surface(color = MaterialTheme.colors.background) {
CompositionLocalProvider(
LocalNavController provides navController,
LocalPreferenceInteractor provides interactor,
) {
NavHost(navController = navController, startDestination = "preferences") {
composable(route = Routes.PREFERENCES) {
pageMeta.provide(Meta(title = stringResource(id = R.string.settings)))
PreferenceCategoryList(navController)
}
generalGraph(route = Routes.GENERAL)
homeScreenGraph(route = Routes.HOME_SCREEN)
dockGraph(route = Routes.DOCK)
appDrawerGraph(route = Routes.APP_DRAWER)
folderGraph(route = Routes.FOLDERS)
aboutGraph(route = Routes.ABOUT)
}
generalGraph(route = Routes.GENERAL)
homeScreenGraph(route = Routes.HOME_SCREEN)
dockGraph(route = Routes.DOCK)
appDrawerGraph(route = Routes.APP_DRAWER)
folderGraph(route = Routes.FOLDERS)
aboutGraph(route = Routes.ABOUT)
TopBar()
}
TopBar()
}
}
}
@@ -0,0 +1,51 @@
package app.lawnchair.ui.preferences.components
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.ModalBottomSheetLayout
import androidx.compose.material.ModalBottomSheetValue
import androidx.compose.material.rememberModalBottomSheetState
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import app.lawnchair.util.backHandler
import kotlinx.coroutines.launch
@ExperimentalMaterialApi
@Composable
fun BottomSheet(
sheetContent: @Composable ColumnScope.() -> Unit,
content: @Composable (showSheet: suspend () -> Unit) -> Unit,
) {
val state = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
val currentSheetContent by rememberUpdatedState(sheetContent)
val addedToHost = rememberSaveable { mutableStateOf(false) }
val wasVisible = rememberSaveable { mutableStateOf(false) }
val scope = rememberCoroutineScope()
if (wasVisible.value && !state.isVisible) {
addedToHost.value = false
}
wasVisible.value = state.isVisible
if (addedToHost.value) {
Portal {
ModalBottomSheetLayout(
sheetState = state,
sheetContent = currentSheetContent
) {
backHandler {
scope.launch { state.hide() }
}
}
}
}
val showSheet = remember {
suspend {
addedToHost.value = true
state.show()
}
}
content(showSheet)
}
@@ -0,0 +1,52 @@
package app.lawnchair.ui.preferences.components
import android.view.View
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.*
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
@Composable
fun ProvidePortalNode(content: @Composable () -> Unit) {
val views = remember { mutableStateListOf<View>() }
Box {
CompositionLocalProvider(
LocalPortalNode provides remember { PortalNode(views) }
) {
content()
}
views.forEach { view ->
AndroidView(factory = { view })
}
}
}
@Composable
fun Portal(portalNode: PortalNode = LocalPortalNode.current, content: @Composable () -> Unit) {
val currentContent by rememberUpdatedState(content)
val compositionContext = rememberCompositionContext()
val context = LocalContext.current
DisposableEffect("portal") {
val view = ComposeView(context)
view.setParentCompositionContext(compositionContext)
view.setContent { currentContent() }
portalNode.addView(view)
onDispose { portalNode.removeView(view) }
}
}
class PortalNode(private val views: MutableList<View>) {
fun addView(view: View) {
views.add(view)
}
fun removeView(view: View) {
views.remove(view)
}
}
val LocalPortalNode = compositionLocalOf<PortalNode> {
error("CompositionLocal LocalPortalNode not present")
}
@@ -0,0 +1,27 @@
package app.lawnchair.util
import androidx.activity.OnBackPressedCallback
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.rememberUpdatedState
@Composable
fun backHandler(onBack: () -> Unit) {
val onBackPressedDispatcherOwner = LocalOnBackPressedDispatcherOwner.current
val onBackPressedDispatcher = onBackPressedDispatcherOwner?.onBackPressedDispatcher
val currentOnBack = rememberUpdatedState(onBack)
DisposableEffect(onBackPressedDispatcher) {
if (onBackPressedDispatcher == null) {
return@DisposableEffect onDispose { }
}
val onBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
currentOnBack.value()
}
}
onBackPressedDispatcher.addCallback(onBackPressedCallback)
onDispose { onBackPressedCallback.remove() }
}
}