From d9dd15eff39f2010896a9c5a628c634be50cba59 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Thu, 9 Jul 2026 09:47:26 -0400 Subject: [PATCH] Use idiomatic nav3 transition patterns. --- .../thoughtcrime/securesms/MainActivity.kt | 25 +- .../QuickTransferOldDeviceNavigation.kt | 6 +- .../local/RestoreLocalBackupNavDisplay.kt | 2 +- .../core/ui/navigation/TransitionSpecs.kt | 70 ++- .../thoughtcrime/video/app/MainActivity.kt | 6 +- .../registration/RegistrationNavigation.kt | 28 +- gradle/libs.versions.toml | 2 +- gradle/verification-metadata.xml | 528 +++++++++++++++--- 8 files changed, 505 insertions(+), 162 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/MainActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/MainActivity.kt index 7b732160f0..0ccf396b3c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MainActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/MainActivity.kt @@ -76,6 +76,7 @@ import androidx.lifecycle.createSavedStateHandle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator +import androidx.navigation3.runtime.NavKey import androidx.navigation3.runtime.entryProvider import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator import androidx.navigation3.ui.NavDisplay @@ -673,23 +674,23 @@ class MainActivity : primaryContent = { when (mainNavigationState.currentListLocation) { MainNavigationListLocation.CHATS, MainNavigationListLocation.ARCHIVE -> { - NavDisplay( + NavDisplay( backStack = mainNavigationViewModel.chatsBackStackEntries, onBack = { mainNavigationViewModel.popChatsDetailLocation() }, - transitionSpec = TransitionSpecs.HorizontalSlide.transitionSpec, - popTransitionSpec = TransitionSpecs.HorizontalSlide.popTransitionSpec, - predictivePopTransitionSpec = TransitionSpecs.HorizontalSlide.predictivePopTransitionSpec, + transitionSpec = { TransitionSpecs.HorizontalSlide.transitionSpec }, + popTransitionSpec = { TransitionSpecs.HorizontalSlide.popTransitionSpec }, + predictivePopTransitionSpec = { TransitionSpecs.HorizontalSlide.predictivePopTransitionSpec }, entryProvider = entryProvider { chatsNavEntries(convoTransitionState) } ) } MainNavigationListLocation.CALLS -> { - NavDisplay( + NavDisplay( backStack = mainNavigationViewModel.callsBackStackEntries, onBack = { mainNavigationViewModel.popCallsDetailLocation() }, - transitionSpec = TransitionSpecs.HorizontalSlide.transitionSpec, - popTransitionSpec = TransitionSpecs.HorizontalSlide.popTransitionSpec, - predictivePopTransitionSpec = TransitionSpecs.HorizontalSlide.predictivePopTransitionSpec, + transitionSpec = { TransitionSpecs.HorizontalSlide.transitionSpec }, + popTransitionSpec = { TransitionSpecs.HorizontalSlide.popTransitionSpec }, + predictivePopTransitionSpec = { TransitionSpecs.HorizontalSlide.predictivePopTransitionSpec }, entryDecorators = listOf( rememberSaveableStateHolderNavEntryDecorator(), rememberViewModelStoreNavEntryDecorator() @@ -699,12 +700,12 @@ class MainActivity : } MainNavigationListLocation.STORIES -> { - NavDisplay( + NavDisplay( backStack = mainNavigationViewModel.storiesBackStackEntries, onBack = { mainNavigationViewModel.popStoriesDetailLocation() }, - transitionSpec = TransitionSpecs.HorizontalSlide.transitionSpec, - popTransitionSpec = TransitionSpecs.HorizontalSlide.popTransitionSpec, - predictivePopTransitionSpec = TransitionSpecs.HorizontalSlide.predictivePopTransitionSpec, + transitionSpec = { TransitionSpecs.HorizontalSlide.transitionSpec }, + popTransitionSpec = { TransitionSpecs.HorizontalSlide.popTransitionSpec }, + predictivePopTransitionSpec = { TransitionSpecs.HorizontalSlide.predictivePopTransitionSpec }, entryDecorators = listOf( rememberSaveableStateHolderNavEntryDecorator(), rememberViewModelStoreNavEntryDecorator() diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/olddevice/QuickTransferOldDeviceNavigation.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/olddevice/QuickTransferOldDeviceNavigation.kt index f9b502c761..445f657d79 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/olddevice/QuickTransferOldDeviceNavigation.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/olddevice/QuickTransferOldDeviceNavigation.kt @@ -78,9 +78,9 @@ fun TransferAccountNavHost( entries = entries, onBack = { viewModel.goBack() }, modifier = modifier, - transitionSpec = TransitionSpecs.HorizontalSlide.transitionSpec, - popTransitionSpec = TransitionSpecs.HorizontalSlide.popTransitionSpec, - predictivePopTransitionSpec = TransitionSpecs.HorizontalSlide.predictivePopTransitionSpec + transitionSpec = { TransitionSpecs.HorizontalSlide.transitionSpec }, + popTransitionSpec = { TransitionSpecs.HorizontalSlide.popTransitionSpec }, + predictivePopTransitionSpec = { TransitionSpecs.HorizontalSlide.predictivePopTransitionSpec } ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/ui/restore/local/RestoreLocalBackupNavDisplay.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/ui/restore/local/RestoreLocalBackupNavDisplay.kt index e2af9d70c1..63286cd01d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/ui/restore/local/RestoreLocalBackupNavDisplay.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/ui/restore/local/RestoreLocalBackupNavDisplay.kt @@ -72,7 +72,7 @@ fun RestoreLocalBackupNavDisplay( Box(modifier = Modifier.fillMaxSize()) { NavDisplay( backStack = backstack, - sceneStrategy = bottomSheetStrategy, + sceneStrategies = listOf(bottomSheetStrategy), entryProvider = entryProvider { entry { SelectLocalBackupTypeScreen( diff --git a/core/ui/src/main/java/org/signal/core/ui/navigation/TransitionSpecs.kt b/core/ui/src/main/java/org/signal/core/ui/navigation/TransitionSpecs.kt index a64b01f765..7d43d3487d 100644 --- a/core/ui/src/main/java/org/signal/core/ui/navigation/TransitionSpecs.kt +++ b/core/ui/src/main/java/org/signal/core/ui/navigation/TransitionSpecs.kt @@ -5,7 +5,6 @@ package org.signal.core.ui.navigation -import androidx.compose.animation.AnimatedContentTransitionScope import androidx.compose.animation.ContentTransform import androidx.compose.animation.EnterTransition import androidx.compose.animation.ExitTransition @@ -17,23 +16,52 @@ import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutHorizontally import androidx.compose.animation.slideOutVertically import androidx.compose.animation.togetherWith -import androidx.navigation3.runtime.NavKey -import androidx.navigation3.scene.Scene +import androidx.navigation3.runtime.metadata import androidx.navigation3.ui.NavDisplay -import androidx.navigationevent.NavigationEvent /** - * A collection of [TransitionSpecs] for setting up nav3 navigation. + * A collection of transition specs for setting up nav3 navigation. */ object TransitionSpecs { + interface Transition { + companion object { + val NONE: ContentTransform = EnterTransition.None togetherWith ExitTransition.None + } + + val transitionSpec: ContentTransform get() = NONE + val popTransitionSpec: ContentTransform get() = NONE + val predictivePopTransitionSpec: ContentTransform get() = NONE + + val metadata: Map get() = metadata { + put(NavDisplay.TransitionKey) { + transitionSpec + } + put(NavDisplay.PopTransitionKey) { + popTransitionSpec + } + put(NavDisplay.PredictivePopTransitionKey) { + predictivePopTransitionSpec + } + } + } + + /** + * No enter/exit animation. + */ + object None : Transition { + override val transitionSpec: ContentTransform = Transition.NONE + override val popTransitionSpec: ContentTransform = Transition.NONE + override val predictivePopTransitionSpec: ContentTransform = Transition.NONE + } + /** * Screens slide in from the right and slide out from the left. */ - object HorizontalSlide { + object HorizontalSlide : Transition { private const val DURATION = 200 - val transitionSpec: AnimatedContentTransitionScope>.() -> ContentTransform = { + override val transitionSpec: ContentTransform = ( slideInHorizontally( initialOffsetX = { it }, @@ -46,9 +74,8 @@ object TransitionSpecs { animationSpec = tween(DURATION) ) + fadeOut(animationSpec = tween(DURATION)) ) - } - val popTransitionSpec: AnimatedContentTransitionScope>.() -> ContentTransform = { + override val popTransitionSpec: ContentTransform = ( slideInHorizontally( initialOffsetX = { -it }, @@ -61,9 +88,8 @@ object TransitionSpecs { animationSpec = tween(DURATION) ) + fadeOut(animationSpec = tween(DURATION)) ) - } - val predictivePopTransitionSpec: AnimatedContentTransitionScope>.(@NavigationEvent.SwipeEdge Int) -> ContentTransform = { + override val predictivePopTransitionSpec: ContentTransform = ( slideInHorizontally( initialOffsetX = { -it }, @@ -76,47 +102,33 @@ object TransitionSpecs { animationSpec = tween(DURATION) ) + fadeOut(animationSpec = tween(DURATION)) ) - } } /** * Screens slide in from the bottom and slide out to the bottom, like a sheet. */ - object VerticalSlide { + object VerticalSlide : Transition { private const val DURATION = 300 - val transitionSpec: AnimatedContentTransitionScope>.() -> ContentTransform = { + override val transitionSpec: ContentTransform = slideInVertically( initialOffsetY = { it }, animationSpec = tween(DURATION) ) + fadeIn(animationSpec = tween(DURATION)) togetherWith fadeOut(animationSpec = tween(DURATION)) - } - val popTransitionSpec: AnimatedContentTransitionScope>.() -> ContentTransform = { + override val popTransitionSpec: ContentTransform = fadeIn(animationSpec = tween(DURATION)) togetherWith slideOutVertically( targetOffsetY = { it }, animationSpec = tween(DURATION) ) + fadeOut(animationSpec = tween(DURATION)) - } - val predictivePopTransitionSpec: AnimatedContentTransitionScope>.(@NavigationEvent.SwipeEdge Int) -> ContentTransform = { + override val predictivePopTransitionSpec: ContentTransform = fadeIn(animationSpec = tween(DURATION)) togetherWith slideOutVertically( targetOffsetY = { it }, animationSpec = tween(DURATION) ) + fadeOut(animationSpec = tween(DURATION)) - } - } - - /** - * No enter/exit animation. - */ - object None { - val metadata: Map = - NavDisplay.transitionSpec { EnterTransition.None togetherWith ExitTransition.None } + - NavDisplay.popTransitionSpec { EnterTransition.None togetherWith ExitTransition.None } + - NavDisplay.predictivePopTransitionSpec { EnterTransition.None togetherWith ExitTransition.None } } } diff --git a/demo/video/src/main/java/org/thoughtcrime/video/app/MainActivity.kt b/demo/video/src/main/java/org/thoughtcrime/video/app/MainActivity.kt index 2792fef620..880572e774 100644 --- a/demo/video/src/main/java/org/thoughtcrime/video/app/MainActivity.kt +++ b/demo/video/src/main/java/org/thoughtcrime/video/app/MainActivity.kt @@ -93,9 +93,9 @@ private fun TranscodeApp() { NavDisplay( backStack = backStack, - transitionSpec = TransitionSpecs.HorizontalSlide.transitionSpec, - popTransitionSpec = TransitionSpecs.HorizontalSlide.popTransitionSpec, - predictivePopTransitionSpec = TransitionSpecs.HorizontalSlide.predictivePopTransitionSpec, + transitionSpec = { TransitionSpecs.HorizontalSlide.transitionSpec }, + popTransitionSpec = { TransitionSpecs.HorizontalSlide.popTransitionSpec }, + predictivePopTransitionSpec = { TransitionSpecs.HorizontalSlide.predictivePopTransitionSpec }, entryProvider = entryProvider { addEntryProvider( key = Screen.Home, diff --git a/feature/registration/src/main/java/org/signal/registration/RegistrationNavigation.kt b/feature/registration/src/main/java/org/signal/registration/RegistrationNavigation.kt index 06e4c7095c..21fa496315 100644 --- a/feature/registration/src/main/java/org/signal/registration/RegistrationNavigation.kt +++ b/feature/registration/src/main/java/org/signal/registration/RegistrationNavigation.kt @@ -335,39 +335,23 @@ fun RegistrationNavHost( entries = entries, onBack = { viewModel.onEvent(RegistrationFlowEvent.NavigateBack) }, modifier = modifier, - transitionSpec = { - if (targetState.key is RegistrationRoute.CountryCodePicker) { - TransitionSpecs.VerticalSlide.transitionSpec.invoke(this) - } else { - TransitionSpecs.HorizontalSlide.transitionSpec.invoke(this) - } - }, + transitionSpec = { TransitionSpecs.HorizontalSlide.transitionSpec }, popTransitionSpec = { when { - initialState.key is RegistrationRoute.CountryCodePicker -> { - TransitionSpecs.VerticalSlide.popTransitionSpec.invoke(this) - } - initialState.key == RegistrationRoute.EnterAepForLocalBackup.toString() || initialState.key == RegistrationRoute.EnterAepForRemoteBackupPreRegistration.toString() -> { - TransitionSpecs.HorizontalSlide.transitionSpec.invoke(this) + TransitionSpecs.HorizontalSlide.transitionSpec } initialState.key == RegistrationRoute.LocalBackupRestore.toString() && targetState.key == RegistrationRoute.PhoneNumberEntry.toString() -> { - TransitionSpecs.HorizontalSlide.transitionSpec.invoke(this) + TransitionSpecs.HorizontalSlide.transitionSpec } else -> { - TransitionSpecs.HorizontalSlide.popTransitionSpec.invoke(this) + TransitionSpecs.HorizontalSlide.popTransitionSpec } } }, - predictivePopTransitionSpec = { - if (initialState.key is RegistrationRoute.CountryCodePicker) { - TransitionSpecs.VerticalSlide.predictivePopTransitionSpec.invoke(this, it) - } else { - TransitionSpecs.HorizontalSlide.predictivePopTransitionSpec.invoke(this, it) - } - } + predictivePopTransitionSpec = { TransitionSpecs.HorizontalSlide.predictivePopTransitionSpec } ) } @@ -533,7 +517,7 @@ private fun EntryProviderScope.navigationEntries( } // -- Country Code Picker - entry { key -> + entry(metadata = TransitionSpecs.VerticalSlide.metadata) { key -> val viewModel: CountryCodePickerViewModel = viewModel( factory = CountryCodePickerViewModel.Factory( repository = CountryCodePickerRepository(), diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 87a423a5ff..a19ae27c3a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -23,7 +23,7 @@ androidx-lifecycle = "2.10.0" androidx-lifecycle-navigation3 = "2.10.0" androidx-media3 = "1.9.1" androidx-navigation = "2.9.8" -androidx-navigation3-core = "1.0.0" +androidx-navigation3-core = "1.1.4" androidx-core-telecom = "1.1.0-alpha04" androidx-window = "1.3.0" glide = "4.15.1" diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index f3e0fcf440..21eca37dbe 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -581,6 +581,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -610,6 +618,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -684,6 +700,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -884,6 +908,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -892,6 +921,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -910,6 +947,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -918,6 +963,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -926,6 +979,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -934,6 +995,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -942,6 +1011,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -950,6 +1027,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -958,6 +1043,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -966,6 +1059,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -974,6 +1075,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -982,6 +1091,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -990,6 +1107,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -998,6 +1123,19 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + @@ -1006,6 +1144,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -1024,6 +1170,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -1032,11 +1186,27 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + @@ -1045,6 +1215,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -1081,6 +1256,22 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + @@ -1089,6 +1280,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -1105,6 +1301,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -1113,6 +1317,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -1121,6 +1333,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -1137,6 +1354,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -1161,6 +1386,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -1169,6 +1399,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -1177,6 +1415,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -1185,6 +1428,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -1193,6 +1444,22 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + @@ -1201,6 +1468,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -1217,6 +1489,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -1233,6 +1513,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -1241,6 +1526,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -1249,6 +1542,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -1257,6 +1555,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -1281,6 +1587,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -1289,6 +1600,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -1297,6 +1616,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -1305,6 +1632,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -1321,6 +1653,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -1329,6 +1669,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -1337,6 +1685,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + @@ -1353,6 +1706,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -2839,52 +3200,52 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + - - + + - - - + + + - + - - - + + + - - + + - - - + + + - - + + - - - + + + - + - - - + + + - - + + @@ -2895,6 +3256,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -2903,6 +3272,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -2911,6 +3288,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -2919,20 +3304,33 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - + + + + + + + + + + + + + + + + - - - + + + - - + + @@ -6428,61 +6826,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -7422,6 +7765,9 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + +