diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherActivity.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherActivity.kt index d2529a3b..b8715035 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherActivity.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherActivity.kt @@ -2,6 +2,7 @@ package de.mm20.launcher2.ui.launcher import android.content.Intent import com.android.launcher3.GestureNavContract +import de.mm20.launcher2.ui.launcher.scaffold.ScaffoldPage class LauncherActivity: SharedLauncherActivity(LauncherActivityMode.Launcher) { @@ -9,7 +10,12 @@ class LauncherActivity: SharedLauncherActivity(LauncherActivityMode.Launcher) { super.onNewIntent(intent) val navContract = intent.let { GestureNavContract.fromIntent(it) } if (navContract != null) { - enterHomeTransitionManager.resolve(navContract, window) + val page = if (System.currentTimeMillis() - pauseTime > 5000L || pauseOnHome) { + ScaffoldPage.Home + } else { + ScaffoldPage.Secondary + } + enterHomeTransitionManager.resolve(navContract, window, page) } } 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 149e6d20..9b324033 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 @@ -432,6 +432,12 @@ abstract class SharedLauncherActivity( } } + var pauseTime = 0L + + /** + * True if the scaffold was on home screen when the activity was paused. + */ + var pauseOnHome = false var isNewIntent = false override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/scaffold/LauncherScaffold.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/scaffold/LauncherScaffold.kt index 0b7d34a4..aab95458 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/scaffold/LauncherScaffold.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/scaffold/LauncherScaffold.kt @@ -28,7 +28,6 @@ import androidx.compose.foundation.layout.absoluteOffset import androidx.compose.foundation.layout.add import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.displayCutout -import androidx.compose.foundation.layout.exclude import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.ime @@ -39,7 +38,6 @@ import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.statusBarsPadding @@ -49,6 +47,7 @@ import androidx.compose.foundation.shape.CutCornerShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.derivedStateOf @@ -245,14 +244,16 @@ internal class LauncherScaffoldState( initialIsLocked: Boolean = false, initialIsSearchBarHidden: Boolean = false, ) { - var currentOffset by mutableStateOf(when { - initialGesture == null || initialGesture.orientation == null || config[initialGesture]?.animation == ScaffoldAnimation.Rubberband -> Offset.Zero - initialGesture == Gesture.SwipeRight -> Offset(-size.width, 0f) - initialGesture == Gesture.SwipeLeft -> Offset(size.width, 0f) - initialGesture == Gesture.SwipeUp -> Offset(0f, -size.height) - initialGesture == Gesture.SwipeDown -> Offset(0f, size.height) - else -> Offset.Zero - }) + var currentOffset by mutableStateOf( + when { + initialGesture == null || initialGesture.orientation == null || config[initialGesture]?.animation == ScaffoldAnimation.Rubberband -> Offset.Zero + initialGesture == Gesture.SwipeRight -> Offset(-size.width, 0f) + initialGesture == Gesture.SwipeLeft -> Offset(size.width, 0f) + initialGesture == Gesture.SwipeUp -> Offset(0f, -size.height) + initialGesture == Gesture.SwipeDown -> Offset(0f, size.height) + else -> Offset.Zero + } + ) private set var currentZOffset by mutableFloatStateOf( if (initialGesture != null && initialGesture.orientation == null) 1f else 0f @@ -1176,10 +1177,10 @@ internal fun LauncherScaffold( } LaunchedEffect(state.isAtTop, state.isAtBottom) { - if (state.currentProgress > 0f && state.currentProgress < 1f){ + if (state.currentProgress > 0f && state.currentProgress < 1f) { return@LaunchedEffect } - when(state.currentComponent?.reverseScrolling) { + when (state.currentComponent?.reverseScrolling) { true -> if (state.isAtBottom) state.resetSearchBarOffset() false -> if (state.isAtTop) state.resetSearchBarOffset() else -> {} @@ -1211,10 +1212,11 @@ internal fun LauncherScaffold( config.homeComponent.onPreActivate(state) config.homeComponent.onActivate(state) - var pauseTime = 0L + val activity = (activity as? SharedLauncherActivity) ?: return@LaunchedEffect + lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) { try { - if (pauseTime > 0L && System.currentTimeMillis() - pauseTime < 50L && (activity as? SharedLauncherActivity)?.isNewIntent == true) { + if (activity.pauseTime > 0L && System.currentTimeMillis() - activity.pauseTime < 50L && activity.isNewIntent) { if (!state.isLocked) { if (state.currentProgress > 0f) { state.onPredictiveBackEnd() @@ -1224,7 +1226,7 @@ internal fun LauncherScaffold( } else { activity.onBackPressedDispatcher.onBackPressed() } - } else if (pauseTime > 0L && System.currentTimeMillis() - pauseTime > 5000L) { + } else if (activity.pauseTime > 0L && System.currentTimeMillis() - activity.pauseTime > 5000L) { if (!state.isLocked) { state.reset() searchVM.reset() @@ -1232,7 +1234,8 @@ internal fun LauncherScaffold( } awaitCancellation() } finally { - pauseTime = System.currentTimeMillis() + activity.pauseTime = System.currentTimeMillis() + activity.pauseOnHome = !state.isSettledOnSecondaryPage } } } @@ -1260,8 +1263,10 @@ internal fun LauncherScaffold( if (config.wallpaperBlurRadius > 0.dp) { val wallpaperBlur by animateIntAsState( - if (state.currentProgress >= 0.5f && (state.currentComponent?.drawBackground ?: config.homeComponent.drawBackground) - || state.currentProgress < 0.5f && config.homeComponent.drawBackground) { + if (state.currentProgress >= 0.5f && (state.currentComponent?.drawBackground + ?: config.homeComponent.drawBackground) + || state.currentProgress < 0.5f && config.homeComponent.drawBackground + ) { 8.dp.toPixels().toInt() } else { 0 @@ -1412,40 +1417,44 @@ internal fun LauncherScaffold( bottom = filterBarHeight ) - config.homeComponent.Component( - Modifier - .fillMaxSize() - .combinedClickable( - enabled = config.longPress != null || config.doubleTap != null, - onClick = {}, - onLongClick = if (config.longPress != null) { - { scope.launch { state.onLongPress() } } - } else null, - onDoubleClick = if (config.doubleTap != null) { - { scope.launch { state.onDoubleTap() } } - } else null, - hapticFeedbackEnabled = false, - indication = null, - interactionSource = null, - ) - .homePageAnimation( - state, - if (config.homeComponent.drawBackground) { - config.backgroundColor.copy(alpha = MaterialTheme.transparency.background) - } else { - Color.Transparent - } - ), - insets = systemBarInsets - .let { if (config.homeComponent.hasIme) it.union(WindowInsets.ime) else it } - .let { - if (config.searchBarStyle == SearchBarStyle.Hidden) it else it.add( - searchBarInsets + CompositionLocalProvider( + LocalScaffoldPage provides ScaffoldPage.Home, + ) { + config.homeComponent.Component( + Modifier + .fillMaxSize() + .combinedClickable( + enabled = config.longPress != null || config.doubleTap != null, + onClick = {}, + onLongClick = if (config.longPress != null) { + { scope.launch { state.onLongPress() } } + } else null, + onDoubleClick = if (config.doubleTap != null) { + { scope.launch { state.onDoubleTap() } } + } else null, + hapticFeedbackEnabled = false, + indication = null, + interactionSource = null, ) - } - .asPaddingValues(), - state - ) + .homePageAnimation( + state, + if (config.homeComponent.drawBackground) { + config.backgroundColor.copy(alpha = MaterialTheme.transparency.background) + } else { + Color.Transparent + } + ), + insets = systemBarInsets + .let { if (config.homeComponent.hasIme) it.union(WindowInsets.ime) else it } + .let { + if (config.searchBarStyle == SearchBarStyle.Hidden) it else it.add( + searchBarInsets + ) + } + .asPaddingValues(), + state + ) + } SecondaryPage( state = state, @@ -1494,7 +1503,7 @@ internal fun LauncherScaffold( highlightedAction = highlightedResult as? SearchAction, darkColors = config.darkSearchBar, isSearchOpen = state.currentComponent is SearchComponent && state.isSettledOnSecondaryPage || - config.homeComponent is SearchComponent && !state.isSettledOnSecondaryPage, + config.homeComponent is SearchComponent && !state.isSettledOnSecondaryPage, ) } if (isFilterBarVisible) { @@ -1566,6 +1575,7 @@ private fun SecondaryPage( modifier: Modifier = Modifier, insets: PaddingValues, ) { + val components = remember(config) { setOfNotNull( config.swipeUp?.component, @@ -1602,7 +1612,11 @@ private fun SecondaryPage( ) val composable = composables[component] - composable?.invoke(mod, insets, state) + CompositionLocalProvider( + LocalScaffoldPage provides ScaffoldPage.Secondary + ) { + composable?.invoke(mod, insets, state) + } } // Keep other components alive, but out of the viewport @@ -1616,7 +1630,6 @@ private fun SecondaryPage( v.invoke(modifier, insets, state) } } - } private fun Modifier.homePageAnimation( @@ -1657,12 +1670,16 @@ private fun Modifier.homePageAnimation( .background(backgroundColor) } - return this then component.homePageModifier(state, Modifier.background(backgroundColor).absoluteOffset { - IntOffset( - x = if (dir.orientation == Orientation.Horizontal) state.currentOffset.x.toInt() else 0, - y = if (dir.orientation == Orientation.Vertical) state.currentOffset.y.toInt() else 0 - ) - }) + return this then component.homePageModifier( + state, + Modifier + .background(backgroundColor) + .absoluteOffset { + IntOffset( + x = if (dir.orientation == Orientation.Horizontal) state.currentOffset.x.toInt() else 0, + y = if (dir.orientation == Orientation.Vertical) state.currentOffset.y.toInt() else 0 + ) + }) } private fun Modifier.secondaryPageAnimation( diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/scaffold/LocalScaffoldPage.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/scaffold/LocalScaffoldPage.kt new file mode 100644 index 00000000..f4c43f61 --- /dev/null +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/scaffold/LocalScaffoldPage.kt @@ -0,0 +1,10 @@ +package de.mm20.launcher2.ui.launcher.scaffold + +import androidx.compose.runtime.compositionLocalOf + +enum class ScaffoldPage { + Home, + Secondary, +} + +val LocalScaffoldPage = compositionLocalOf { null } \ No newline at end of file diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/transitions/EnterHomeTransitionHandler.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/transitions/EnterHomeTransitionHandler.kt index 38aac177..66f7a877 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/transitions/EnterHomeTransitionHandler.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/transitions/EnterHomeTransitionHandler.kt @@ -3,6 +3,7 @@ package de.mm20.launcher2.ui.launcher.transitions import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import com.android.launcher3.GestureNavContract +import de.mm20.launcher2.ui.launcher.scaffold.LocalScaffoldPage fun interface EnterHomeTransitionHandler { fun handle(gestureNavContract: GestureNavContract): EnterHomeTransitionParams? @@ -11,11 +12,14 @@ fun interface EnterHomeTransitionHandler { @Composable fun HandleEnterHomeTransition(handler: EnterHomeTransitionHandler) { val transitionManager = LocalEnterHomeTransitionManager.current - DisposableEffect(handler) { - transitionManager?.registerHandler(handler) + val page = LocalScaffoldPage.current + if (page != null && transitionManager != null) { + DisposableEffect(handler, page) { + transitionManager.registerHandler(handler, page) - onDispose { - transitionManager?.unregisterHandler(handler) + onDispose { + transitionManager.unregisterHandler(handler, page) + } } } } \ No newline at end of file diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/transitions/EnterHomeTransitionManager.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/transitions/EnterHomeTransitionManager.kt index d839f842..13c3a544 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/transitions/EnterHomeTransitionManager.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/transitions/EnterHomeTransitionManager.kt @@ -3,21 +3,24 @@ package de.mm20.launcher2.ui.launcher.transitions import android.view.Window import androidx.compose.runtime.compositionLocalOf import androidx.compose.ui.graphics.toAndroidRect -import androidx.compose.ui.graphics.toAndroidRectF import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntRect import androidx.compose.ui.unit.IntSize import androidx.core.graphics.toRectF import com.android.launcher3.GestureNavContract +import de.mm20.launcher2.ui.launcher.scaffold.ScaffoldPage import kotlinx.coroutines.flow.MutableSharedFlow class EnterHomeTransitionManager { val currentTransition = MutableSharedFlow(1) - private val handlers = mutableSetOf() + private val homeHandlers = mutableSetOf() + private val secondaryHandlers = mutableSetOf() + + fun resolve(gestureNavContract: GestureNavContract, window: Window, page: ScaffoldPage) { + val handlers = if (page === ScaffoldPage.Secondary) secondaryHandlers else homeHandlers - fun resolve(gestureNavContract: GestureNavContract, window: Window) { for (handler in handlers) { val result = handler.handle(gestureNavContract) if (result != null) { @@ -44,12 +47,23 @@ class EnterHomeTransitionManager { currentTransition.tryEmit(null) } - fun registerHandler(handler: EnterHomeTransitionHandler) { - handlers.add(handler) + /** + * The scaffold page that needs to be active for this handler to be considered. + */ + fun registerHandler(handler: EnterHomeTransitionHandler, page: ScaffoldPage) { + if (page == ScaffoldPage.Home) { + homeHandlers.add(handler) + } else if (page == ScaffoldPage.Secondary) { + secondaryHandlers.add(handler) + } } - fun unregisterHandler(handler: EnterHomeTransitionHandler) { - handlers.remove(handler) + fun unregisterHandler(handler: EnterHomeTransitionHandler, page: ScaffoldPage) { + if (page == ScaffoldPage.Home) { + homeHandlers.remove(handler) + } else if (page == ScaffoldPage.Secondary) { + secondaryHandlers.remove(handler) + } } }