From a9032735f2151d06a7ceba24dc2216b50141432a Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Sat, 8 Oct 2022 18:41:14 +0200 Subject: [PATCH] Animate launcher icon in return to home gesture animation --- .../launcher2/ui/launcher/LauncherActivity.kt | 3 +- .../ui/launcher/SharedLauncherActivity.kt | 64 ++++++++++++++----- .../launcher/search/common/grid/GridItem.kt | 6 +- .../ui/launcher/transitions/HomeTransition.kt | 11 ++++ .../transitions/HomeTransitionManager.kt | 22 +++++-- .../transitions/HomeTransitionOffer.kt | 8 --- .../transitions/HomeTransitionParams.kt | 3 +- 7 files changed, 87 insertions(+), 30 deletions(-) create mode 100644 ui/src/main/java/de/mm20/launcher2/ui/launcher/transitions/HomeTransition.kt delete mode 100644 ui/src/main/java/de/mm20/launcher2/ui/launcher/transitions/HomeTransitionOffer.kt diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherActivity.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherActivity.kt index 6707460b..e2dc1378 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherActivity.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherActivity.kt @@ -9,8 +9,9 @@ class LauncherActivity: SharedLauncherActivity(LauncherActivityMode.Launcher) { super.onNewIntent(intent) val navContract = intent?.let { GestureNavContract.fromIntent(it) } if (navContract != null) { - homeTransitionManager.resolve(navContract) + homeTransitionManager.resolve(navContract, window) } else { + homeTransitionManager.clear() onBackPressed() } } diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/SharedLauncherActivity.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/SharedLauncherActivity.kt index d3c76b50..2e39111a 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/SharedLauncherActivity.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/SharedLauncherActivity.kt @@ -1,11 +1,9 @@ package de.mm20.launcher2.ui.launcher import android.app.WallpaperManager -import android.content.Intent import android.content.res.Configuration import android.content.res.Resources import android.os.Bundle -import android.util.Log import androidx.activity.compose.setContent import androidx.activity.viewModels import androidx.compose.foundation.background @@ -16,34 +14,40 @@ import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState -import androidx.compose.runtime.* +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.pointerInput -import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsControllerCompat import androidx.lifecycle.Lifecycle import androidx.lifecycle.flowWithLifecycle -import com.android.launcher3.GestureNavContract import com.google.accompanist.systemuicontroller.rememberSystemUiController import de.mm20.launcher2.preferences.Settings -import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.assistant.AssistantScaffold import de.mm20.launcher2.ui.base.BaseActivity import de.mm20.launcher2.ui.base.ProvideCurrentTime import de.mm20.launcher2.ui.base.ProvideSettings import de.mm20.launcher2.ui.component.NavBarEffects import de.mm20.launcher2.ui.ktx.animateTo +import de.mm20.launcher2.ui.launcher.transitions.HomeTransition import de.mm20.launcher2.ui.launcher.transitions.HomeTransitionManager import de.mm20.launcher2.ui.launcher.transitions.LocalHomeTransitionManager import de.mm20.launcher2.ui.locals.LocalSnackbarHostState import de.mm20.launcher2.ui.locals.LocalWindowSize import de.mm20.launcher2.ui.theme.LauncherTheme +import kotlin.math.pow abstract class SharedLauncherActivity( @@ -86,15 +90,22 @@ abstract class SharedLauncherActivity( val systemUiController = rememberSystemUiController() - val enterTransition = remember { mutableStateOf(1f) } + val enterTransitionProgress = remember { mutableStateOf(1f) } + var enterTransition by remember { + mutableStateOf( + null + ) + } LaunchedEffect(null) { homeTransitionManager .currentTransition .flowWithLifecycle(lifecycle, Lifecycle.State.RESUMED) .collect { - enterTransition.value = 0f - enterTransition.animateTo(1f) + enterTransitionProgress.value = 0f + enterTransition = it + enterTransitionProgress.animateTo(1f) + enterTransition = null } } @@ -138,29 +149,33 @@ abstract class SharedLauncherActivity( modifier = Modifier .fillMaxSize() .graphicsLayer { - scaleX = 0.5f + enterTransition.value * 0.5f - scaleY = 0.5f + enterTransition.value * 0.5f - alpha = enterTransition.value + scaleX = + 0.5f + enterTransitionProgress.value * 0.5f + scaleY = + 0.5f + enterTransitionProgress.value * 0.5f + alpha = enterTransitionProgress.value }, darkStatusBarIcons = lightStatus, darkNavBarIcons = lightNav, ) } + Settings.AppearanceSettings.Layout.Pager, Settings.AppearanceSettings.Layout.PagerReversed -> { PagerScaffold( modifier = Modifier .fillMaxSize() .graphicsLayer { - scaleX = enterTransition.value - scaleY = enterTransition.value - alpha = enterTransition.value + scaleX = enterTransitionProgress.value + scaleY = enterTransitionProgress.value + alpha = enterTransitionProgress.value }, darkStatusBarIcons = lightStatus, darkNavBarIcons = lightNav, reverse = layout == Settings.AppearanceSettings.Layout.PagerReversed ) } + else -> {} } } @@ -170,6 +185,25 @@ abstract class SharedLauncherActivity( .navigationBarsPadding() .imePadding() ) + enterTransition?.let { + val dX = it.startBounds.center.x - it.targetBounds.center.x + val dY = it.startBounds.center.y - it.targetBounds.center.y + val s = (it.startBounds.minDimension / it.targetBounds.minDimension - 1f) * 0.5f + Box( + modifier = Modifier + .align(Alignment.TopStart) + .graphicsLayer { + val p = (enterTransitionProgress.value).pow(2f) + transformOrigin = TransformOrigin.Center + translationX = it.targetBounds.left + dX * (1 - p) + translationY = it.targetBounds.top + dY * (1 - p) + alpha = enterTransitionProgress.value + scaleX = 1f + s * (1 - p) + scaleY = 1f + s * (1 - p) + }) { + it.icon?.invoke(Offset(dX, dY)) { enterTransitionProgress.value } + } + } } } } diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/grid/GridItem.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/grid/GridItem.kt index 99531da8..f46b7716 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/grid/GridItem.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/grid/GridItem.kt @@ -73,7 +73,11 @@ fun GridItem(modifier: Modifier = Modifier, item: Searchable, showLabels: Boolea bounds.right > 0f && bounds.left < windowSize.width && bounds.bottom > 0f && bounds.top < windowSize.height ) { - return@HandleHomeTransition HomeTransitionParams(bounds) + return@HandleHomeTransition HomeTransitionParams( + bounds + ) { _, _ -> + ShapedLauncherIcon(size = LocalGridIconSize.current, icon = { icon }) + } } return@HandleHomeTransition null } diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/transitions/HomeTransition.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/transitions/HomeTransition.kt new file mode 100644 index 00000000..991d4867 --- /dev/null +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/transitions/HomeTransition.kt @@ -0,0 +1,11 @@ +package de.mm20.launcher2.ui.launcher.transitions + +import androidx.compose.runtime.Composable +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Rect + +data class HomeTransition( + val startBounds: Rect, + val targetBounds: Rect, + val icon: (@Composable (animVector: Offset, progress: () -> Float) -> Unit)? = null +) \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/transitions/HomeTransitionManager.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/transitions/HomeTransitionManager.kt index 925e6ae1..12194f9f 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/transitions/HomeTransitionManager.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/transitions/HomeTransitionManager.kt @@ -1,28 +1,42 @@ package de.mm20.launcher2.ui.launcher.transitions +import android.view.Window import androidx.compose.runtime.compositionLocalOf +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.toAndroidRectF import com.android.launcher3.GestureNavContract import kotlinx.coroutines.flow.MutableSharedFlow class HomeTransitionManager { - val currentTransition = MutableSharedFlow(1) + val currentTransition = MutableSharedFlow(1) private val handlers = mutableSetOf() - fun resolve(gestureNavContract: GestureNavContract) { + fun resolve(gestureNavContract: GestureNavContract, window: Window) { for (handler in handlers) { val result = handler.handle(gestureNavContract) if (result != null) { - gestureNavContract.sendEndPosition(result.targetBounds.toAndroidRectF()) - currentTransition.tryEmit(result) + val startRect = Rect(Offset(0f, 0f), Size(window.decorView.width.toFloat(), window.decorView.height.toFloat())) + val targetBounds = result.targetBounds + gestureNavContract.sendEndPosition(targetBounds.toAndroidRectF()) + currentTransition.tryEmit(HomeTransition( + startBounds = startRect, + icon = result.icon, + targetBounds = targetBounds, + )) return } } currentTransition.tryEmit(null) } + fun clear() { + currentTransition.tryEmit(null) + } + fun registerHandler(handler: HomeTransitionHandler) { handlers.add(handler) } diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/transitions/HomeTransitionOffer.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/transitions/HomeTransitionOffer.kt deleted file mode 100644 index d77bd948..00000000 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/transitions/HomeTransitionOffer.kt +++ /dev/null @@ -1,8 +0,0 @@ -package de.mm20.launcher2.ui.launcher.transitions - -import androidx.compose.runtime.Composable -import androidx.compose.ui.geometry.Rect - -fun interface HomeTransitionOffer { - fun accept(targetBounds: Rect, icon: @Composable () -> Unit) -} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/transitions/HomeTransitionParams.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/transitions/HomeTransitionParams.kt index 535f80df..5b5bd95e 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/transitions/HomeTransitionParams.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/transitions/HomeTransitionParams.kt @@ -2,10 +2,11 @@ package de.mm20.launcher2.ui.launcher.transitions import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect @Stable data class HomeTransitionParams( val targetBounds: Rect, - val icon: (@Composable () -> Unit)? = null + val icon: (@Composable (animVector: Offset, progress: () -> Float) -> Unit)? = null ) \ No newline at end of file