mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-07-04 13:05:19 +01:00
Reduce Compose overhead on lower-end device.
Co-authored-by: Greyson Parrelli <greyson@signal.org>
This commit is contained in:
committed by
Greyson Parrelli
parent
370fca3c89
commit
4dd5a4ee53
@@ -21,6 +21,8 @@ import org.thoughtcrime.securesms.serialization.JsonSerializableNavType
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkRoomId
|
||||
import kotlin.reflect.typeOf
|
||||
|
||||
private val callLinkRoomIdType = typeOf<CallLinkRoomId>()
|
||||
|
||||
fun NavGraphBuilder.callNavGraphBuilder(navHostController: NavHostController) {
|
||||
composable<MainNavigationDetailLocation.Empty> {
|
||||
EmptyDetailScreen()
|
||||
@@ -28,7 +30,7 @@ fun NavGraphBuilder.callNavGraphBuilder(navHostController: NavHostController) {
|
||||
|
||||
composable<MainNavigationDetailLocation.Calls.CallLinks.CallLinkDetails>(
|
||||
typeMap = mapOf(
|
||||
typeOf<CallLinkRoomId>() to JsonSerializableNavType(CallLinkRoomId.serializer())
|
||||
callLinkRoomIdType to JsonSerializableNavType(CallLinkRoomId.serializer())
|
||||
)
|
||||
) {
|
||||
informNavigatorWeAreReady()
|
||||
@@ -40,7 +42,7 @@ fun NavGraphBuilder.callNavGraphBuilder(navHostController: NavHostController) {
|
||||
|
||||
composable<MainNavigationDetailLocation.Calls.CallLinks.EditCallLinkName>(
|
||||
typeMap = mapOf(
|
||||
typeOf<CallLinkRoomId>() to JsonSerializableNavType(CallLinkRoomId.serializer())
|
||||
callLinkRoomIdType to JsonSerializableNavType(CallLinkRoomId.serializer())
|
||||
)
|
||||
) {
|
||||
informNavigatorWeAreReady()
|
||||
|
||||
@@ -62,6 +62,10 @@ import org.thoughtcrime.securesms.window.AppScaffoldAnimationState
|
||||
import kotlin.reflect.typeOf
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
private val conversationArgsType = typeOf<ConversationArgs>()
|
||||
private val recipientIdType = typeOf<RecipientId>()
|
||||
private val messageIdType = typeOf<MessageId>()
|
||||
|
||||
fun NavGraphBuilder.chatNavGraphBuilder(
|
||||
chatNavGraphState: ChatNavGraphState
|
||||
) {
|
||||
@@ -71,7 +75,7 @@ fun NavGraphBuilder.chatNavGraphBuilder(
|
||||
|
||||
composable<MainNavigationDetailLocation.Chats.Conversation>(
|
||||
typeMap = mapOf(
|
||||
typeOf<ConversationArgs>() to JsonSerializableNavType(ConversationArgs.serializer())
|
||||
conversationArgsType to JsonSerializableNavType(ConversationArgs.serializer())
|
||||
)
|
||||
) { navBackStackEntry ->
|
||||
val route = navBackStackEntry.toRoute<MainNavigationDetailLocation.Chats.Conversation>()
|
||||
@@ -147,8 +151,8 @@ fun NavGraphBuilder.chatNavGraphBuilder(
|
||||
|
||||
composable<MainNavigationDetailLocation.Chats.MessageDetails>(
|
||||
typeMap = mapOf(
|
||||
typeOf<RecipientId>() to JsonSerializableNavType(RecipientId.serializer()),
|
||||
typeOf<MessageId>() to MessageId.NavType()
|
||||
recipientIdType to JsonSerializableNavType(RecipientId.serializer()),
|
||||
messageIdType to MessageId.NavType()
|
||||
)
|
||||
) { navBackStackEntry ->
|
||||
val context = LocalContext.current
|
||||
@@ -173,7 +177,7 @@ fun NavGraphBuilder.chatNavGraphBuilder(
|
||||
|
||||
composable<MainNavigationDetailLocation.Chats.ConversationSettings>(
|
||||
typeMap = mapOf(
|
||||
typeOf<RecipientId>() to JsonSerializableNavType(RecipientId.serializer())
|
||||
recipientIdType to JsonSerializableNavType(RecipientId.serializer())
|
||||
)
|
||||
) { navBackStackEntry ->
|
||||
|
||||
|
||||
@@ -5,8 +5,16 @@
|
||||
|
||||
package org.thoughtcrime.securesms.window
|
||||
|
||||
import android.os.Build
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.EnterTransition
|
||||
import androidx.compose.animation.ExitTransition
|
||||
import androidx.compose.animation.SizeTransform
|
||||
import androidx.compose.animation.core.snap
|
||||
import androidx.compose.animation.slideInHorizontally
|
||||
import androidx.compose.animation.slideOutHorizontally
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -24,6 +32,7 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
|
||||
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
|
||||
import androidx.compose.material3.adaptive.layout.AnimatedPane
|
||||
import androidx.compose.material3.adaptive.layout.PaneAdaptedValue
|
||||
import androidx.compose.material3.adaptive.layout.PaneExpansionState
|
||||
import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope
|
||||
import androidx.compose.material3.adaptive.layout.defaultDragHandleSemantics
|
||||
@@ -31,6 +40,7 @@ import androidx.compose.material3.adaptive.layout.rememberPaneExpansionState
|
||||
import androidx.compose.material3.adaptive.navigation.NavigableListDetailPaneScaffold
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clipToBounds
|
||||
@@ -41,13 +51,14 @@ import androidx.compose.ui.layout.layout
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalResources
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.zIndex
|
||||
import kotlinx.coroutines.launch
|
||||
import org.signal.core.ui.WindowBreakpoint
|
||||
import org.signal.core.ui.compose.AllDevicePreviews
|
||||
import org.signal.core.ui.compose.Previews
|
||||
import org.signal.core.ui.getWindowBreakpoint
|
||||
import org.signal.core.ui.isSplitPane
|
||||
import org.signal.core.ui.isWidthExpanded
|
||||
import org.signal.core.ui.rememberIsSplitPane
|
||||
import org.thoughtcrime.securesms.main.MainFloatingActionButtonsCallback
|
||||
@@ -87,6 +98,11 @@ enum class NavigationType {
|
||||
* A top-level scaffold that automatically adapts its layout based on the device's window size class. It is a generic container designed to handle the
|
||||
* arrangement of navigation rails, top/bottom bars, and list-detail pane management for both compact and large screens.
|
||||
*
|
||||
* On phone-class layouts (single horizontal partition) running on devices that predate predictive back (API < 33),
|
||||
* this dispatches to [SinglePaneAppScaffold], which skips [NavigableListDetailPaneScaffold] / [ThreePaneScaffold] and
|
||||
* its lookahead measurement pass. The scaffold's seek-driven predictive back animation never fires on those devices,
|
||||
* so we pay no UX cost for the simpler implementation.
|
||||
*
|
||||
* @param topBarContent An optional top bar that spans across all panes.
|
||||
*
|
||||
* @param primaryContent The main content, which is typically the detail view in a split-pane layout.
|
||||
@@ -95,10 +111,10 @@ enum class NavigationType {
|
||||
* @param navRailContent The side navigation rail, shown on medium and larger screen sizes.
|
||||
* @param bottomNavContent The bottom navigation bar, shown on compact screen sizes.
|
||||
*
|
||||
* @param paneExpansionState Manages the position and expansion of the panes in a list-detail layout.
|
||||
* @param paneExpansionDragHandle An optional drag handle used to resize panes in the list-detail layout.
|
||||
* @param paneExpansionState Manages the position and expansion of the panes in a list-detail layout. Ignored by [SinglePaneAppScaffold].
|
||||
* @param paneExpansionDragHandle An optional drag handle used to resize panes in the list-detail layout. Ignored by [SinglePaneAppScaffold].
|
||||
*
|
||||
* @param animatorFactory Provides animations to control how panes enter and exit the screen during navigation.
|
||||
* @param animatorFactory Provides animations to control how panes enter and exit the screen during navigation. Ignored by [SinglePaneAppScaffold].
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
|
||||
@Composable
|
||||
@@ -115,6 +131,52 @@ fun AppScaffold(
|
||||
snackbarHost: @Composable () -> Unit = {},
|
||||
contentWindowInsets: WindowInsets = WindowInsets.systemBars,
|
||||
animatorFactory: AppScaffoldAnimationStateFactory = AppScaffoldAnimationStateFactory.Default
|
||||
) {
|
||||
val useSimpleScaffold = navigator.scaffoldDirective.maxHorizontalPartitions == 1 && Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU
|
||||
if (useSimpleScaffold) {
|
||||
SinglePaneAppScaffold(
|
||||
navigator = navigator,
|
||||
modifier = modifier,
|
||||
topBarContent = topBarContent,
|
||||
primaryContent = primaryContent,
|
||||
secondaryContent = secondaryContent,
|
||||
bottomNavContent = bottomNavContent,
|
||||
snackbarHost = snackbarHost,
|
||||
contentWindowInsets = contentWindowInsets
|
||||
)
|
||||
} else {
|
||||
AdaptiveAppScaffold(
|
||||
navigator = navigator,
|
||||
modifier = modifier,
|
||||
topBarContent = topBarContent,
|
||||
primaryContent = primaryContent,
|
||||
secondaryContent = secondaryContent,
|
||||
navRailContent = navRailContent,
|
||||
bottomNavContent = bottomNavContent,
|
||||
paneExpansionState = paneExpansionState,
|
||||
paneExpansionDragHandle = paneExpansionDragHandle,
|
||||
snackbarHost = snackbarHost,
|
||||
contentWindowInsets = contentWindowInsets,
|
||||
animatorFactory = animatorFactory
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
|
||||
@Composable
|
||||
private fun AdaptiveAppScaffold(
|
||||
navigator: AppScaffoldNavigator<Any>,
|
||||
modifier: Modifier = Modifier,
|
||||
topBarContent: @Composable () -> Unit = {},
|
||||
primaryContent: @Composable () -> Unit = {},
|
||||
secondaryContent: @Composable () -> Unit,
|
||||
navRailContent: @Composable () -> Unit = {},
|
||||
bottomNavContent: @Composable () -> Unit = {},
|
||||
paneExpansionState: PaneExpansionState = rememberPaneExpansionState(),
|
||||
paneExpansionDragHandle: (@Composable ThreePaneScaffoldScope.(PaneExpansionState) -> Unit)? = null,
|
||||
snackbarHost: @Composable () -> Unit = {},
|
||||
contentWindowInsets: WindowInsets = WindowInsets.systemBars,
|
||||
animatorFactory: AppScaffoldAnimationStateFactory = AppScaffoldAnimationStateFactory.Default
|
||||
) {
|
||||
val minPaneWidth = navigator.scaffoldDirective.defaultPanePreferredWidth
|
||||
val navigationState = navigator.state
|
||||
@@ -229,6 +291,67 @@ fun AppScaffold(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Phone-only scaffold that swaps content between [secondaryContent] and [primaryContent] without using
|
||||
* [NavigableListDetailPaneScaffold]. Avoids the lookahead measurement pass and deep adaptive layout tree
|
||||
* that drives ANR on low-end devices.
|
||||
*
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
|
||||
@Composable
|
||||
private fun SinglePaneAppScaffold(
|
||||
navigator: AppScaffoldNavigator<Any>,
|
||||
modifier: Modifier = Modifier,
|
||||
topBarContent: @Composable () -> Unit = {},
|
||||
primaryContent: @Composable () -> Unit = {},
|
||||
secondaryContent: @Composable () -> Unit,
|
||||
bottomNavContent: @Composable () -> Unit = {},
|
||||
snackbarHost: @Composable () -> Unit = {},
|
||||
contentWindowInsets: WindowInsets = WindowInsets.systemBars
|
||||
) {
|
||||
val showDetail = navigator.scaffoldValue.primary == PaneAdaptedValue.Expanded
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
BackHandler(enabled = navigator.canNavigateBack()) {
|
||||
coroutineScope.launch { navigator.navigateBack() }
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
containerColor = Color.Transparent,
|
||||
contentWindowInsets = contentWindowInsets,
|
||||
topBar = topBarContent,
|
||||
snackbarHost = snackbarHost,
|
||||
modifier = modifier
|
||||
) { paddingValues ->
|
||||
AnimatedContent(
|
||||
targetState = showDetail,
|
||||
transitionSpec = {
|
||||
val transform = if (targetState) {
|
||||
slideInHorizontally(animationSpec = AppScaffoldAnimationDefaults.tween()) { fullWidth -> fullWidth } togetherWith
|
||||
slideOutHorizontally(animationSpec = AppScaffoldAnimationDefaults.tween()) { fullWidth -> -fullWidth }
|
||||
} else {
|
||||
slideInHorizontally(animationSpec = AppScaffoldAnimationDefaults.tween()) { fullWidth -> -fullWidth } togetherWith
|
||||
slideOutHorizontally(animationSpec = AppScaffoldAnimationDefaults.tween()) { fullWidth -> fullWidth }
|
||||
}
|
||||
transform using SizeTransform(clip = false) { _, _ -> snap() }
|
||||
},
|
||||
modifier = Modifier.padding(paddingValues),
|
||||
label = "SimpleAppScaffold"
|
||||
) { isDetail ->
|
||||
if (isDetail) {
|
||||
primaryContent()
|
||||
} else {
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
Box(modifier = Modifier.weight(1f)) {
|
||||
secondaryContent()
|
||||
}
|
||||
bottomNavContent()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ListAndNavigation(
|
||||
topBarContent: @Composable () -> Unit,
|
||||
|
||||
Reference in New Issue
Block a user