From a071acb6eec3ee6835daabc32a9b0539edfd52da Mon Sep 17 00:00:00 2001 From: Jay Ohms Date: Fri, 5 Jan 2024 12:15:57 -0500 Subject: [PATCH] Initial work to provide better transition animations between destinations --- .../hotwire/turbo/demo/base/NavDestination.kt | 26 ------------- turbo/build.gradle | 10 ++--- .../main/assets/json/test-configuration.json | 8 ++++ .../hotwire/turbo/nav/TurboNavDestination.kt | 22 ----------- .../dev/hotwire/turbo/nav/TurboNavRule.kt | 13 +++---- .../dev/hotwire/turbo/nav/TurboNavigator.kt | 11 ------ .../dev/hotwire/turbo/nav/TurboNavRuleTest.kt | 37 ++++++++++++++----- 7 files changed, 45 insertions(+), 82 deletions(-) diff --git a/demo/src/main/kotlin/dev/hotwire/turbo/demo/base/NavDestination.kt b/demo/src/main/kotlin/dev/hotwire/turbo/demo/base/NavDestination.kt index 857e2a37..e5e0688a 100644 --- a/demo/src/main/kotlin/dev/hotwire/turbo/demo/base/NavDestination.kt +++ b/demo/src/main/kotlin/dev/hotwire/turbo/demo/base/NavDestination.kt @@ -5,15 +5,10 @@ import android.view.MenuItem import androidx.browser.customtabs.CustomTabColorSchemeParams import androidx.browser.customtabs.CustomTabsIntent import androidx.browser.customtabs.CustomTabsIntent.SHARE_STATE_ON -import androidx.navigation.NavOptions -import androidx.navigation.navOptions import dev.hotwire.strada.BridgeDestination -import dev.hotwire.turbo.config.TurboPathConfigurationProperties -import dev.hotwire.turbo.config.context import dev.hotwire.turbo.demo.R import dev.hotwire.turbo.demo.util.BASE_URL import dev.hotwire.turbo.nav.TurboNavDestination -import dev.hotwire.turbo.nav.TurboNavPresentationContext.MODAL interface NavDestination : TurboNavDestination, BridgeDestination { val menuProgress: MenuItem? @@ -29,16 +24,6 @@ interface NavDestination : TurboNavDestination, BridgeDestination { } } - override fun getNavigationOptions( - newLocation: String, - newPathProperties: TurboPathConfigurationProperties - ): NavOptions { - return when (newPathProperties.context) { - MODAL -> slideAnimation() - else -> super.getNavigationOptions(newLocation, newPathProperties) - } - } - override fun bridgeWebViewIsReady(): Boolean { return session.isReady } @@ -63,15 +48,4 @@ interface NavDestination : TurboNavDestination, BridgeDestination { .build() .launchUrl(context, Uri.parse(location)) } - - private fun slideAnimation(): NavOptions { - return navOptions { - anim { - enter = R.anim.nav_slide_enter - exit = R.anim.nav_slide_exit - popEnter = R.anim.nav_slide_pop_enter - popExit = R.anim.nav_slide_pop_exit - } - } - } } diff --git a/turbo/build.gradle b/turbo/build.gradle index b15bc5a1..b8abe7c0 100644 --- a/turbo/build.gradle +++ b/turbo/build.gradle @@ -73,7 +73,7 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'org.jetbrains.kotlin:kotlin-reflect:1.9.10' - implementation 'com.google.android.material:material:1.10.0' + implementation 'com.google.android.material:material:1.11.0' // AndroidX implementation 'androidx.constraintlayout:constraintlayout:2.1.4' @@ -95,14 +95,14 @@ dependencies { api 'androidx.appcompat:appcompat:1.6.1' api 'androidx.core:core-ktx:1.12.0' api 'androidx.webkit:webkit:1.8.0' - api 'androidx.activity:activity-ktx:1.8.1' + api 'androidx.activity:activity-ktx:1.8.2' api 'androidx.fragment:fragment-ktx:1.6.2' - api 'androidx.navigation:navigation-fragment-ktx:2.7.5' - api 'androidx.navigation:navigation-ui-ktx:2.7.5' + api 'androidx.navigation:navigation-fragment-ktx:2.7.6' + api 'androidx.navigation:navigation-ui-ktx:2.7.6' // Tests testImplementation 'androidx.test:core:1.5.0' // Robolectric - testImplementation 'androidx.navigation:navigation-testing:2.7.5' + testImplementation 'androidx.navigation:navigation-testing:2.7.6' testImplementation 'androidx.arch.core:core-testing:2.2.0' testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3' testImplementation 'org.assertj:assertj-core:3.24.2' diff --git a/turbo/src/main/assets/json/test-configuration.json b/turbo/src/main/assets/json/test-configuration.json index 3cbeabaf..99c2ddaa 100644 --- a/turbo/src/main/assets/json/test-configuration.json +++ b/turbo/src/main/assets/json/test-configuration.json @@ -76,6 +76,14 @@ "presentation": "none" } }, + { + "patterns": [ + "/custom/replace-root" + ], + "properties": { + "presentation": "replace_root" + } + }, { "patterns": [ "/custom/modal" diff --git a/turbo/src/main/kotlin/dev/hotwire/turbo/nav/TurboNavDestination.kt b/turbo/src/main/kotlin/dev/hotwire/turbo/nav/TurboNavDestination.kt index 30eb5b99..a64f2b02 100644 --- a/turbo/src/main/kotlin/dev/hotwire/turbo/nav/TurboNavDestination.kt +++ b/turbo/src/main/kotlin/dev/hotwire/turbo/nav/TurboNavDestination.kt @@ -8,11 +8,8 @@ import androidx.appcompat.widget.Toolbar import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.navigation.NavController -import androidx.navigation.NavOptions import androidx.navigation.fragment.FragmentNavigator import androidx.navigation.fragment.findNavController -import androidx.navigation.navOptions -import androidx.navigation.ui.R import dev.hotwire.turbo.config.TurboPathConfiguration import dev.hotwire.turbo.config.TurboPathConfigurationProperties import dev.hotwire.turbo.delegates.TurboFragmentDelegate @@ -157,25 +154,6 @@ interface TurboNavDestination { navigator.navigate(location, options, bundle, extras) } - /** - * Gets the default set of navigation options (basic enter/exit animations) for the Android - * Navigation component to use to execute a navigation event. This can be overridden if - * you'd like to provide your own. - */ - fun getNavigationOptions( - newLocation: String, - newPathProperties: TurboPathConfigurationProperties - ): NavOptions { - return navOptions { - anim { - enter = R.anim.nav_default_enter_anim - exit = R.anim.nav_default_exit_anim - popEnter = R.anim.nav_default_pop_enter_anim - popExit = R.anim.nav_default_pop_exit_anim - } - } - } - /** * Navigates up to the previous destination. See [NavController.navigateUp] for * more details. diff --git a/turbo/src/main/kotlin/dev/hotwire/turbo/nav/TurboNavRule.kt b/turbo/src/main/kotlin/dev/hotwire/turbo/nav/TurboNavRule.kt index 7a44db0d..5e1ab2ea 100644 --- a/turbo/src/main/kotlin/dev/hotwire/turbo/nav/TurboNavRule.kt +++ b/turbo/src/main/kotlin/dev/hotwire/turbo/nav/TurboNavRule.kt @@ -19,7 +19,6 @@ internal class TurboNavRule( location: String, visitOptions: TurboVisitOptions, bundle: Bundle?, - navOptions: NavOptions, extras: FragmentNavigator.Extras?, pathConfiguration: TurboPathConfiguration, val controller: NavController @@ -46,7 +45,7 @@ internal class TurboNavRule( val newFallbackUri = newProperties.fallbackUri val newDestination = controller.destinationFor(newDestinationUri) val newFallbackDestination = controller.destinationFor(newFallbackUri) - val newNavOptions = newNavOptions(navOptions) + val newNavOptions = newNavOptions() init { verifyNavRules() @@ -76,15 +75,13 @@ internal class TurboNavRule( } } - private fun newNavOptions(navOptions: NavOptions): NavOptions { - // Use separate NavOptions if we need to pop up to the new root destination - if (newPresentation == TurboNavPresentation.REPLACE_ROOT && newDestination != null) { - return navOptions { + private fun newNavOptions(): NavOptions { + return navOptions { + if (newPresentation == TurboNavPresentation.REPLACE_ROOT && newDestination != null) { + // Pop up to the new root destination popUpTo(newDestination.id) { inclusive = true } } } - - return navOptions } private fun newNavigationMode(): TurboNavMode { diff --git a/turbo/src/main/kotlin/dev/hotwire/turbo/nav/TurboNavigator.kt b/turbo/src/main/kotlin/dev/hotwire/turbo/nav/TurboNavigator.kt index 5a814aba..77b57de7 100644 --- a/turbo/src/main/kotlin/dev/hotwire/turbo/nav/TurboNavigator.kt +++ b/turbo/src/main/kotlin/dev/hotwire/turbo/nav/TurboNavigator.kt @@ -4,7 +4,6 @@ import android.os.Bundle import androidx.fragment.app.DialogFragment import androidx.navigation.NavBackStackEntry import androidx.navigation.NavController -import androidx.navigation.NavOptions import androidx.navigation.fragment.FragmentNavigator import androidx.navigation.fragment.findNavController import dev.hotwire.turbo.util.location @@ -47,7 +46,6 @@ internal class TurboNavigator(private val navDestination: TurboNavDestination) { location = location, visitOptions = options, bundle = bundle, - navOptions = navOptions(location), extras = extras, pathConfiguration = session.pathConfiguration, controller = currentControllerForLocation(location) @@ -270,15 +268,6 @@ internal class TurboNavigator(private val navDestination: TurboNavDestination) { return shouldNavigate } - private fun navOptions(location: String): NavOptions { - val properties = session.pathConfiguration.properties(location) - - return navDestination.getNavigationOptions( - newLocation = location, - newPathProperties = properties - ) - } - private val NavBackStackEntry?.isModalContext: Boolean get() { val context = this?.arguments?.getSerializable("presentation-context") diff --git a/turbo/src/test/kotlin/dev/hotwire/turbo/nav/TurboNavRuleTest.kt b/turbo/src/test/kotlin/dev/hotwire/turbo/nav/TurboNavRuleTest.kt index cba10de6..bf42ed63 100644 --- a/turbo/src/test/kotlin/dev/hotwire/turbo/nav/TurboNavRuleTest.kt +++ b/turbo/src/test/kotlin/dev/hotwire/turbo/nav/TurboNavRuleTest.kt @@ -11,7 +11,6 @@ import androidx.navigation.createGraph import androidx.navigation.navOptions import androidx.navigation.testing.TestNavHostController import androidx.test.core.app.ApplicationProvider -import androidx.navigation.ui.R import dev.hotwire.turbo.config.TurboPathConfiguration import dev.hotwire.turbo.visit.TurboVisitOptions import org.assertj.core.api.Assertions.assertThat @@ -37,6 +36,7 @@ class TurboNavRuleTest { private val recedeUrl = "https://hotwired.dev/custom/recede" private val refreshUrl = "https://hotwired.dev/custom/refresh" private val resumeUrl = "https://hotwired.dev/custom/resume" + private val replaceRootUrl = "https://hotwired.dev/custom/replace-root" private val modalRootUrl = "https://hotwired.dev/custom/modal" private val filterUrl = "https://hotwired.dev/feature?filter=true" private val customUrl = "https://hotwired.dev/custom" @@ -51,14 +51,7 @@ class TurboNavRuleTest { private val webHomeUri = Uri.parse("turbo://fragment/web/home") private val extras = null - private val navOptions = navOptions { - anim { - enter = R.anim.nav_default_enter_anim - exit = R.anim.nav_default_exit_anim - popEnter = R.anim.nav_default_pop_enter_anim - popExit = R.anim.nav_default_pop_exit_anim - } - } + private val navOptions = navOptions {} @Before fun setup() { @@ -113,6 +106,30 @@ class TurboNavRuleTest { assertThat(rule.newNavOptions).isEqualTo(navOptions) } + @Test + fun `navigate replacing the root`() { + val rule = getNavigatorRule(replaceRootUrl) + + // Current destination + assertThat(rule.previousLocation).isNull() + assertThat(rule.currentLocation).isEqualTo(homeUrl) + assertThat(rule.currentPresentationContext).isEqualTo(TurboNavPresentationContext.DEFAULT) + assertThat(rule.isAtStartDestination).isTrue() + + // New destination + assertThat(rule.newLocation).isEqualTo(replaceRootUrl) + assertThat(rule.newPresentationContext).isEqualTo(TurboNavPresentationContext.DEFAULT) + assertThat(rule.newPresentation).isEqualTo(TurboNavPresentation.REPLACE_ROOT) + assertThat(rule.newQueryStringPresentation).isEqualTo(TurboNavQueryStringPresentation.DEFAULT) + assertThat(rule.newNavigationMode).isEqualTo(TurboNavMode.IN_CONTEXT) + assertThat(rule.newModalResult).isNull() + assertThat(rule.newDestinationUri).isEqualTo(webUri) + assertThat(rule.newDestination).isNotNull() + assertThat(rule.newNavOptions).isEqualTo(navOptions { + popUpTo(webDestinationId) { inclusive = true } + }) + } + @Test fun `navigate to modal context replacing root`() { assertThatThrownBy { getNavigatorRule(modalRootUrl) } @@ -359,7 +376,7 @@ class TurboNavRuleTest { bundle: Bundle? = null ): TurboNavRule { return TurboNavRule( - location, visitOptions, bundle, navOptions, extras, pathConfiguration, controller + location, visitOptions, bundle, extras, pathConfiguration, controller ) }