Skip to content

Commit c88c127

Browse files
authored
Merge pull request #107 from hotwired/modal-navigation-action
Improved `default` context visit behavior when dismissing the `modal` context
2 parents 9aaf631 + 23a2304 commit c88c127

File tree

3 files changed

+78
-7
lines changed

3 files changed

+78
-7
lines changed

navigation-fragments/src/main/java/dev/hotwire/navigation/destinations/HotwireDestination.kt

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -137,13 +137,23 @@ interface HotwireDestination : BridgeDestination {
137137
newPathProperties: PathConfigurationProperties,
138138
action: VisitAction
139139
): NavOptions {
140-
val modal = newPathProperties.context == PresentationContext.MODAL
141-
val clearAll = newPathProperties.presentation == Presentation.CLEAR_ALL
142-
val animate = action != VisitAction.REPLACE &&
140+
val navigatingToModalContext = pathProperties.context == PresentationContext.DEFAULT &&
141+
newPathProperties.context == PresentationContext.MODAL
142+
143+
val navigatingWithinModalContext = pathProperties.context == PresentationContext.MODAL &&
144+
newPathProperties.context == PresentationContext.MODAL
145+
146+
val dismissingModalContext = pathProperties.context == PresentationContext.MODAL &&
147+
newPathProperties.context == PresentationContext.DEFAULT
148+
149+
val animate = (navigatingToModalContext || dismissingModalContext) ||
150+
(action != VisitAction.REPLACE &&
143151
newPathProperties.presentation != Presentation.REPLACE &&
144-
newPathProperties.presentation != Presentation.REPLACE_ROOT
152+
newPathProperties.presentation != Presentation.REPLACE_ROOT)
153+
154+
val clearAll = newPathProperties.presentation == Presentation.CLEAR_ALL
145155

146-
return if (modal) {
156+
return if (navigatingToModalContext || navigatingWithinModalContext || dismissingModalContext) {
147157
navOptions {
148158
anim {
149159
enter = if (animate) R.anim.enter_slide_in_bottom else 0

navigation-fragments/src/main/java/dev/hotwire/navigation/navigator/NavigatorRule.kt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,12 @@ internal class NavigatorRule(
8282
val locationIsCurrent = locationsAreSame(newLocation, currentLocation)
8383
val locationIsPrevious = locationsAreSame(newLocation, previousLocation)
8484
val replace = newVisitOptions.action == VisitAction.REPLACE
85+
val dismissModalContext = currentPresentationContext == PresentationContext.MODAL &&
86+
newPresentationContext == PresentationContext.DEFAULT
8587

8688
return when {
8789
locationIsCurrent && isAtStartDestination -> Presentation.REPLACE_ROOT
88-
locationIsPrevious -> Presentation.POP
90+
locationIsPrevious || dismissModalContext -> Presentation.POP
8991
locationIsCurrent || replace -> Presentation.REPLACE
9092
else -> Presentation.PUSH
9193
}
@@ -134,9 +136,16 @@ internal class NavigatorRule(
134136
return null
135137
}
136138

139+
// When dismissing a modal to navigate in the "default" context, override the visit
140+
// action. A visit proposal with an original "replace" action is respected by first
141+
// dismissing the modal. But we don't want to also "replace" the underlying "default"
142+
// context screen (unless the urls are the same).
143+
val locationIsPrevious = locationsAreSame(newLocation, previousLocation)
144+
val action = if (locationIsPrevious) VisitAction.REPLACE else VisitAction.ADVANCE
145+
137146
return SessionModalResult(
138147
location = newLocation,
139-
options = newVisitOptions,
148+
options = newVisitOptions.copy(action = action),
140149
bundle = newBundle,
141150
shouldNavigate = newProperties.presentation != Presentation.NONE
142151
)

navigation-fragments/src/test/kotlin/dev/hotwire/navigation/navigator/NavigatorRuleTest.kt

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import dev.hotwire.core.turbo.config.PathConfiguration.Location
1717
import dev.hotwire.core.turbo.nav.Presentation
1818
import dev.hotwire.core.turbo.nav.PresentationContext
1919
import dev.hotwire.core.turbo.nav.QueryStringPresentation
20+
import dev.hotwire.core.turbo.visit.VisitAction
2021
import dev.hotwire.core.turbo.visit.VisitOptions
2122
import org.assertj.core.api.Assertions.assertThat
2223
import org.assertj.core.api.Assertions.assertThatThrownBy
@@ -37,6 +38,7 @@ class NavigatorRuleTest {
3738
private val homeUrl = "https://hotwired.dev/home"
3839
private val newHomeUrl = "https://hotwired.dev/new-home"
3940
private val featureUrl = "https://hotwired.dev/feature"
41+
private val featureTwoUrl = "https://hotwired.dev/feature-two"
4042
private val newUrl = "https://hotwired.dev/feature/new"
4143
private val editUrl = "https://hotwired.dev/feature/edit"
4244
private val recedeUrl = "https://hotwired.dev/custom/recede"
@@ -203,6 +205,56 @@ class NavigatorRuleTest {
203205
assertThat(rule.newNavOptions).isEqualTo(navOptions)
204206
}
205207

208+
@Test
209+
fun `navigating back to feature from modal context with replace action maintains replace`() {
210+
controller.navigate(webDestinationId, locationArgs(featureUrl))
211+
controller.navigate(webModalDestinationId, locationArgs(newUrl))
212+
val rule = getNavigatorRule(featureUrl, VisitOptions(action = VisitAction.REPLACE))
213+
214+
// Current destination
215+
assertThat(rule.previousLocation).isEqualTo(featureUrl)
216+
assertThat(rule.currentLocation).isEqualTo(newUrl)
217+
assertThat(rule.currentPresentationContext).isEqualTo(PresentationContext.MODAL)
218+
assertThat(rule.isAtStartDestination).isFalse()
219+
220+
// New destination
221+
assertThat(rule.newLocation).isEqualTo(featureUrl)
222+
assertThat(rule.newPresentationContext).isEqualTo(PresentationContext.DEFAULT)
223+
assertThat(rule.newPresentation).isEqualTo(Presentation.POP)
224+
assertThat(rule.newQueryStringPresentation).isEqualTo(QueryStringPresentation.REPLACE)
225+
assertThat(rule.newNavigationMode).isEqualTo(NavigatorMode.DISMISS_MODAL)
226+
assertThat(rule.newModalResult?.location).isEqualTo(featureUrl)
227+
assertThat(rule.newModalResult?.options?.action).isEqualTo(VisitAction.REPLACE)
228+
assertThat(rule.newDestinationUri).isEqualTo(webUri)
229+
assertThat(rule.newDestination).isNotNull()
230+
assertThat(rule.newNavOptions).isEqualTo(navOptions)
231+
}
232+
233+
@Test
234+
fun `navigating to new feature from modal context with replace action changes to advance`() {
235+
controller.navigate(webDestinationId, locationArgs(featureUrl))
236+
controller.navigate(webModalDestinationId, locationArgs(newUrl))
237+
val rule = getNavigatorRule(featureTwoUrl, VisitOptions(action = VisitAction.REPLACE))
238+
239+
// Current destination
240+
assertThat(rule.previousLocation).isEqualTo(featureUrl)
241+
assertThat(rule.currentLocation).isEqualTo(newUrl)
242+
assertThat(rule.currentPresentationContext).isEqualTo(PresentationContext.MODAL)
243+
assertThat(rule.isAtStartDestination).isFalse()
244+
245+
// New destination
246+
assertThat(rule.newLocation).isEqualTo(featureTwoUrl)
247+
assertThat(rule.newPresentationContext).isEqualTo(PresentationContext.DEFAULT)
248+
assertThat(rule.newPresentation).isEqualTo(Presentation.POP)
249+
assertThat(rule.newQueryStringPresentation).isEqualTo(QueryStringPresentation.REPLACE)
250+
assertThat(rule.newNavigationMode).isEqualTo(NavigatorMode.DISMISS_MODAL)
251+
assertThat(rule.newModalResult?.location).isEqualTo(featureTwoUrl)
252+
assertThat(rule.newModalResult?.options?.action).isEqualTo(VisitAction.ADVANCE)
253+
assertThat(rule.newDestinationUri).isEqualTo(webUri)
254+
assertThat(rule.newDestination).isNotNull()
255+
assertThat(rule.newNavOptions).isEqualTo(navOptions)
256+
}
257+
206258
@Test
207259
fun `navigate from modal to same modal`() {
208260
controller.navigate(webModalDestinationId, locationArgs(newUrl))

0 commit comments

Comments
 (0)