Add "fake" chat list bitmap to fake transition.

This commit is contained in:
Alex Hart
2025-10-22 10:50:27 -03:00
committed by Greyson Parrelli
parent bd25447a8f
commit d4c266561f
8 changed files with 252 additions and 32 deletions

View File

@@ -221,6 +221,7 @@ enum class WindowSizeClass(
@Composable
fun AppScaffold(
navigator: AppScaffoldNavigator<Any>,
modifier: Modifier = Modifier,
topBarContent: @Composable () -> Unit = {},
primaryContent: @Composable () -> Unit = {},
secondaryContent: @Composable () -> Unit,
@@ -242,7 +243,8 @@ fun AppScaffold(
navRailContent = navRailContent,
bottomNavContent = bottomNavContent,
windowSizeClass = windowSizeClass,
contentWindowInsets = contentWindowInsets
contentWindowInsets = contentWindowInsets,
modifier = modifier
)
return
@@ -255,7 +257,8 @@ fun AppScaffold(
containerColor = Color.Transparent,
contentWindowInsets = contentWindowInsets,
topBar = topBarContent,
snackbarHost = snackbarHost
snackbarHost = snackbarHost,
modifier = modifier
) { paddingValues ->
NavigableListDetailPaneScaffold(
navigator = navigator,
@@ -353,13 +356,15 @@ private fun ListAndNavigation(
bottomNavContent: @Composable () -> Unit,
snackbarHost: @Composable () -> Unit = {},
windowSizeClass: WindowSizeClass,
contentWindowInsets: WindowInsets
contentWindowInsets: WindowInsets,
modifier: Modifier = Modifier
) {
Scaffold(
containerColor = Color.Transparent,
topBar = topBarContent,
contentWindowInsets = contentWindowInsets,
snackbarHost = snackbarHost
snackbarHost = snackbarHost,
modifier = modifier
) { paddingValues ->
Row(
modifier = Modifier

View File

@@ -5,6 +5,8 @@
package org.thoughtcrime.securesms.window
import androidx.compose.animation.core.CubicBezierEasing
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
@@ -18,6 +20,16 @@ import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
/**
* Default animation settings for app-scaffold animations.
*/
object AppScaffoldAnimationDefaults {
val TweenEasing = CubicBezierEasing(0.4f, 0.0f, 0.2f, 1f)
val InitAnimationOffset = 48.dp
fun <T> tween() = tween<T>(durationMillis = 200, easing = TweenEasing)
}
/**
* Produces modifier that can be composed into another modifier chain.
* This object allows us to store "latest state" as we transition.
@@ -47,17 +59,31 @@ data class AppScaffoldAnimationState(
* Allows for the customization of the AppScaffold Animators.
*/
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
abstract class AppScaffoldAnimationStateFactory {
class AppScaffoldAnimationStateFactory(
val enabledStates: Set<AppScaffoldNavigator.NavigationState> = AppScaffoldNavigator.NavigationState.entries.toSet()
) {
object Default : AppScaffoldAnimationStateFactory()
companion object {
val Default = AppScaffoldAnimationStateFactory()
protected var latestListSeekState: AppScaffoldAnimationState = AppScaffoldAnimationState(AppScaffoldNavigator.NavigationState.SEEK)
protected var latestDetailSeekState: AppScaffoldAnimationState = AppScaffoldAnimationState(AppScaffoldNavigator.NavigationState.SEEK)
private val EMPTY_STATE = AppScaffoldAnimationState(
navigationState = AppScaffoldNavigator.NavigationState.ENTER,
alpha = 1f
)
}
private var latestListSeekState: AppScaffoldAnimationState = AppScaffoldAnimationState(AppScaffoldNavigator.NavigationState.SEEK)
private var latestDetailSeekState: AppScaffoldAnimationState = AppScaffoldAnimationState(AppScaffoldNavigator.NavigationState.SEEK)
@Composable
fun ThreePaneScaffoldPaneScope.getListAnimationState(navigationState: AppScaffoldNavigator.NavigationState): AppScaffoldAnimationState {
if (navigationState !in enabledStates) {
return EMPTY_STATE
}
return when (navigationState) {
AppScaffoldNavigator.NavigationState.INIT -> defaultListInitAnimationState()
AppScaffoldNavigator.NavigationState.ENTER -> defaultListInitAnimationState()
AppScaffoldNavigator.NavigationState.EXIT -> defaultListInitAnimationState()
AppScaffoldNavigator.NavigationState.SEEK -> defaultListSeekAnimationState().also {
latestListSeekState = it
}
@@ -67,8 +93,13 @@ abstract class AppScaffoldAnimationStateFactory {
@Composable
fun ThreePaneScaffoldPaneScope.getDetailAnimationState(navigationState: AppScaffoldNavigator.NavigationState): AppScaffoldAnimationState {
if (navigationState !in enabledStates) {
return EMPTY_STATE
}
return when (navigationState) {
AppScaffoldNavigator.NavigationState.INIT -> defaultDetailInitAnimationState()
AppScaffoldNavigator.NavigationState.ENTER -> defaultDetailInitAnimationState()
AppScaffoldNavigator.NavigationState.EXIT -> defaultDetailInitAnimationState()
AppScaffoldNavigator.NavigationState.SEEK -> defaultDetailSeekAnimationState().also {
latestDetailSeekState = it
}

View File

@@ -5,14 +5,12 @@
package org.thoughtcrime.securesms.window
import androidx.compose.animation.core.CubicBezierEasing
import androidx.compose.animation.core.FiniteAnimationSpec
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.Transition
import androidx.compose.animation.core.animateDp
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
import androidx.compose.material3.adaptive.layout.PaneAdaptedValue
@@ -38,12 +36,10 @@ fun <T> appScaffoldSeekSpring(): FiniteAnimationSpec<T> = spring(
stiffness = SEEK_STIFFNESS
)
private val easing = CubicBezierEasing(0.4f, 0.0f, 0.2f, 1f)
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun ThreePaneScaffoldPaneScope.animateDp(
transitionSpec: @Composable Transition.Segment<*>.() -> FiniteAnimationSpec<Dp> = { tween(durationMillis = 200, easing = easing) },
transitionSpec: @Composable Transition.Segment<*>.() -> FiniteAnimationSpec<Dp> = { AppScaffoldAnimationDefaults.tween() },
targetWhenHiding: () -> Dp = { 0.dp },
targetWhenShowing: () -> Dp
): State<Dp> {
@@ -63,7 +59,7 @@ fun ThreePaneScaffoldPaneScope.animateDp(
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun ThreePaneScaffoldPaneScope.animateFloat(
transitionSpec: @Composable Transition.Segment<*>.() -> FiniteAnimationSpec<Float> = { tween(durationMillis = 200, easing = easing) },
transitionSpec: @Composable Transition.Segment<*>.() -> FiniteAnimationSpec<Float> = { AppScaffoldAnimationDefaults.tween() },
targetWhenHiding: () -> Float = { 0f },
targetWhenShowing: () -> Float
): State<Float> {
@@ -85,7 +81,7 @@ fun ThreePaneScaffoldPaneScope.animateFloat(
fun ThreePaneScaffoldPaneScope.defaultListInitAnimationState(): AppScaffoldAnimationState {
val offset by animateDp(
targetWhenHiding = {
(-48).dp
-AppScaffoldAnimationDefaults.InitAnimationOffset
},
targetWhenShowing = {
0.dp
@@ -97,7 +93,7 @@ fun ThreePaneScaffoldPaneScope.defaultListInitAnimationState(): AppScaffoldAnima
}
return AppScaffoldAnimationState(
AppScaffoldNavigator.NavigationState.INIT,
AppScaffoldNavigator.NavigationState.ENTER,
alpha = alpha,
offset = offset
)
@@ -169,7 +165,7 @@ fun ThreePaneScaffoldPaneScope.defaultListReleaseAnimationState(from: AppScaffol
fun ThreePaneScaffoldPaneScope.defaultDetailInitAnimationState(): AppScaffoldAnimationState {
val offset by animateDp(
targetWhenHiding = {
48.dp
AppScaffoldAnimationDefaults.InitAnimationOffset
},
targetWhenShowing = {
0.dp
@@ -181,7 +177,7 @@ fun ThreePaneScaffoldPaneScope.defaultDetailInitAnimationState(): AppScaffoldAni
}
return AppScaffoldAnimationState(
navigationState = AppScaffoldNavigator.NavigationState.INIT,
navigationState = AppScaffoldNavigator.NavigationState.ENTER,
alpha = alpha,
offset = offset
)

View File

@@ -31,11 +31,11 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
open class AppScaffoldNavigator<T> @RememberInComposition constructor(private val delegate: ThreePaneScaffoldNavigator<T>) : ThreePaneScaffoldNavigator<T> by delegate {
var state: NavigationState by mutableStateOf(NavigationState.INIT)
var state: NavigationState by mutableStateOf(NavigationState.ENTER)
private set
override suspend fun navigateTo(pane: ThreePaneScaffoldRole, contentKey: T?) {
state = NavigationState.INIT
state = NavigationState.ENTER
return delegate.navigateTo(pane, contentKey)
}
@@ -44,6 +44,10 @@ open class AppScaffoldNavigator<T> @RememberInComposition constructor(private va
state = NavigationState.RELEASE
}
if (state == NavigationState.ENTER) {
state = NavigationState.EXIT
}
return delegate.navigateBack(backNavigationBehavior)
}
@@ -60,11 +64,14 @@ open class AppScaffoldNavigator<T> @RememberInComposition constructor(private va
*/
enum class NavigationState {
/**
* We've navigated to a new pane. This animation is used for both immediate
* pane entry and exit (such as tapping a back button instead of using a
* gesture)
* We've navigated to a new pane.
*/
INIT,
ENTER,
/**
* We've navigated back from a pane without using seek.
*/
EXIT,
/**
* The user is performing a back gesture seek action.