diff --git a/lawnchair/src/app/lawnchair/ui/preferences/Preferences.kt b/lawnchair/src/app/lawnchair/ui/preferences/Preferences.kt index 80e0e81289..f079be66e1 100644 --- a/lawnchair/src/app/lawnchair/ui/preferences/Preferences.kt +++ b/lawnchair/src/app/lawnchair/ui/preferences/Preferences.kt @@ -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 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) +} diff --git a/lawnchair/src/app/lawnchair/ui/preferences/components/Portal.kt b/lawnchair/src/app/lawnchair/ui/preferences/components/Portal.kt new file mode 100644 index 0000000000..1bd8193460 --- /dev/null +++ b/lawnchair/src/app/lawnchair/ui/preferences/components/Portal.kt @@ -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() } + 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) { + fun addView(view: View) { + views.add(view) + } + + fun removeView(view: View) { + views.remove(view) + } +} + +val LocalPortalNode = compositionLocalOf { + error("CompositionLocal LocalPortalNode not present") +} diff --git a/lawnchair/src/app/lawnchair/util/backHandler.kt b/lawnchair/src/app/lawnchair/util/backHandler.kt new file mode 100644 index 0000000000..a891b9c253 --- /dev/null +++ b/lawnchair/src/app/lawnchair/util/backHandler.kt @@ -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() } + } +}