Rewrite popups and bottom sheets to not use popup windows

Popup windows are too unrealiable
This commit is contained in:
MM20 2023-09-06 22:39:01 +02:00
parent cbcd9cbfc7
commit d7c9936014
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
14 changed files with 403 additions and 266 deletions

View File

@ -26,6 +26,7 @@ import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.CornerSize 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.PopupPositionProvider
import androidx.compose.ui.window.PopupProperties import androidx.compose.ui.window.PopupProperties
import de.mm20.launcher2.ui.ktx.toPixels import de.mm20.launcher2.ui.ktx.toPixels
import de.mm20.launcher2.ui.overlays.Overlay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.math.min import kotlin.math.min
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -159,20 +161,11 @@ fun BottomSheetDialog(
val positionProvider = remember(insets, density) { val positionProvider = remember(insets, density) {
BottomSheetPositionProvider(insets, density) BottomSheetPositionProvider(insets, density)
} }
Popup( Overlay(zIndex = 9999f) {
popupPositionProvider = positionProvider,
properties = PopupProperties(
dismissOnBackPress = dismissible(),
dismissOnClickOutside = dismissible(),
usePlatformDefaultWidth = false,
focusable = true,
clippingEnabled = false,
),
onDismissRequest = onDismissRequest,
) {
BoxWithConstraints( BoxWithConstraints(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.systemBarsPadding()
.imePadding(), .imePadding(),
propagateMinConstraints = true, propagateMinConstraints = true,
contentAlignment = Alignment.BottomCenter contentAlignment = Alignment.BottomCenter

View File

@ -55,6 +55,7 @@ import de.mm20.launcher2.ui.locals.LocalPreferDarkContentOverWallpaper
import de.mm20.launcher2.ui.locals.LocalSnackbarHostState import de.mm20.launcher2.ui.locals.LocalSnackbarHostState
import de.mm20.launcher2.ui.locals.LocalWallpaperColors import de.mm20.launcher2.ui.locals.LocalWallpaperColors
import de.mm20.launcher2.ui.locals.LocalWindowSize 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.LauncherTheme
import de.mm20.launcher2.ui.theme.wallpaperColorsAsState import de.mm20.launcher2.ui.theme.wallpaperColorsAsState
import kotlin.math.pow import kotlin.math.pow
@ -160,7 +161,7 @@ abstract class SharedLauncherActivity(
systemUiController.isNavigationBarVisible = !hideNav systemUiController.isNavigationBarVisible = !hideNav
} }
Box( OverlayHost(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.background(if (dimBackground) Color.Black.copy(alpha = 0.30f) else Color.Transparent), .background(if (dimBackground) Color.Black.copy(alpha = 0.30f) else Color.Transparent),

View File

@ -4,6 +4,7 @@ import android.app.PendingIntent
import android.content.Intent import android.content.Intent
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.expandIn import androidx.compose.animation.expandIn
import androidx.compose.animation.shrinkOut 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.res.stringResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.lerp
import androidx.compose.ui.unit.roundToIntRect import androidx.compose.ui.unit.roundToIntRect
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@ -428,7 +430,7 @@ fun AppItem(
@Composable @Composable
fun AppItemGridPopup( fun AppItemGridPopup(
app: LauncherApp, app: LauncherApp,
show: Boolean, show: MutableTransitionState<Boolean>,
animationProgress: Float, animationProgress: Float,
origin: Rect, origin: Rect,
onDismiss: () -> Unit onDismiss: () -> Unit
@ -452,8 +454,8 @@ fun AppItemGridPopup(
transformOrigin = TransformOrigin(1f, 0f) transformOrigin = TransformOrigin(1f, 0f)
) )
.offset( .offset(
x = 16.dp * (1 - animationProgress).pow(10), x = lerp(16.dp, 0.dp, animationProgress),
y = -16.dp * (1 - animationProgress), y = lerp(-16.dp, 0.dp, animationProgress)
), ),
app = app, app = app,
onBack = onDismiss onBack = onDismiss

View File

@ -3,6 +3,7 @@ package de.mm20.launcher2.ui.launcher.search.calendar
import android.content.Context import android.content.Context
import android.text.format.DateUtils import android.text.format.DateUtils
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.expandIn import androidx.compose.animation.expandIn
@ -301,7 +302,7 @@ fun CalendarItem(
@Composable @Composable
fun CalendarItemGridPopup( fun CalendarItemGridPopup(
calendar: CalendarEvent, calendar: CalendarEvent,
show: Boolean, show: MutableTransitionState<Boolean>,
animationProgress: Float, animationProgress: Float,
origin: Rect, origin: Rect,
onDismiss: () -> Unit onDismiss: () -> Unit

View File

@ -1,24 +1,30 @@
package de.mm20.launcher2.ui.launcher.search.common.grid package de.mm20.launcher2.ui.launcher.search.common.grid
import android.content.ComponentName import android.content.ComponentName
import android.util.Log
import androidx.activity.compose.BackHandler 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.animation.core.tween
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column 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.fillMaxWidth
import androidx.compose.foundation.layout.ime
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding 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.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@ -27,18 +33,18 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Rect import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.boundsInWindow import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.layout
import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow 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.unit.dp
import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupProperties
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.search.SavableSearchable import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.Searchable import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.data.AppShortcut 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.LauncherCard
import de.mm20.launcher2.ui.component.LocalIconShape import de.mm20.launcher2.ui.component.LocalIconShape
import de.mm20.launcher2.ui.component.ShapedLauncherIcon 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.ktx.toPixels
import de.mm20.launcher2.ui.launcher.search.apps.AppItemGridPopup import de.mm20.launcher2.ui.launcher.search.apps.AppItemGridPopup
import de.mm20.launcher2.ui.launcher.search.calendar.CalendarItemGridPopup 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.EnterHomeTransitionParams
import de.mm20.launcher2.ui.launcher.transitions.HandleEnterHomeTransition import de.mm20.launcher2.ui.launcher.transitions.HandleEnterHomeTransition
import de.mm20.launcher2.ui.locals.LocalGridSettings import de.mm20.launcher2.ui.locals.LocalGridSettings
import de.mm20.launcher2.ui.locals.LocalWindowPosition
import de.mm20.launcher2.ui.locals.LocalWindowSize 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 @Composable
@ -98,7 +104,8 @@ fun GridItem(
onClick = { onClick = {
if (!launchOnPress || !viewModel.launch(context, bounds)) { if (!launchOnPress || !viewModel.launch(context, bounds)) {
showPopup = true showPopup = true
}}, }
},
onLongClick = { onLongClick = {
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
showPopup = true showPopup = true
@ -106,7 +113,8 @@ fun GridItem(
indication = null, indication = null,
interactionSource = remember { MutableInteractionSource() }, interactionSource = remember { MutableInteractionSource() },
), ),
horizontalAlignment = Alignment.CenterHorizontally) { horizontalAlignment = Alignment.CenterHorizontally
) {
val badge by viewModel.badge.collectAsStateWithLifecycle() val badge by viewModel.badge.collectAsStateWithLifecycle()
val icon by viewModel.icon.collectAsStateWithLifecycle() val icon by viewModel.icon.collectAsStateWithLifecycle()
@ -181,132 +189,133 @@ fun GridItem(
@Composable @Composable
fun ItemPopup(origin: Rect, searchable: Searchable, onDismissRequest: () -> Unit) { fun ItemPopup(origin: Rect, searchable: Searchable, onDismissRequest: () -> Unit) {
var show by remember { mutableStateOf(false) } val show = remember {
LaunchedEffect(null) { MutableTransitionState(false).apply {
show = true targetState = true
}
} }
LaunchedEffect(show) { val animationProgress = remember { Animatable(0f) }
if (!show) { LaunchedEffect(show.targetState) {
delay(300L) if (!show.targetState) {
animationProgress.animateTo(0f, tween(300))
onDismissRequest() onDismissRequest()
} else {
animationProgress.animateTo(1f, tween(300))
} }
} }
BackHandler { BackHandler {
show = false show.targetState = false
} }
val animationProgress by animateFloatAsState(if (show) 1f else 0f, tween(300)) Overlay(zIndex = 1f) {
Popup( Box(
properties = PopupProperties( modifier = Modifier
usePlatformDefaultWidth = false, .background(MaterialTheme.colorScheme.scrim.copy(alpha = 0.32f * animationProgress.value))
dismissOnBackPress = true .fillMaxSize()
), .systemBarsPadding()
alignment = Alignment.TopCenter, .imePadding()
onDismissRequest = { .padding(horizontal = 16.dp)
show = false .pointerInput(Unit) {
}, detectTapGestures(onPress = {
offset = IntOffset(-origin.left.toInt(), 0) show.targetState = false
) { })
CompositionLocalProvider(LocalWindowPosition provides origin.top) { },
Box( ) {
LauncherCard(
elevation = 8.dp * animationProgress.value,
backgroundOpacity = 1f,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .placeOverlay(
origin.translate(
-16.dp.toPixels(),
-WindowInsets.systemBars.union(WindowInsets.ime).getTop(LocalDensity.current).toFloat()
),
animationProgress.value
)
) { ) {
LauncherCard( when (searchable) {
elevation = 8.dp * animationProgress, is LauncherApp -> {
backgroundOpacity = 1f, AppItemGridPopup(
modifier = Modifier app = searchable,
.padding(horizontal = 16.dp) show = show,
.absoluteOffset( animationProgress = animationProgress.value,
x = ((1 - animationProgress) * origin.left).toDp() - 20.dp * (1 - animationProgress), 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 -> { is Website -> {
WebsiteItemGridPopup( WebsiteItemGridPopup(
website = searchable, website = searchable,
show = show, show = show,
animationProgress = animationProgress, animationProgress = animationProgress.value,
origin = origin, origin = origin,
onDismiss = { onDismiss = {
show = false show.targetState = false
} }
) )
} }
is Wikipedia -> { is Wikipedia -> {
WikipediaItemGridPopup( WikipediaItemGridPopup(
wikipedia = searchable, wikipedia = searchable,
show = show, show = show,
animationProgress = animationProgress, animationProgress = animationProgress.value,
origin = origin, origin = origin,
onDismiss = { onDismiss = {
show = false show.targetState = false
} }
) )
} }
is Contact -> { is Contact -> {
ContactItemGridPopup( ContactItemGridPopup(
contact = searchable, contact = searchable,
show = show, show = show,
animationProgress = animationProgress, animationProgress = animationProgress.value,
origin = origin, origin = origin,
onDismiss = { onDismiss = {
show = false show.targetState = false
} }
) )
} }
is File -> { is File -> {
FileItemGridPopup( FileItemGridPopup(
file = searchable, file = searchable,
show = show, show = show,
animationProgress = animationProgress, animationProgress = animationProgress.value,
origin = origin, origin = origin,
onDismiss = { onDismiss = {
show = false show.targetState = false
} }
) )
} }
is CalendarEvent -> { is CalendarEvent -> {
CalendarItemGridPopup( CalendarItemGridPopup(
calendar = searchable, calendar = searchable,
show = show, show = show,
animationProgress = animationProgress, animationProgress = animationProgress.value,
origin = origin, origin = origin,
onDismiss = { onDismiss = {
show = false show.targetState = false
} }
) )
} }
is AppShortcut -> { is AppShortcut -> {
ShortcutItemGridPopup( ShortcutItemGridPopup(
shortcut = searchable, shortcut = searchable,
show = show, show = show,
animationProgress = animationProgress, animationProgress = animationProgress.value,
origin = origin, origin = origin,
onDismiss = { onDismiss = {
show = false show.targetState = false
} }
) )
}
} }
} }
} }
@ -314,3 +323,42 @@ fun ItemPopup(origin: Rect, searchable: Searchable, onDismissRequest: () -> Unit
} }
} }
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()
}

View File

@ -3,6 +3,7 @@ package de.mm20.launcher2.ui.launcher.search.contacts
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.core.animateDp import androidx.compose.animation.core.animateDp
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.core.updateTransition import androidx.compose.animation.core.updateTransition
@ -402,7 +403,7 @@ fun ContactItem(
@Composable @Composable
fun ContactItemGridPopup( fun ContactItemGridPopup(
contact: Contact, contact: Contact,
show: Boolean, show: MutableTransitionState<Boolean>,
animationProgress: Float, animationProgress: Float,
origin: Rect, origin: Rect,
onDismiss: () -> Unit onDismiss: () -> Unit

View File

@ -1,6 +1,7 @@
package de.mm20.launcher2.ui.launcher.search.files package de.mm20.launcher2.ui.launcher.search.files
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.core.animateDp import androidx.compose.animation.core.animateDp
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.core.updateTransition import androidx.compose.animation.core.updateTransition
@ -322,7 +323,7 @@ fun FileItem(
@Composable @Composable
fun FileItemGridPopup( fun FileItemGridPopup(
file: File, file: File,
show: Boolean, show: MutableTransitionState<Boolean>,
animationProgress: Float, animationProgress: Float,
origin: Rect, origin: Rect,
onDismiss: () -> Unit onDismiss: () -> Unit

View File

@ -5,6 +5,7 @@ import android.content.Intent
import android.net.Uri import android.net.Uri
import android.provider.Settings import android.provider.Settings
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.core.animateDp import androidx.compose.animation.core.animateDp
import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
@ -284,7 +285,7 @@ fun AppShortcutItem(
@Composable @Composable
fun ShortcutItemGridPopup( fun ShortcutItemGridPopup(
shortcut: AppShortcut, shortcut: AppShortcut,
show: Boolean, show: MutableTransitionState<Boolean>,
animationProgress: Float, animationProgress: Float,
origin: Rect, origin: Rect,
onDismiss: () -> Unit onDismiss: () -> Unit

View File

@ -1,6 +1,7 @@
package de.mm20.launcher2.ui.launcher.search.website package de.mm20.launcher2.ui.launcher.search.website
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.expandIn import androidx.compose.animation.expandIn
import androidx.compose.animation.shrinkOut import androidx.compose.animation.shrinkOut
@ -154,7 +155,7 @@ fun WebsiteItem(
@Composable @Composable
fun WebsiteItemGridPopup( fun WebsiteItemGridPopup(
website: Website, website: Website,
show: Boolean, show: MutableTransitionState<Boolean>,
animationProgress: Float, animationProgress: Float,
origin: Rect, origin: Rect,
onDismiss: () -> Unit onDismiss: () -> Unit

View File

@ -1,6 +1,7 @@
package de.mm20.launcher2.ui.launcher.search.wikipedia package de.mm20.launcher2.ui.launcher.search.wikipedia
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.expandIn import androidx.compose.animation.expandIn
import androidx.compose.animation.shrinkOut import androidx.compose.animation.shrinkOut
@ -161,7 +162,7 @@ fun WikipediaItem(
@Composable @Composable
fun WikipediaItemGridPopup( fun WikipediaItemGridPopup(
wikipedia: Wikipedia, wikipedia: Wikipedia,
show: Boolean, show: MutableTransitionState<Boolean>,
animationProgress: Float, animationProgress: Float,
origin: Rect, origin: Rect,
onDismiss: () -> Unit onDismiss: () -> Unit

View File

@ -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)
}
}
}

View File

@ -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> {
OverlayManager()
}

View File

@ -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<Overlay>()
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()
}
}

View File

@ -21,6 +21,7 @@ import de.mm20.launcher2.ui.base.BaseActivity
import de.mm20.launcher2.ui.base.ProvideSettings import de.mm20.launcher2.ui.base.ProvideSettings
import de.mm20.launcher2.ui.locals.LocalNavController import de.mm20.launcher2.ui.locals.LocalNavController
import de.mm20.launcher2.ui.locals.LocalWallpaperColors 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.about.AboutSettingsScreen
import de.mm20.launcher2.ui.settings.appearance.AppearanceSettingsScreen import de.mm20.launcher2.ui.settings.appearance.AppearanceSettingsScreen
import de.mm20.launcher2.ui.settings.backup.BackupSettingsScreen import de.mm20.launcher2.ui.settings.backup.BackupSettingsScreen
@ -78,129 +79,132 @@ class SettingsActivity : BaseActivity() {
) { ) {
ProvideSettings { ProvideSettings {
LauncherTheme { LauncherTheme {
AnimatedNavHost( OverlayHost {
navController = navController, AnimatedNavHost(
startDestination = "settings", navController = navController,
exitTransition = { fadeOut(tween(300, 300)) }, startDestination = "settings",
enterTransition = { fadeIn(tween(200)) }, exitTransition = { fadeOut(tween(300, 300)) },
popEnterTransition = { fadeIn(tween(0)) }, enterTransition = { fadeIn(tween(200)) },
popExitTransition = { fadeOut(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
})
) { ) {
val fileName = it.arguments?.getString("fileName") composable("settings") {
?.let { MainSettingsScreen()
URLDecoder.decode(it, "utf8") }
} composable("settings/appearance") {
CrashReportScreen(fileName!!) AppearanceSettingsScreen()
} }
composable( composable("settings/homescreen") {
"settings/license?library={libraryName}", HomescreenSettingsScreen()
arguments = listOf(navArgument("libraryName") { }
nullable = true composable("settings/icons") {
}) IconsSettingsScreen()
) { }
val libraryName = it.arguments?.getString("libraryName") composable("settings/appearance/themes") {
val library = remember(libraryName) { ThemesSettingsScreen()
if (libraryName == null) { }
AppLicense.get(this@SettingsActivity) composable(
} else { "settings/appearance/themes/{id}",
OpenSourceLicenses.first { it.name == libraryName } 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)
} }
} }
} }