From d7c993601419a62919b76d4c6b4c8ee9ca0e9811 Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Wed, 6 Sep 2023 22:39:01 +0200 Subject: [PATCH] Rewrite popups and bottom sheets to not use popup windows Popup windows are too unrealiable --- .../ui/component/BottomSheetDialog.kt | 15 +- .../ui/launcher/SharedLauncherActivity.kt | 3 +- .../ui/launcher/search/apps/AppItem.kt | 8 +- .../launcher/search/calendar/CalendarItem.kt | 3 +- .../launcher/search/common/grid/GridItem.kt | 296 ++++++++++-------- .../launcher/search/contacts/ContactItem.kt | 3 +- .../ui/launcher/search/files/FileItem.kt | 3 +- .../launcher/search/shortcut/ShortcutItem.kt | 3 +- .../ui/launcher/search/website/WebsiteItem.kt | 3 +- .../search/wikipedia/WikipediaItem.kt | 3 +- .../de/mm20/launcher2/ui/overlays/Overlay.kt | 18 ++ .../mm20/launcher2/ui/overlays/OverlayHost.kt | 39 +++ .../launcher2/ui/overlays/OverlayManager.kt | 26 ++ .../launcher2/ui/settings/SettingsActivity.kt | 246 ++++++++------- 14 files changed, 403 insertions(+), 266 deletions(-) create mode 100644 app/ui/src/main/java/de/mm20/launcher2/ui/overlays/Overlay.kt create mode 100644 app/ui/src/main/java/de/mm20/launcher2/ui/overlays/OverlayHost.kt create mode 100644 app/ui/src/main/java/de/mm20/launcher2/ui/overlays/OverlayManager.kt diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/component/BottomSheetDialog.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/component/BottomSheetDialog.kt index a75e43cd..932195ad 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/component/BottomSheetDialog.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/component/BottomSheetDialog.kt @@ -26,6 +26,7 @@ import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.shape.CornerSize @@ -65,6 +66,7 @@ import androidx.compose.ui.window.Popup import androidx.compose.ui.window.PopupPositionProvider import androidx.compose.ui.window.PopupProperties import de.mm20.launcher2.ui.ktx.toPixels +import de.mm20.launcher2.ui.overlays.Overlay import kotlinx.coroutines.launch import kotlin.math.min import kotlin.math.roundToInt @@ -159,20 +161,11 @@ fun BottomSheetDialog( val positionProvider = remember(insets, density) { BottomSheetPositionProvider(insets, density) } - Popup( - popupPositionProvider = positionProvider, - properties = PopupProperties( - dismissOnBackPress = dismissible(), - dismissOnClickOutside = dismissible(), - usePlatformDefaultWidth = false, - focusable = true, - clippingEnabled = false, - ), - onDismissRequest = onDismissRequest, - ) { + Overlay(zIndex = 9999f) { BoxWithConstraints( modifier = Modifier .fillMaxSize() + .systemBarsPadding() .imePadding(), propagateMinConstraints = true, contentAlignment = Alignment.BottomCenter diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/SharedLauncherActivity.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/SharedLauncherActivity.kt index f70f2a50..a387a916 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/SharedLauncherActivity.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/SharedLauncherActivity.kt @@ -55,6 +55,7 @@ import de.mm20.launcher2.ui.locals.LocalPreferDarkContentOverWallpaper import de.mm20.launcher2.ui.locals.LocalSnackbarHostState import de.mm20.launcher2.ui.locals.LocalWallpaperColors import de.mm20.launcher2.ui.locals.LocalWindowSize +import de.mm20.launcher2.ui.overlays.OverlayHost import de.mm20.launcher2.ui.theme.LauncherTheme import de.mm20.launcher2.ui.theme.wallpaperColorsAsState import kotlin.math.pow @@ -160,7 +161,7 @@ abstract class SharedLauncherActivity( systemUiController.isNavigationBarVisible = !hideNav } - Box( + OverlayHost( modifier = Modifier .fillMaxSize() .background(if (dimBackground) Color.Black.copy(alpha = 0.30f) else Color.Transparent), diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/apps/AppItem.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/apps/AppItem.kt index a4b42d9d..ced47078 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/apps/AppItem.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/apps/AppItem.kt @@ -4,6 +4,7 @@ import android.app.PendingIntent import android.content.Intent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateContentSize +import androidx.compose.animation.core.MutableTransitionState import androidx.compose.animation.core.tween import androidx.compose.animation.expandIn import androidx.compose.animation.shrinkOut @@ -58,6 +59,7 @@ import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.lerp import androidx.compose.ui.unit.roundToIntRect import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.lifecycleScope @@ -428,7 +430,7 @@ fun AppItem( @Composable fun AppItemGridPopup( app: LauncherApp, - show: Boolean, + show: MutableTransitionState, animationProgress: Float, origin: Rect, onDismiss: () -> Unit @@ -452,8 +454,8 @@ fun AppItemGridPopup( transformOrigin = TransformOrigin(1f, 0f) ) .offset( - x = 16.dp * (1 - animationProgress).pow(10), - y = -16.dp * (1 - animationProgress), + x = lerp(16.dp, 0.dp, animationProgress), + y = lerp(-16.dp, 0.dp, animationProgress) ), app = app, onBack = onDismiss diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/calendar/CalendarItem.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/calendar/CalendarItem.kt index 11b18f34..dc36031f 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/calendar/CalendarItem.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/calendar/CalendarItem.kt @@ -3,6 +3,7 @@ package de.mm20.launcher2.ui.launcher.search.calendar import android.content.Context import android.text.format.DateUtils import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.MutableTransitionState import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.tween import androidx.compose.animation.expandIn @@ -301,7 +302,7 @@ fun CalendarItem( @Composable fun CalendarItemGridPopup( calendar: CalendarEvent, - show: Boolean, + show: MutableTransitionState, animationProgress: Float, origin: Rect, onDismiss: () -> Unit diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/grid/GridItem.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/grid/GridItem.kt index 2ecec81e..32918bdb 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/grid/GridItem.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/grid/GridItem.kt @@ -1,24 +1,30 @@ package de.mm20.launcher2.ui.launcher.search.common.grid import android.content.ComponentName +import android.util.Log import androidx.activity.compose.BackHandler -import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.MutableTransitionState import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.absoluteOffset +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.ime +import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.foundation.layout.union import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -27,18 +33,18 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Rect import androidx.compose.ui.hapticfeedback.HapticFeedbackType +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.boundsInWindow +import androidx.compose.ui.layout.layout import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Popup -import androidx.compose.ui.window.PopupProperties import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel import de.mm20.launcher2.search.SavableSearchable import de.mm20.launcher2.search.Searchable import de.mm20.launcher2.search.data.AppShortcut @@ -51,7 +57,6 @@ import de.mm20.launcher2.search.data.Wikipedia import de.mm20.launcher2.ui.component.LauncherCard import de.mm20.launcher2.ui.component.LocalIconShape import de.mm20.launcher2.ui.component.ShapedLauncherIcon -import de.mm20.launcher2.ui.ktx.toDp import de.mm20.launcher2.ui.ktx.toPixels import de.mm20.launcher2.ui.launcher.search.apps.AppItemGridPopup import de.mm20.launcher2.ui.launcher.search.calendar.CalendarItemGridPopup @@ -65,9 +70,10 @@ import de.mm20.launcher2.ui.launcher.search.wikipedia.WikipediaItemGridPopup import de.mm20.launcher2.ui.launcher.transitions.EnterHomeTransitionParams import de.mm20.launcher2.ui.launcher.transitions.HandleEnterHomeTransition import de.mm20.launcher2.ui.locals.LocalGridSettings -import de.mm20.launcher2.ui.locals.LocalWindowPosition import de.mm20.launcher2.ui.locals.LocalWindowSize -import kotlinx.coroutines.delay +import de.mm20.launcher2.ui.overlays.Overlay +import kotlin.math.min +import kotlin.math.pow @Composable @@ -98,7 +104,8 @@ fun GridItem( onClick = { if (!launchOnPress || !viewModel.launch(context, bounds)) { showPopup = true - }}, + } + }, onLongClick = { hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) showPopup = true @@ -106,7 +113,8 @@ fun GridItem( indication = null, interactionSource = remember { MutableInteractionSource() }, ), - horizontalAlignment = Alignment.CenterHorizontally) { + horizontalAlignment = Alignment.CenterHorizontally + ) { val badge by viewModel.badge.collectAsStateWithLifecycle() val icon by viewModel.icon.collectAsStateWithLifecycle() @@ -181,136 +189,176 @@ fun GridItem( @Composable fun ItemPopup(origin: Rect, searchable: Searchable, onDismissRequest: () -> Unit) { - var show by remember { mutableStateOf(false) } - LaunchedEffect(null) { - show = true + val show = remember { + MutableTransitionState(false).apply { + targetState = true + } } - LaunchedEffect(show) { - if (!show) { - delay(300L) + val animationProgress = remember { Animatable(0f) } + LaunchedEffect(show.targetState) { + if (!show.targetState) { + animationProgress.animateTo(0f, tween(300)) onDismissRequest() + } else { + animationProgress.animateTo(1f, tween(300)) } } BackHandler { - show = false + show.targetState = false } - val animationProgress by animateFloatAsState(if (show) 1f else 0f, tween(300)) - Popup( - properties = PopupProperties( - usePlatformDefaultWidth = false, - dismissOnBackPress = true - ), - alignment = Alignment.TopCenter, - onDismissRequest = { - show = false - }, - offset = IntOffset(-origin.left.toInt(), 0) - ) { - CompositionLocalProvider(LocalWindowPosition provides origin.top) { - Box( + Overlay(zIndex = 1f) { + Box( + modifier = Modifier + .background(MaterialTheme.colorScheme.scrim.copy(alpha = 0.32f * animationProgress.value)) + .fillMaxSize() + .systemBarsPadding() + .imePadding() + .padding(horizontal = 16.dp) + .pointerInput(Unit) { + detectTapGestures(onPress = { + show.targetState = false + }) + }, + ) { + LauncherCard( + elevation = 8.dp * animationProgress.value, + backgroundOpacity = 1f, modifier = Modifier - .fillMaxWidth() + .placeOverlay( + origin.translate( + -16.dp.toPixels(), + -WindowInsets.systemBars.union(WindowInsets.ime).getTop(LocalDensity.current).toFloat() + ), + animationProgress.value + ) ) { - LauncherCard( - elevation = 8.dp * animationProgress, - backgroundOpacity = 1f, - modifier = Modifier - .padding(horizontal = 16.dp) - .absoluteOffset( - x = ((1 - animationProgress) * origin.left).toDp() - 20.dp * (1 - animationProgress), + when (searchable) { + is LauncherApp -> { + AppItemGridPopup( + app = searchable, + show = show, + animationProgress = animationProgress.value, + origin = origin, + onDismiss = { + show.targetState = false + } ) - .wrapContentSize() - .padding(4.dp) - ) { - when (searchable) { - is LauncherApp -> { - AppItemGridPopup( - app = searchable, - show = show, - animationProgress = animationProgress, - origin = origin, - onDismiss = { - show = false - } - ) - } + } - is Website -> { - WebsiteItemGridPopup( - website = searchable, - show = show, - animationProgress = animationProgress, - origin = origin, - onDismiss = { - show = false - } - ) - } + is Website -> { + WebsiteItemGridPopup( + website = searchable, + show = show, + animationProgress = animationProgress.value, + origin = origin, + onDismiss = { + show.targetState = false + } + ) + } - is Wikipedia -> { - WikipediaItemGridPopup( - wikipedia = searchable, - show = show, - animationProgress = animationProgress, - origin = origin, - onDismiss = { - show = false - } - ) - } + is Wikipedia -> { + WikipediaItemGridPopup( + wikipedia = searchable, + show = show, + animationProgress = animationProgress.value, + origin = origin, + onDismiss = { + show.targetState = false + } + ) + } - is Contact -> { - ContactItemGridPopup( - contact = searchable, - show = show, - animationProgress = animationProgress, - origin = origin, - onDismiss = { - show = false - } - ) - } + is Contact -> { + ContactItemGridPopup( + contact = searchable, + show = show, + animationProgress = animationProgress.value, + origin = origin, + onDismiss = { + show.targetState = false + } + ) + } - is File -> { - FileItemGridPopup( - file = searchable, - show = show, - animationProgress = animationProgress, - origin = origin, - onDismiss = { - show = false - } - ) - } + is File -> { + FileItemGridPopup( + file = searchable, + show = show, + animationProgress = animationProgress.value, + origin = origin, + onDismiss = { + show.targetState = false + } + ) + } - is CalendarEvent -> { - CalendarItemGridPopup( - calendar = searchable, - show = show, - animationProgress = animationProgress, - origin = origin, - onDismiss = { - show = false - } - ) - } + is CalendarEvent -> { + CalendarItemGridPopup( + calendar = searchable, + show = show, + animationProgress = animationProgress.value, + origin = origin, + onDismiss = { + show.targetState = false + } + ) + } - is AppShortcut -> { - ShortcutItemGridPopup( - shortcut = searchable, - show = show, - animationProgress = animationProgress, - origin = origin, - onDismiss = { - show = false - } - ) - } + is AppShortcut -> { + ShortcutItemGridPopup( + shortcut = searchable, + show = show, + animationProgress = animationProgress.value, + origin = origin, + onDismiss = { + show.targetState = false + } + ) } } } } } +} + +private fun Modifier.placeOverlay( + origin: Rect, + animationProgress: Float, +): Modifier { + return layout { measurable, constraints -> + val placeable = measurable.measure(constraints) + Log.d( + "MM20", + "Layout: maxWidth: ${constraints.maxWidth}, origin: $origin, placeable: ${placeable.width}" + ) + layout(constraints.maxWidth, constraints.maxHeight) { + placeable.placeRelative( + ( + lerp( + origin.center.x, + constraints.maxWidth / 2f, + ((placeable.width.toFloat() - origin.width) / (constraints.maxWidth.toFloat() - origin.width)) + ) - placeable.width / 2f).toInt(), + lerp( + origin.top, + (origin.center.y - placeable.height / 2f).coerceIn( + 0f, + constraints.maxHeight.toFloat() - placeable.height.toFloat(), + ), + animationProgress.pow(2f) + ).toInt() + ) + } + } +} + +private fun lerp(start: Float, stop: Float, fraction: Float): Float { + return start + fraction * (stop - start) +} + +private fun lerp(start: Int, stop: Int, fraction: Float): Int { + return start + (fraction * (stop - start)).toInt() } \ No newline at end of file diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/contacts/ContactItem.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/contacts/ContactItem.kt index 9826a5f7..40188f64 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/contacts/ContactItem.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/contacts/ContactItem.kt @@ -3,6 +3,7 @@ package de.mm20.launcher2.ui.launcher.search.contacts import android.content.Intent import android.net.Uri import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.MutableTransitionState import androidx.compose.animation.core.animateDp import androidx.compose.animation.core.tween import androidx.compose.animation.core.updateTransition @@ -402,7 +403,7 @@ fun ContactItem( @Composable fun ContactItemGridPopup( contact: Contact, - show: Boolean, + show: MutableTransitionState, animationProgress: Float, origin: Rect, onDismiss: () -> Unit diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/files/FileItem.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/files/FileItem.kt index 2dd1e6ad..fbd62f02 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/files/FileItem.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/files/FileItem.kt @@ -1,6 +1,7 @@ package de.mm20.launcher2.ui.launcher.search.files import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.MutableTransitionState import androidx.compose.animation.core.animateDp import androidx.compose.animation.core.tween import androidx.compose.animation.core.updateTransition @@ -322,7 +323,7 @@ fun FileItem( @Composable fun FileItemGridPopup( file: File, - show: Boolean, + show: MutableTransitionState, animationProgress: Float, origin: Rect, onDismiss: () -> Unit diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/shortcut/ShortcutItem.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/shortcut/ShortcutItem.kt index 80df9898..84b2e688 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/shortcut/ShortcutItem.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/shortcut/ShortcutItem.kt @@ -5,6 +5,7 @@ import android.content.Intent import android.net.Uri import android.provider.Settings import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.MutableTransitionState import androidx.compose.animation.core.animateDp import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.tween @@ -284,7 +285,7 @@ fun AppShortcutItem( @Composable fun ShortcutItemGridPopup( shortcut: AppShortcut, - show: Boolean, + show: MutableTransitionState, animationProgress: Float, origin: Rect, onDismiss: () -> Unit diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/website/WebsiteItem.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/website/WebsiteItem.kt index eb301fca..ae6ceab5 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/website/WebsiteItem.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/website/WebsiteItem.kt @@ -1,6 +1,7 @@ package de.mm20.launcher2.ui.launcher.search.website import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.MutableTransitionState import androidx.compose.animation.core.tween import androidx.compose.animation.expandIn import androidx.compose.animation.shrinkOut @@ -154,7 +155,7 @@ fun WebsiteItem( @Composable fun WebsiteItemGridPopup( website: Website, - show: Boolean, + show: MutableTransitionState, animationProgress: Float, origin: Rect, onDismiss: () -> Unit diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/wikipedia/WikipediaItem.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/wikipedia/WikipediaItem.kt index 4ae64470..a6a03661 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/wikipedia/WikipediaItem.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/wikipedia/WikipediaItem.kt @@ -1,6 +1,7 @@ package de.mm20.launcher2.ui.launcher.search.wikipedia import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.MutableTransitionState import androidx.compose.animation.core.tween import androidx.compose.animation.expandIn import androidx.compose.animation.shrinkOut @@ -161,7 +162,7 @@ fun WikipediaItem( @Composable fun WikipediaItemGridPopup( wikipedia: Wikipedia, - show: Boolean, + show: MutableTransitionState, animationProgress: Float, origin: Rect, onDismiss: () -> Unit diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/overlays/Overlay.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/overlays/Overlay.kt new file mode 100644 index 00000000..edf2e7a3 --- /dev/null +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/overlays/Overlay.kt @@ -0,0 +1,18 @@ +package de.mm20.launcher2.ui.overlays + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect + +@Composable +fun Overlay( + zIndex: Float = 0f, + overlay: @Composable () -> Unit +) { + val overlayManager = LocalOverlayManager.current + DisposableEffect(Unit) { + overlayManager.addOverlay(overlay, zIndex) + onDispose { + overlayManager.removeOverlay(overlay) + } + } +} \ No newline at end of file diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/overlays/OverlayHost.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/overlays/OverlayHost.kt new file mode 100644 index 00000000..e85bbe4c --- /dev/null +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/overlays/OverlayHost.kt @@ -0,0 +1,39 @@ +package de.mm20.launcher2.ui.overlays + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.compositionLocalOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.zIndex + +@Composable +fun OverlayHost( + modifier: Modifier = Modifier, + contentAlignment: Alignment = Alignment.TopStart, + content: @Composable BoxScope.() -> Unit, +) { + val overlayManager = remember { OverlayManager() } + CompositionLocalProvider(LocalOverlayManager provides overlayManager) { + Box { + Box( + contentAlignment = contentAlignment, + modifier = modifier.zIndex(0f), + ) { + content() + } + for (overlay in overlayManager.overlays) { + Box(modifier = Modifier.zIndex(overlay.zIndex + 1f)) { + overlay() + } + } + } + } +} + +val LocalOverlayManager = compositionLocalOf { + OverlayManager() +} \ No newline at end of file diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/overlays/OverlayManager.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/overlays/OverlayManager.kt new file mode 100644 index 00000000..2da6191c --- /dev/null +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/overlays/OverlayManager.kt @@ -0,0 +1,26 @@ +package de.mm20.launcher2.ui.overlays + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateListOf + +class OverlayManager { + val overlays = mutableStateListOf() + + fun addOverlay(overlay: @Composable () -> Unit, zIndex: Float = 0f) { + overlays.add(Overlay(overlay, zIndex)) + } + + fun removeOverlay(overlay: @Composable () -> Unit) { + overlays.removeAll { overlay == it.overlay } + } +} + +data class Overlay( + val overlay: @Composable () -> Unit, + val zIndex: Float = 0f, +) { + @Composable + operator fun invoke() { + overlay() + } +} \ No newline at end of file diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/SettingsActivity.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/SettingsActivity.kt index 192bec45..ff5ada3f 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/SettingsActivity.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/SettingsActivity.kt @@ -21,6 +21,7 @@ import de.mm20.launcher2.ui.base.BaseActivity import de.mm20.launcher2.ui.base.ProvideSettings import de.mm20.launcher2.ui.locals.LocalNavController import de.mm20.launcher2.ui.locals.LocalWallpaperColors +import de.mm20.launcher2.ui.overlays.OverlayHost import de.mm20.launcher2.ui.settings.about.AboutSettingsScreen import de.mm20.launcher2.ui.settings.appearance.AppearanceSettingsScreen import de.mm20.launcher2.ui.settings.backup.BackupSettingsScreen @@ -78,129 +79,132 @@ class SettingsActivity : BaseActivity() { ) { ProvideSettings { LauncherTheme { - AnimatedNavHost( - navController = navController, - startDestination = "settings", - exitTransition = { fadeOut(tween(300, 300)) }, - enterTransition = { fadeIn(tween(200)) }, - popEnterTransition = { fadeIn(tween(0)) }, - popExitTransition = { fadeOut(tween(200)) }, - ) { - composable("settings") { - MainSettingsScreen() - } - composable("settings/appearance") { - AppearanceSettingsScreen() - } - composable("settings/homescreen") { - HomescreenSettingsScreen() - } - composable("settings/icons") { - IconsSettingsScreen() - } - composable("settings/appearance/themes") { - ThemesSettingsScreen() - } - composable( - "settings/appearance/themes/{id}", - arguments = listOf(navArgument("id") { - nullable = false - })) { - val id = it.arguments?.getString("id")?.let { - UUID.fromString(it) - } ?: return@composable - ThemeSettingsScreen(id) - } - composable("settings/appearance/cards") { - CardsSettingsScreen() - } - composable("settings/search") { - SearchSettingsScreen() - } - composable("settings/gestures") { - GestureSettingsScreen() - } - composable("settings/search/unitconverter") { - UnitConverterSettingsScreen() - } - composable("settings/search/wikipedia") { - WikipediaSettingsScreen() - } - composable("settings/search/files") { - FileSearchSettingsScreen() - } - composable("settings/search/searchactions") { - SearchActionsSettingsScreen() - } - composable("settings/search/hiddenitems") { - HiddenItemsSettingsScreen() - } - composable("settings/search/tags") { - TagsSettingsScreen() - } - composable(ROUTE_WEATHER_INTEGRATION) { - WeatherIntegrationSettingsScreen() - } - composable(ROUTE_MEDIA_INTEGRATION) { - MediaIntegrationSettingsScreen() - } - composable("settings/homescreen/clock") { - ClockWidgetSettingsScreen() - } - composable("settings/favorites") { - FavoritesSettingsScreen() - } - composable("settings/integrations") { - IntegrationsSettingsScreen() - } - composable("settings/about") { - AboutSettingsScreen() - } - composable("settings/about/buildinfo") { - BuildInfoSettingsScreen() - } - composable("settings/about/easteregg") { - EasterEggSettingsScreen() - } - composable("settings/debug") { - DebugSettingsScreen() - } - composable("settings/backup") { - BackupSettingsScreen() - } - composable("settings/debug/crashreporter") { - CrashReporterScreen() - } - composable("settings/debug/logs") { - LogScreen() - } - composable( - "settings/debug/crashreporter/report?fileName={fileName}", - arguments = listOf(navArgument("fileName") { - nullable = false - }) + OverlayHost { + AnimatedNavHost( + navController = navController, + startDestination = "settings", + exitTransition = { fadeOut(tween(300, 300)) }, + enterTransition = { fadeIn(tween(200)) }, + popEnterTransition = { fadeIn(tween(0)) }, + popExitTransition = { fadeOut(tween(200)) }, ) { - val fileName = it.arguments?.getString("fileName") - ?.let { - URLDecoder.decode(it, "utf8") - } - CrashReportScreen(fileName!!) - } - composable( - "settings/license?library={libraryName}", - arguments = listOf(navArgument("libraryName") { - nullable = true - }) - ) { - val libraryName = it.arguments?.getString("libraryName") - val library = remember(libraryName) { - if (libraryName == null) { - AppLicense.get(this@SettingsActivity) - } else { - OpenSourceLicenses.first { it.name == libraryName } - } + composable("settings") { + MainSettingsScreen() + } + composable("settings/appearance") { + AppearanceSettingsScreen() + } + composable("settings/homescreen") { + HomescreenSettingsScreen() + } + composable("settings/icons") { + IconsSettingsScreen() + } + composable("settings/appearance/themes") { + ThemesSettingsScreen() + } + composable( + "settings/appearance/themes/{id}", + arguments = listOf(navArgument("id") { + nullable = false + }) + ) { + val id = it.arguments?.getString("id")?.let { + UUID.fromString(it) + } ?: return@composable + ThemeSettingsScreen(id) + } + composable("settings/appearance/cards") { + CardsSettingsScreen() + } + composable("settings/search") { + SearchSettingsScreen() + } + composable("settings/gestures") { + GestureSettingsScreen() + } + composable("settings/search/unitconverter") { + UnitConverterSettingsScreen() + } + composable("settings/search/wikipedia") { + WikipediaSettingsScreen() + } + composable("settings/search/files") { + FileSearchSettingsScreen() + } + composable("settings/search/searchactions") { + SearchActionsSettingsScreen() + } + composable("settings/search/hiddenitems") { + HiddenItemsSettingsScreen() + } + composable("settings/search/tags") { + TagsSettingsScreen() + } + composable(ROUTE_WEATHER_INTEGRATION) { + WeatherIntegrationSettingsScreen() + } + composable(ROUTE_MEDIA_INTEGRATION) { + MediaIntegrationSettingsScreen() + } + composable("settings/homescreen/clock") { + ClockWidgetSettingsScreen() + } + composable("settings/favorites") { + FavoritesSettingsScreen() + } + composable("settings/integrations") { + IntegrationsSettingsScreen() + } + composable("settings/about") { + AboutSettingsScreen() + } + composable("settings/about/buildinfo") { + BuildInfoSettingsScreen() + } + composable("settings/about/easteregg") { + EasterEggSettingsScreen() + } + composable("settings/debug") { + DebugSettingsScreen() + } + composable("settings/backup") { + BackupSettingsScreen() + } + composable("settings/debug/crashreporter") { + CrashReporterScreen() + } + composable("settings/debug/logs") { + LogScreen() + } + composable( + "settings/debug/crashreporter/report?fileName={fileName}", + arguments = listOf(navArgument("fileName") { + nullable = false + }) + ) { + val fileName = it.arguments?.getString("fileName") + ?.let { + URLDecoder.decode(it, "utf8") + } + CrashReportScreen(fileName!!) + } + composable( + "settings/license?library={libraryName}", + arguments = listOf(navArgument("libraryName") { + nullable = true + }) + ) { + val libraryName = it.arguments?.getString("libraryName") + val library = remember(libraryName) { + if (libraryName == null) { + AppLicense.get(this@SettingsActivity) + } else { + OpenSourceLicenses.first { it.name == libraryName } + } + } + LicenseScreen(library) } - LicenseScreen(library) } } }