Skip to content

Commit d004b7a

Browse files
authored
Default transitions for Nav2 (JetBrains#3023)
Remove `NonAndroidDefaultNavTransitions` and unify platform-specific `DefaultNavTransitions` implementations Fixes [CMP-9920](https://youtrack.jetbrains.com/issue/CMP-9920) Correctly implement DefaultNavTransitions predictive pop transitions for iOS ## Release Notes ### Features - Navigation - Improved iOS specific default navigation transactions in Nav2. - Web and Desktop default navigation transactions are set to `None` in Nav2.
1 parent d813291 commit d004b7a

8 files changed

Lines changed: 209 additions & 220 deletions

File tree

navigation/navigation-compose/build.gradle

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,13 @@ androidXMultiplatform {
4444
commonMain.dependencies {
4545
api(project(":navigation:navigation-runtime"))
4646
api(project(":navigation:navigation-common"))
47-
api("org.jetbrains.compose.animation:animation:1.8.2")
48-
api("org.jetbrains.compose.runtime:runtime:1.8.2")
49-
api("org.jetbrains.compose.runtime:runtime-saveable:1.8.2")
50-
api("org.jetbrains.compose.ui:ui:1.8.2")
51-
api("org.jetbrains.compose.animation:animation:1.8.2")
52-
implementation("org.jetbrains.compose.animation:animation-core:1.8.2")
53-
implementation("org.jetbrains.compose.foundation:foundation-layout:1.8.2")
47+
api("org.jetbrains.compose.animation:animation:1.10.0")
48+
api("org.jetbrains.compose.runtime:runtime:1.10.0")
49+
api("org.jetbrains.compose.runtime:runtime-saveable:1.10.0")
50+
api("org.jetbrains.compose.ui:ui:1.10.0")
51+
api("org.jetbrains.compose.animation:animation:1.10.0")
52+
implementation("org.jetbrains.compose.animation:animation-core:1.10.0")
53+
implementation("org.jetbrains.compose.foundation:foundation-layout:1.10.0")
5454
implementation("androidx.collection:collection:1.5.0")
5555
implementation(project(":lifecycle:lifecycle-common"))
5656
implementation(project(":lifecycle:lifecycle-runtime-compose"))

navigation/navigation-compose/src/commonTest/kotlin/androidx/navigation/compose/NavHostTest.kt

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,16 @@
1616

1717
package androidx.navigation.compose
1818

19+
import androidx.compose.animation.AnimatedContentTransitionScope
20+
import androidx.compose.animation.EnterTransition
21+
import androidx.compose.animation.ExitTransition
22+
import androidx.compose.animation.SizeTransform
1923
import androidx.compose.animation.core.AnimationConstants.DefaultDurationMillis
24+
import androidx.compose.animation.core.spring
25+
import androidx.compose.animation.core.tween
26+
import androidx.compose.animation.fadeIn
27+
import androidx.compose.animation.fadeOut
28+
import androidx.compose.animation.scaleOut
2029
import androidx.compose.foundation.layout.Column
2130
import androidx.compose.foundation.layout.fillMaxSize
2231
import androidx.compose.foundation.text.BasicText
@@ -54,6 +63,7 @@ import androidx.lifecycle.testing.TestLifecycleOwner
5463
import androidx.lifecycle.viewmodel.CreationExtras
5564
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
5665
import androidx.lifecycle.viewmodel.compose.viewModel
66+
import androidx.navigation.NavBackStackEntry
5767
import androidx.navigation.NavGraph
5868
import androidx.navigation.NavGraph.Companion.findStartDestination
5969
import androidx.navigation.NavHostController
@@ -66,6 +76,7 @@ import androidx.savedstate.SavedState
6676
import androidx.testutils.TestNavigator
6777
import androidx.testutils.test
6878
import kotlin.reflect.KClass
79+
import kotlin.test.Ignore
6980
import kotlin.test.Test
7081

7182
@OptIn(ExperimentalTestApi::class)
@@ -849,7 +860,14 @@ class NavHostTest {
849860

850861
setContent {
851862
navController = rememberNavController()
852-
NavHost(navController, startDestination = first) {
863+
NavHost(
864+
navController = navController,
865+
enterTransition = TestNavTransitions.enterTransition,
866+
exitTransition = TestNavTransitions.exitTransition,
867+
popEnterTransition = TestNavTransitions.enterTransition,
868+
popExitTransition = TestNavTransitions.exitTransition,
869+
startDestination = first
870+
) {
853871
composable(first) { BasicText(first) }
854872
composable(second) { BasicText(second) }
855873
}
@@ -1302,3 +1320,18 @@ private class TestViewModelStoreOwnerWithDefaults(
13021320
override val defaultViewModelProviderFactory: ViewModelProvider.Factory = TestViewModelFactory(),
13031321
override val defaultViewModelCreationExtras: CreationExtras = CreationExtras.Empty,
13041322
) : ViewModelStoreOwner, HasDefaultViewModelProviderFactory
1323+
1324+
private object TestNavTransitions {
1325+
1326+
val enterTransition:
1327+
AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition =
1328+
{
1329+
fadeIn(animationSpec = tween(700))
1330+
}
1331+
1332+
val exitTransition:
1333+
AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition =
1334+
{
1335+
fadeOut(animationSpec = tween(700))
1336+
}
1337+
}

navigation/navigation-compose/src/desktopMain/kotlin/androidx/navigation/compose/DefaultNavTransitions.desktop.kt

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,30 +23,36 @@ import androidx.compose.animation.EnterTransition
2323
import androidx.compose.animation.ExitTransition
2424
import androidx.compose.animation.SizeTransform
2525
import androidx.navigation.NavBackStackEntry
26-
import androidx.navigation.compose.internal.NonAndroidDefaultNavTransitions
2726

2827
public actual object DefaultNavTransitions {
29-
public actual val enterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition
30-
get() = NonAndroidDefaultNavTransitions.enterTransition
28+
public actual val enterTransition:
29+
AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition =
30+
{ EnterTransition.None }
3131

32-
public actual val exitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition
33-
get() = NonAndroidDefaultNavTransitions.exitTransition
32+
public actual val exitTransition:
33+
AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition =
34+
{ ExitTransition.None }
3435

3536
public actual val predictivePopEnterTransition:
36-
AnimatedContentTransitionScope<NavBackStackEntry>.(Int) -> EnterTransition
37-
get() = NonAndroidDefaultNavTransitions.predictivePopEnterTransition
37+
AnimatedContentTransitionScope<NavBackStackEntry>.(Int) -> EnterTransition =
38+
{ EnterTransition.None }
3839

3940
public actual val predictivePopExitTransition:
40-
AnimatedContentTransitionScope<NavBackStackEntry>.(Int) -> ExitTransition
41-
get() = NonAndroidDefaultNavTransitions.predictivePopExitTransition
41+
AnimatedContentTransitionScope<NavBackStackEntry>.(Int) -> ExitTransition =
42+
{ ExitTransition.None }
4243

4344

44-
public actual val sizeTransform: (AnimatedContentTransitionScope<NavBackStackEntry>.() -> SizeTransform?)?
45-
get() = NonAndroidDefaultNavTransitions.sizeTransform
45+
public actual val sizeTransform:
46+
(AnimatedContentTransitionScope<NavBackStackEntry>.() -> SizeTransform?)? =
47+
null
4648

47-
public actual fun popEnterTransition(enterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition): AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition =
48-
NonAndroidDefaultNavTransitions.popEnterTransition(enterTransition)
49+
public actual fun popEnterTransition(
50+
enterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition
51+
): AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition =
52+
enterTransition
4953

50-
public actual fun popExitTransition(exitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition): AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition =
51-
NonAndroidDefaultNavTransitions.popExitTransition(exitTransition)
54+
public actual fun popExitTransition(
55+
exitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition
56+
): AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition =
57+
exitTransition
5258
}

navigation/navigation-compose/src/iosMain/kotlin/androidx/navigation/compose/DefaultNavTransitions.ios.kt

Lines changed: 105 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,38 +19,121 @@ package androidx.navigation.compose
1919
import androidx.compose.animation.AnimatedContentTransitionScope
2020
import androidx.compose.animation.EnterTransition
2121
import androidx.compose.animation.ExitTransition
22+
import androidx.compose.animation.ExperimentalAnimationApi
2223
import androidx.compose.animation.SizeTransform
24+
import androidx.compose.animation.core.CubicBezierEasing
25+
import androidx.compose.animation.core.LinearEasing
26+
import androidx.compose.animation.core.tween
27+
import androidx.compose.animation.unveilIn
28+
import androidx.compose.animation.veilOut
2329
import androidx.navigation.NavBackStackEntry
24-
import androidx.navigation.compose.internal.NonAndroidDefaultNavTransitions
25-
import androidx.navigation.compose.internal.NonAndroidDefaultNavTransitions.enterTransition
26-
import androidx.navigation.compose.internal.NonAndroidDefaultNavTransitions.exitTransition
27-
import androidx.navigation.compose.internal.NonAndroidDefaultNavTransitions.predictivePopEnterTransition
28-
import androidx.navigation.compose.internal.NonAndroidDefaultNavTransitions.predictivePopExitTransition
29-
import androidx.navigation.compose.internal.NonAndroidDefaultNavTransitions.sizeTransform
30-
31-
// TODO: https://youtrack.jetbrains.com/issue/CMP-9920/Correctly-implement-DefaultNavTransitions-predictive-pop-transitions-for-iOS
30+
import androidx.navigationevent.NavigationEvent.Companion.EDGE_LEFT
31+
32+
private const val DEFAULT_TRANSITION_DURATION_MILLISECOND = 500
33+
private val IosTransitionEasing = CubicBezierEasing(0.2833f, 0.99f, 0.31833f, 0.99f)
34+
35+
@OptIn(ExperimentalAnimationApi::class)
3236
public actual object DefaultNavTransitions {
33-
public actual val enterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition
34-
get() = NonAndroidDefaultNavTransitions.enterTransition
37+
public actual val enterTransition:
38+
AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition = {
39+
slideIntoContainer(
40+
towards = AnimatedContentTransitionScope.SlideDirection.Start,
41+
animationSpec = tween(
42+
DEFAULT_TRANSITION_DURATION_MILLISECOND,
43+
easing = IosTransitionEasing
44+
),
45+
)
46+
}
3547

36-
public actual val exitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition
37-
get() = NonAndroidDefaultNavTransitions.exitTransition
48+
public actual val exitTransition:
49+
AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition = {
50+
slideOutOfContainer(
51+
towards = AnimatedContentTransitionScope.SlideDirection.Start,
52+
targetOffset = { it / 4 },
53+
animationSpec = tween(
54+
DEFAULT_TRANSITION_DURATION_MILLISECOND,
55+
easing = IosTransitionEasing
56+
),
57+
) + veilOut(
58+
animationSpec = tween(
59+
DEFAULT_TRANSITION_DURATION_MILLISECOND,
60+
easing = IosTransitionEasing
61+
),
62+
)
63+
}
3864

3965
public actual val predictivePopEnterTransition:
40-
AnimatedContentTransitionScope<NavBackStackEntry>.(Int) -> EnterTransition
41-
get() = NonAndroidDefaultNavTransitions.predictivePopEnterTransition
66+
AnimatedContentTransitionScope<NavBackStackEntry>.(Int) -> EnterTransition =
67+
{ edge ->
68+
val towards = if (edge == EDGE_LEFT) {
69+
AnimatedContentTransitionScope.SlideDirection.Right
70+
} else {
71+
AnimatedContentTransitionScope.SlideDirection.Left
72+
}
73+
slideIntoContainer(
74+
towards = towards,
75+
initialOffset = { it / 4 },
76+
animationSpec = tween(
77+
DEFAULT_TRANSITION_DURATION_MILLISECOND,
78+
easing = LinearEasing
79+
),
80+
) + unveilIn(
81+
animationSpec = tween(
82+
DEFAULT_TRANSITION_DURATION_MILLISECOND,
83+
easing = LinearEasing
84+
),
85+
)
86+
}
4287

4388
public actual val predictivePopExitTransition:
44-
AnimatedContentTransitionScope<NavBackStackEntry>.(Int) -> ExitTransition
45-
get() = NonAndroidDefaultNavTransitions.predictivePopExitTransition
89+
AnimatedContentTransitionScope<NavBackStackEntry>.(Int) -> ExitTransition =
90+
{ edge ->
91+
val towards = if (edge == EDGE_LEFT) {
92+
AnimatedContentTransitionScope.SlideDirection.Right
93+
} else {
94+
AnimatedContentTransitionScope.SlideDirection.Left
95+
}
96+
slideOutOfContainer(
97+
towards = towards,
98+
animationSpec = tween(
99+
DEFAULT_TRANSITION_DURATION_MILLISECOND,
100+
easing = LinearEasing
101+
),
102+
)
103+
}
46104

47105

48-
public actual val sizeTransform: (AnimatedContentTransitionScope<NavBackStackEntry>.() -> SizeTransform?)?
49-
get() = NonAndroidDefaultNavTransitions.sizeTransform
106+
public actual val sizeTransform:
107+
(AnimatedContentTransitionScope<NavBackStackEntry>.() -> SizeTransform?)? =
108+
null
50109

51-
public actual fun popEnterTransition(enterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition): AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition =
52-
NonAndroidDefaultNavTransitions.popEnterTransition(enterTransition)
110+
public actual fun popEnterTransition(
111+
enterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition
112+
): AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition = {
113+
slideIntoContainer(
114+
towards = AnimatedContentTransitionScope.SlideDirection.End,
115+
initialOffset = { it / 4 },
116+
animationSpec = tween(
117+
DEFAULT_TRANSITION_DURATION_MILLISECOND,
118+
easing = IosTransitionEasing
119+
),
120+
) + unveilIn(
121+
animationSpec = tween(
122+
DEFAULT_TRANSITION_DURATION_MILLISECOND,
123+
easing = IosTransitionEasing
124+
)
125+
)
126+
}
53127

54-
public actual fun popExitTransition(exitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition): AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition =
55-
NonAndroidDefaultNavTransitions.popExitTransition(exitTransition)
128+
public actual fun popExitTransition(
129+
exitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition
130+
): AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition = {
131+
slideOutOfContainer(
132+
towards = AnimatedContentTransitionScope.SlideDirection.End,
133+
animationSpec = tween(
134+
DEFAULT_TRANSITION_DURATION_MILLISECOND,
135+
easing = IosTransitionEasing
136+
),
137+
)
138+
}
56139
}

navigation/navigation-compose/src/macosMain/kotlin/androidx/navigation/compose/DefaultNavTransitions.macos.kt

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,30 +21,36 @@ import androidx.compose.animation.EnterTransition
2121
import androidx.compose.animation.ExitTransition
2222
import androidx.compose.animation.SizeTransform
2323
import androidx.navigation.NavBackStackEntry
24-
import androidx.navigation.compose.internal.NonAndroidDefaultNavTransitions
2524

2625
public actual object DefaultNavTransitions {
27-
public actual val enterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition
28-
get() = NonAndroidDefaultNavTransitions.enterTransition
26+
public actual val enterTransition:
27+
AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition =
28+
{ EnterTransition.None }
2929

30-
public actual val exitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition
31-
get() = NonAndroidDefaultNavTransitions.exitTransition
30+
public actual val exitTransition:
31+
AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition =
32+
{ ExitTransition.None }
3233

3334
public actual val predictivePopEnterTransition:
34-
AnimatedContentTransitionScope<NavBackStackEntry>.(Int) -> EnterTransition
35-
get() = NonAndroidDefaultNavTransitions.predictivePopEnterTransition
35+
AnimatedContentTransitionScope<NavBackStackEntry>.(Int) -> EnterTransition =
36+
{ EnterTransition.None }
3637

3738
public actual val predictivePopExitTransition:
38-
AnimatedContentTransitionScope<NavBackStackEntry>.(Int) -> ExitTransition
39-
get() = NonAndroidDefaultNavTransitions.predictivePopExitTransition
39+
AnimatedContentTransitionScope<NavBackStackEntry>.(Int) -> ExitTransition =
40+
{ ExitTransition.None }
4041

4142

42-
public actual val sizeTransform: (AnimatedContentTransitionScope<NavBackStackEntry>.() -> SizeTransform?)?
43-
get() = NonAndroidDefaultNavTransitions.sizeTransform
43+
public actual val sizeTransform:
44+
(AnimatedContentTransitionScope<NavBackStackEntry>.() -> SizeTransform?)? =
45+
null
4446

45-
public actual fun popEnterTransition(enterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition): AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition =
46-
NonAndroidDefaultNavTransitions.popEnterTransition(enterTransition)
47+
public actual fun popEnterTransition(
48+
enterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition
49+
): AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition =
50+
enterTransition
4751

48-
public actual fun popExitTransition(exitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition): AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition =
49-
NonAndroidDefaultNavTransitions.popExitTransition(exitTransition)
52+
public actual fun popExitTransition(
53+
exitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition
54+
): AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition =
55+
exitTransition
5056
}

0 commit comments

Comments
 (0)