@apexnative/navigation 1.0.0-alpha.6 → 1.0.0-alpha.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/ApexNavigation.podspec +2 -1
  2. package/CHANGELOG.md +7 -0
  3. package/android/CLAUDE.md +19 -10
  4. package/android/CORE.md +3 -2
  5. package/android/consumer-rules.pro +6 -0
  6. package/android/src/main/java/dev/apexnative/navigation/options/BottomTabOptions.kt +1 -1
  7. package/android/src/main/java/dev/apexnative/navigation/options/ButtonOptions.kt +1 -1
  8. package/android/src/main/java/dev/apexnative/navigation/options/LargeTitleOptions.kt +1 -1
  9. package/android/src/main/java/dev/apexnative/navigation/options/LayoutAnimation.kt +1 -1
  10. package/android/src/main/java/dev/apexnative/navigation/options/OptionKey.kt +18 -0
  11. package/android/src/main/java/dev/apexnative/navigation/options/OptionPaths.kt +91 -0
  12. package/android/src/main/java/dev/apexnative/navigation/options/SheetOptions.kt +7 -4
  13. package/android/src/main/java/dev/apexnative/navigation/options/TitleOptions.kt +2 -2
  14. package/android/src/main/java/dev/apexnative/navigation/options/TopBarAppearanceOptions.kt +1 -1
  15. package/android/src/main/java/dev/apexnative/navigation/options/TopBarOptions.kt +16 -7
  16. package/android/src/main/java/dev/apexnative/navigation/react/ContentHeights.kt +95 -0
  17. package/android/src/main/java/dev/apexnative/navigation/react/ContentMounts.kt +66 -0
  18. package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +4 -0
  19. package/android/src/main/java/dev/apexnative/navigation/react/OptionsCommands.kt +41 -3
  20. package/android/src/main/java/dev/apexnative/navigation/react/ReactView.kt +16 -1
  21. package/android/src/main/java/dev/apexnative/navigation/slots/SlotScreenView.kt +27 -7
  22. package/android/src/main/java/dev/apexnative/navigation/slots/SlotView.kt +12 -1
  23. package/android/src/main/java/dev/apexnative/navigation/utils/MaterialTheme.kt +5 -6
  24. package/android/src/main/java/dev/apexnative/navigation/utils/SystemAnimations.kt +36 -0
  25. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalSheet.kt +329 -55
  26. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +26 -5
  27. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/SheetStops.kt +60 -2
  28. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuMotion.kt +8 -2
  29. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/BackPreview.kt +32 -4
  30. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +46 -7
  31. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackQueue.kt +15 -3
  32. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +7 -4
  33. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabMotion.kt +142 -0
  34. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabPresenter.kt +215 -43
  35. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/StackSearchPresenter.kt +25 -26
  36. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/BarRowTransition.kt +3 -2
  37. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/ButtonPlaces.kt +11 -5
  38. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +36 -38
  39. package/android/src/main/java/dev/apexnative/navigation/views/element/ElementTransitions.kt +124 -181
  40. package/android/src/main/java/dev/apexnative/navigation/views/element/TargetsWait.kt +8 -1
  41. package/android/src/main/java/dev/apexnative/navigation/views/element/TransitionLayer.kt +37 -112
  42. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ElementPictures.kt +56 -3
  43. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/FlightGeometry.kt +170 -153
  44. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMorph.kt +199 -0
  45. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/SharedAnimatorFactory.kt +55 -45
  46. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/{TextChangeAnimator.kt → TextMorph.kt} +79 -129
  47. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewMorph.kt +108 -0
  48. package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/FabCopy.kt +85 -0
  49. package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/FabMenu.kt +13 -0
  50. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TitleBar.kt +5 -24
  51. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TopBar.kt +51 -34
  52. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarButtonComponentCell.kt +6 -6
  53. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarButtonView.kt +113 -0
  54. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarMenu.kt +219 -0
  55. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonBar.kt +121 -175
  56. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonMenus.kt +34 -64
  57. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonRow.kt +216 -0
  58. package/android/src/main/jni/ANNContentHeights.cpp +82 -0
  59. package/android/src/main/res/drawable/apex_navigation_ic_arrow_right.xml +14 -0
  60. package/android/src/main/res/layout/apex_navigation_menu_header.xml +19 -0
  61. package/android/src/main/res/layout/apex_navigation_menu_item.xml +68 -0
  62. package/android/src/main/res/values/styles.xml +1 -1
  63. package/android/src/main/res/values-night/styles.xml +1 -1
  64. package/cli/doctor/checks/app.js +16 -1
  65. package/cli/project.js +4 -0
  66. package/cli/setup/index.js +5 -0
  67. package/cli/setup/steps/metro.js +46 -0
  68. package/docs-md/architecture/native-screens.md +4 -0
  69. package/docs-md/architecture/theme-and-context.md +131 -12
  70. package/docs-md/architecture/transitions.md +43 -4
  71. package/docs-md/flows/bottom-sheets.md +4 -2
  72. package/docs-md/fundamentals/screens-and-props.md +9 -1
  73. package/docs-md/guides/cli.md +3 -1
  74. package/docs-md/help/troubleshooting.md +1 -1
  75. package/docs-md/index.json +29 -1
  76. package/docs-md/screen-ui/back-and-gestures.md +3 -1
  77. package/docs-md/screen-ui/bar-buttons.md +1 -1
  78. package/docs-md/screen-ui/toolbar-and-fab.md +2 -0
  79. package/docs-md/start/installation.md +16 -1
  80. package/ios/Animations/ANNFrameDriver.h +55 -15
  81. package/ios/Animations/ANNFrameDriver.mm +214 -60
  82. package/ios/Animations/ANNTransitionAnimator.h +26 -5
  83. package/ios/Animations/ANNTransitionAnimator.mm +49 -3
  84. package/ios/Animations/ANNTransitionPlan.h +2 -2
  85. package/ios/Animations/ANNTransitionPlan.mm +10 -0
  86. package/ios/Animations/Elements/ANNElementFinder.h +2 -2
  87. package/ios/Animations/Elements/ANNElementFinder.mm +2 -2
  88. package/ios/Animations/Elements/ANNElementTransition.h +3 -3
  89. package/ios/Animations/Elements/ANNElementTransition.mm +2 -2
  90. package/ios/Animations/Elements/ANNSharedElementFlight.h +3 -3
  91. package/ios/Animations/Elements/ANNSharedElementFlight.mm +181 -43
  92. package/ios/Animations/Interpolators/ANNViewTracks.h +6 -5
  93. package/ios/Animations/Interpolators/ANNViewTracks.mm +51 -21
  94. package/ios/CLAUDE.md +15 -7
  95. package/ios/CORE.md +1 -1
  96. package/ios/Core/ANNAppDelegate.mm +0 -2
  97. package/ios/Core/ANNDeepLinkQueue.h +6 -5
  98. package/ios/Core/ANNDeepLinkQueue.mm +22 -10
  99. package/ios/Core/ANNSceneDelegate.mm +2 -2
  100. package/ios/Core/ApexNavigation.mm +6 -2
  101. package/ios/Layouts/Root/ANNRootCoordinator.mm +45 -15
  102. package/ios/Layouts/Screen/ANNComponentViewController.mm +4 -1
  103. package/ios/Layouts/Stack/ANNStackAnimatingDelegate.h +11 -2
  104. package/ios/Layouts/Stack/ANNStackAnimatingDelegate.mm +14 -7
  105. package/ios/Layouts/Stack/ANNStackController.h +8 -0
  106. package/ios/Layouts/Stack/ANNStackController.mm +153 -11
  107. package/ios/Layouts/Stack/ANNSwipePopInteraction.h +26 -0
  108. package/ios/Layouts/Stack/ANNSwipePopInteraction.mm +185 -0
  109. package/ios/Layouts/Stack/InteractivePopGestureDelegate.mm +28 -4
  110. package/ios/Options/ANNAnimationOptions.h +2 -1
  111. package/ios/Options/ANNAnimationOptions.mm +1 -1
  112. package/ios/Options/ANNOptions.h +7 -0
  113. package/ios/Options/ANNOptions.mm +45 -0
  114. package/ios/Options/ANNOptionsReader.mm +2 -4
  115. package/ios/Options/ANNStackPopAnimationOptions.h +13 -0
  116. package/ios/Options/ANNStackPopAnimationOptions.mm +11 -0
  117. package/ios/Options/Params/ColorParser.h +4 -3
  118. package/ios/Options/Params/ColorParser.mm +22 -4
  119. package/ios/React/ANNSurfaceHostingView.h +8 -0
  120. package/ios/React/ANNSurfaceHostingView.mm +28 -1
  121. package/ios/React/Slots/ANNSlotScreenView.mm +8 -2
  122. package/ios/TurboModules/ANNTurboModule.mm +46 -0
  123. package/ios/cpp/react/renderer/components/ApexNavigationSpec/ANNSlotState.cpp +20 -0
  124. package/ios/cpp/react/renderer/components/ApexNavigationSpec/ANNSlotState.h +5 -14
  125. package/lib/module/ARCHITECTURE.md +9 -3
  126. package/lib/module/adapters/NativeANNTurboModule.js.map +1 -1
  127. package/lib/module/api/app/AppRegistry.js +14 -2
  128. package/lib/module/api/app/AppRegistry.js.map +1 -1
  129. package/lib/module/api/app/createNavigation.js +188 -66
  130. package/lib/module/api/app/createNavigation.js.map +1 -1
  131. package/lib/module/api/events/navigationEvents.js +6 -2
  132. package/lib/module/api/events/navigationEvents.js.map +1 -1
  133. package/lib/module/api/linking/linking.js +11 -0
  134. package/lib/module/api/linking/linking.js.map +1 -1
  135. package/lib/module/api/screens/defineScreen.js +45 -3
  136. package/lib/module/api/screens/defineScreen.js.map +1 -1
  137. package/lib/module/api/screens/nativeScreens.js +31 -6
  138. package/lib/module/api/screens/nativeScreens.js.map +1 -1
  139. package/lib/module/api/screens/registerScreens.js +172 -35
  140. package/lib/module/api/screens/registerScreens.js.map +1 -1
  141. package/lib/module/api/theme/theme.js +5 -0
  142. package/lib/module/api/theme/theme.js.map +1 -1
  143. package/lib/module/engine/commands.js +21 -1
  144. package/lib/module/engine/commands.js.map +1 -1
  145. package/lib/module/engine/layout.js +7 -2
  146. package/lib/module/engine/layout.js.map +1 -1
  147. package/lib/module/engine/nativeScreens.js +9 -0
  148. package/lib/module/engine/nativeScreens.js.map +1 -1
  149. package/lib/module/engine/options.js +5 -4
  150. package/lib/module/engine/options.js.map +1 -1
  151. package/lib/module/engine/optionsChange.js +102 -0
  152. package/lib/module/engine/optionsChange.js.map +1 -0
  153. package/lib/module/engine/refresh.js +85 -0
  154. package/lib/module/engine/refresh.js.map +1 -0
  155. package/lib/module/engine/rememberedOptions.js +60 -0
  156. package/lib/module/engine/rememberedOptions.js.map +1 -1
  157. package/lib/module/engine/screens.js +26 -0
  158. package/lib/module/engine/screens.js.map +1 -1
  159. package/lib/typescript/adapters/NativeANNTurboModule.d.ts +7 -0
  160. package/lib/typescript/adapters/NativeANNTurboModule.d.ts.map +1 -1
  161. package/lib/typescript/api/app/AppRegistry.d.ts.map +1 -1
  162. package/lib/typescript/api/app/createNavigation.d.ts +5 -0
  163. package/lib/typescript/api/app/createNavigation.d.ts.map +1 -1
  164. package/lib/typescript/api/events/navigationEvents.d.ts +2 -2
  165. package/lib/typescript/api/events/navigationEvents.d.ts.map +1 -1
  166. package/lib/typescript/api/index.d.ts +1 -1
  167. package/lib/typescript/api/index.d.ts.map +1 -1
  168. package/lib/typescript/api/linking/linking.d.ts +8 -0
  169. package/lib/typescript/api/linking/linking.d.ts.map +1 -1
  170. package/lib/typescript/api/screens/defineScreen.d.ts +23 -4
  171. package/lib/typescript/api/screens/defineScreen.d.ts.map +1 -1
  172. package/lib/typescript/api/screens/nativeScreens.d.ts +8 -1
  173. package/lib/typescript/api/screens/nativeScreens.d.ts.map +1 -1
  174. package/lib/typescript/api/screens/registerScreens.d.ts.map +1 -1
  175. package/lib/typescript/api/theme/theme.d.ts +2 -0
  176. package/lib/typescript/api/theme/theme.d.ts.map +1 -1
  177. package/lib/typescript/engine/commands.d.ts +5 -0
  178. package/lib/typescript/engine/commands.d.ts.map +1 -1
  179. package/lib/typescript/engine/layout.d.ts.map +1 -1
  180. package/lib/typescript/engine/nativeScreens.d.ts +5 -0
  181. package/lib/typescript/engine/nativeScreens.d.ts.map +1 -1
  182. package/lib/typescript/engine/optionsChange.d.ts +28 -0
  183. package/lib/typescript/engine/optionsChange.d.ts.map +1 -0
  184. package/lib/typescript/engine/refresh.d.ts +35 -0
  185. package/lib/typescript/engine/refresh.d.ts.map +1 -0
  186. package/lib/typescript/engine/rememberedOptions.d.ts +13 -0
  187. package/lib/typescript/engine/rememberedOptions.d.ts.map +1 -1
  188. package/lib/typescript/engine/screens.d.ts +7 -0
  189. package/lib/typescript/engine/screens.d.ts.map +1 -1
  190. package/lib/typescript/interfaces/ComponentEvents.d.ts +2 -2
  191. package/lib/typescript/interfaces/options/animations.d.ts +40 -15
  192. package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
  193. package/lib/typescript/interfaces/options/bottomTabs.d.ts +2 -2
  194. package/lib/typescript/interfaces/options/buttons.d.ts +2 -2
  195. package/lib/typescript/interfaces/options/modals.d.ts +6 -7
  196. package/lib/typescript/interfaces/options/modals.d.ts.map +1 -1
  197. package/lib/typescript/interfaces/options/stack.d.ts +8 -3
  198. package/lib/typescript/interfaces/options/stack.d.ts.map +1 -1
  199. package/lib/typescript/interfaces/options/topBar.d.ts +14 -0
  200. package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
  201. package/metro/acceptPlugin.js +57 -0
  202. package/metro/babelTransformer.js +45 -0
  203. package/metro/index.js +36 -0
  204. package/package.json +3 -1
  205. package/src/ARCHITECTURE.md +9 -3
  206. package/src/adapters/NativeANNTurboModule.ts +7 -0
  207. package/src/api/app/AppRegistry.ts +16 -2
  208. package/src/api/app/createNavigation.ts +198 -66
  209. package/src/api/events/navigationEvents.ts +9 -3
  210. package/src/api/index.ts +1 -0
  211. package/src/api/linking/linking.ts +22 -1
  212. package/src/api/screens/defineScreen.ts +49 -5
  213. package/src/api/screens/nativeScreens.ts +40 -4
  214. package/src/api/screens/registerScreens.tsx +170 -42
  215. package/src/api/theme/theme.ts +5 -0
  216. package/src/engine/commands.ts +21 -0
  217. package/src/engine/layout.ts +11 -2
  218. package/src/engine/nativeScreens.ts +10 -1
  219. package/src/engine/options.ts +6 -8
  220. package/src/engine/optionsChange.ts +107 -0
  221. package/src/engine/refresh.ts +97 -0
  222. package/src/engine/rememberedOptions.ts +70 -0
  223. package/src/engine/screens.tsx +24 -0
  224. package/src/interfaces/ComponentEvents.ts +2 -2
  225. package/src/interfaces/options/animations.ts +40 -15
  226. package/src/interfaces/options/bottomTabs.ts +2 -2
  227. package/src/interfaces/options/buttons.ts +2 -2
  228. package/src/interfaces/options/modals.ts +6 -7
  229. package/src/interfaces/options/stack.ts +8 -3
  230. package/src/interfaces/options/topBar.ts +14 -0
  231. package/android/src/main/java/dev/apexnative/navigation/views/element/ElementStub.kt +0 -31
  232. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMatrixAnimator.kt +0 -134
  233. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PlainViewAnimator.kt +0 -78
  234. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PropertyAnimatorCreator.kt +0 -33
  235. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ReactImageAnimator.kt +0 -57
  236. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewChangeAnimator.kt +0 -146
@@ -3,10 +3,68 @@ package dev.apexnative.navigation.viewcontrollers.modal
3
3
  import dev.apexnative.navigation.options.SheetDetent
4
4
 
5
5
  /**
6
- * Where a shown sheet goes at new options, as on iOS: the sheet keeps the stop it stands at (the user's last drag
7
- * included) until a `selectedDetent` arrives in a `mergeOptions`, or until its stop is no longer among the detents.
6
+ * Where a sheet rests: the stops it keeps of its detents, how the Material behavior stands at them, and where a shown
7
+ * sheet goes at new options, as on iOS: the sheet keeps the stop it stands at (the user's last drag included) until a
8
+ * `selectedDetent` arrives in a `mergeOptions`, or until its stop is no longer among the detents.
8
9
  */
9
10
  internal object SheetStops {
11
+ /** The Material behavior has three resting states: collapsed, half expanded and expanded. */
12
+ const val MAX_STOPS = 3
13
+
14
+ /**
15
+ * The stops of the sheet, from the lowest: [detents] by their height ([heightOf]), one of each height (the first
16
+ * the options give), and at most [MAX_STOPS] of them: the lowest, the highest, and between them [selected] when
17
+ * it stands there, else the lowest of the rest.
18
+ */
19
+ fun stops(detents: List<SheetDetent>, heightOf: (SheetDetent) -> Int, selected: SheetDetent?): List<SheetDetent> {
20
+ val sorted = detents.sortedBy(heightOf).distinctBy(heightOf)
21
+ if (sorted.size <= MAX_STOPS) return sorted
22
+ val between = sorted.subList(1, sorted.size - 1)
23
+ val middle = selected?.takeIf { it in between } ?: between.first()
24
+ return listOf(sorted.first(), middle, sorted.last())
25
+ }
26
+
27
+ /**
28
+ * How the Material behavior stands at the stops: [fitToContents] with one or two (collapsed and expanded, the
29
+ * sheet as tall as the highest, [maxHeight]), else half expanded between them at [halfExpandedRatio] of the
30
+ * layout, and expanded [expandedOffset] below its top. [skipCollapsed] with one stop: the sheet has only that.
31
+ * The heights are in pixels, as is the rest.
32
+ */
33
+ data class Material(
34
+ val fitToContents: Boolean,
35
+ val skipCollapsed: Boolean,
36
+ val peekHeight: Int,
37
+ val halfExpandedRatio: Float?,
38
+ val expandedOffset: Int,
39
+ val maxHeight: Int,
40
+ )
41
+
42
+ /**
43
+ * The behavior's settings for stops [heights] tall (from the lowest, each higher than the one before) in a layout
44
+ * [parentHeight] tall.
45
+ */
46
+ fun material(heights: List<Int>, parentHeight: Int): Material {
47
+ val lowest = heights.first()
48
+ val highest = heights.last()
49
+ val three = heights.size == MAX_STOPS && parentHeight > 0
50
+ return Material(
51
+ fitToContents = !three,
52
+ skipCollapsed = heights.size == 1,
53
+ peekHeight = lowest,
54
+ halfExpandedRatio = if (three) heights[1].toFloat() / parentHeight else null,
55
+ expandedOffset = (parentHeight - highest).coerceAtLeast(0),
56
+ maxHeight = highest,
57
+ )
58
+ }
59
+
60
+ /**
61
+ * The heights of stops [heights] tall (from the lowest, each higher) lifted by [lift] pixels, the part of the
62
+ * keyboard over the navigation bar: the sheet stands on the keyboard as it stood on the bottom. At most [full]
63
+ * each, and each still higher than the one below, so the behavior keeps all of its states.
64
+ */
65
+ fun lifted(heights: List<Int>, lift: Int, full: Int): List<Int> =
66
+ heights.mapIndexed { i, height -> (height + lift).coerceAtMost(full - (heights.lastIndex - i)) }
67
+
10
68
  /**
11
69
  * The stop to move to, or null to stay: [arrived] (the `selectedDetent` the merge names) when it is among
12
70
  * [detents]; else, when [settled] (the stop the sheet rests at) is no longer one of them, [selected] (the resolved
@@ -1,6 +1,7 @@
1
1
  package dev.apexnative.navigation.viewcontrollers.sidemenu
2
2
 
3
3
  import dev.apexnative.navigation.react.events.AppearanceDispatcher
4
+ import dev.apexnative.navigation.utils.SystemAnimations
4
5
  import dev.apexnative.navigation.viewcontrollers.viewcontroller.ViewController
5
6
 
6
7
  /**
@@ -26,6 +27,8 @@ import dev.apexnative.navigation.viewcontrollers.viewcontroller.ViewController
26
27
  internal class SideMenuMotion(
27
28
  private val drawer: () -> Drawer,
28
29
  private val host: Host,
30
+ /** The system's animations are off ([SystemAnimations]): a command or back moves the drawer at once. */
31
+ private val animationsOff: () -> Boolean = { SystemAnimations.areOff() },
29
32
  ) {
30
33
  /** A side of the side menu: physical, `left` stays on the left in RTL too. */
31
34
  enum class Side(
@@ -242,11 +245,14 @@ internal class SideMenuMotion(
242
245
  drawer.lock(side, locked = !isOnScreen(side) && !host.isEnabled(side))
243
246
  }
244
247
 
245
- // The drawer opens or closes [side]; one that doesn't call back has its result recorded here.
248
+ // The drawer opens or closes [side]; one that doesn't call back has its result recorded here. With the system's
249
+ // animations off it jumps, unless it moves: the drag and the settle aren't animators, and it can't jump while it
250
+ // settles.
246
251
  private fun move(side: Side, open: Boolean, animate: Boolean) {
247
252
  val drawer = drawer()
253
+ val animated = animate && (moving || !animationsOff())
248
254
  val silent = !drawer.callsBack
249
- if (open) drawer.open(side, animate) else drawer.close(side, animate)
255
+ if (open) drawer.open(side, animated) else drawer.close(side, animated)
250
256
  if (silent) record(side, open)
251
257
  }
252
258
 
@@ -2,6 +2,7 @@ package dev.apexnative.navigation.viewcontrollers.stack
2
2
 
3
3
  import android.animation.Animator
4
4
  import android.animation.AnimatorListenerAdapter
5
+ import android.animation.StateListAnimator
5
6
  import android.animation.ValueAnimator
6
7
  import android.content.res.Configuration
7
8
  import android.graphics.Bitmap
@@ -29,7 +30,7 @@ import dev.apexnative.navigation.views.stack.StackLayout
29
30
  * The preview of a pop while the user's back gesture runs (predictive back), as the system shows the activity under
30
31
  * the one that closes: the screen on top with its bar, a picture taken as the gesture starts, shrinks toward the
31
32
  * gesture's side with rounded corners and follows the finger up and down; the screen under it shows behind it with
32
- * its own bar, smaller and dimmed. Released, the pop plays its end ([commit]); cancelled, everything goes back
33
+ * its own bar and FAB, smaller and dimmed. Released, the pop plays its end ([commit]); cancelled, everything goes back
33
34
  * ([cancel]). The values are those of the system's cross-activity back animation (WMShell
34
35
  * `DefaultCrossActivityBackAnimation` of Android 15 and 16, the smallest scale as Android 17 shows it), also on Android
35
36
  * 14, whose own preview differs. Main thread only.
@@ -46,8 +47,13 @@ internal class BackPreview(
46
47
  * corners, while the tab bar stays, as Gmail shows it.
47
48
  */
48
49
  private val bottomRoom: Int,
49
- /** The picture is there: the stack shows the screen under the top, with its bar, before the preview draws. */
50
+ /**
51
+ * The picture is there: the stack shows the screen under the top, with its bar and its FAB, before the preview
52
+ * draws.
53
+ */
50
54
  private val onReveal: () -> Unit,
55
+ /** The view of the FAB the stack shows now, which goes with the screen under the top; none. */
56
+ private val fab: () -> View? = { null },
51
57
  ) {
52
58
  private val density = layout.resources.displayMetrics.density
53
59
  private val bar = layout.topBar
@@ -58,6 +64,11 @@ internal class BackPreview(
58
64
  private var animator: Animator? = null
59
65
  private var ended = false
60
66
  private var topAlpha = 1f
67
+ // The FAB that shows with the screen under the top, drawn flat under the dim and the picture, its own Z and the
68
+ // animator of its Z by its state.
69
+ private var flatFab: View? = null
70
+ private var fabZ = 0f
71
+ private var fabZByState: StateListAnimator? = null
61
72
  // What puts the stack as it was after a cancel: once, as the preview ends.
62
73
  private var onCancelled: (() -> Unit)? = null
63
74
  // Forward is to the end of the line: right to left, the screens move the other way, as the stack's pop does.
@@ -175,10 +186,17 @@ internal class BackPreview(
175
186
  card = null
176
187
  (scrim.parent as? ViewGroup)?.removeView(scrim)
177
188
  top.alpha = topAlpha
189
+ flatFab?.let {
190
+ flatFab = null
191
+ it.translationZ = fabZ
192
+ it.stateListAnimator = fabZByState
193
+ fabZByState = null
194
+ }
178
195
  entering(1f, 0f)
179
196
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
180
197
  target.resetPivot()
181
198
  bar.resetPivot()
199
+ fab()?.resetPivot()
182
200
  }
183
201
  onCancelled?.let {
184
202
  onCancelled = null
@@ -190,6 +208,16 @@ internal class BackPreview(
190
208
  // the top's place, cut to rounded corners.
191
209
  private fun show(picture: Bitmap) {
192
210
  onReveal()
211
+ // A child with a Z draws over those without, whatever their order: the FAB, raised by its elevation, would lie
212
+ // over the dim and the picture. Its state's animator, which Material's FAB sets on the frame after it joins the
213
+ // window (and on a press, focus or hover), would raise it again: it stays off while the preview draws.
214
+ fab()?.takeIf { it.z > 0f }?.let {
215
+ flatFab = it
216
+ fabZ = it.translationZ
217
+ fabZByState = it.stateListAnimator
218
+ it.stateListAnimator = null
219
+ it.translationZ = -it.elevation
220
+ }
193
221
  val dark = layout.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
194
222
  Configuration.UI_MODE_NIGHT_YES
195
223
  scrim.setBackgroundColor(Color.BLACK)
@@ -235,9 +263,9 @@ internal class BackPreview(
235
263
  entering(scale, -SystemTransition.DISTANCE_DP * density * forward)
236
264
  }
237
265
 
238
- // The screen under the top and its bar at [scale] around the stack's center, [x] along the line.
266
+ // The screen under the top, its bar and its FAB at [scale] around the stack's center, [x] along the line.
239
267
  private fun entering(scale: Float, x: Float) {
240
- for (view in listOf(target, bar)) {
268
+ for (view in listOfNotNull(target, bar, fab())) {
241
269
  view.pivotX = layout.width / 2f - view.left
242
270
  view.pivotY = areaHeight / 2f - view.top
243
271
  view.scaleX = scale
@@ -31,6 +31,7 @@ import dev.apexnative.navigation.viewcontrollers.parent.OptionsScreen
31
31
  import dev.apexnative.navigation.viewcontrollers.parent.ParentController
32
32
  import dev.apexnative.navigation.viewcontrollers.parent.ReturnsScreenFromColumn
33
33
  import dev.apexnative.navigation.viewcontrollers.parent.nodeWith
34
+ import dev.apexnative.navigation.viewcontrollers.stack.fab.FabMotion
34
35
  import dev.apexnative.navigation.viewcontrollers.stack.fab.FabPresenter
35
36
  import dev.apexnative.navigation.viewcontrollers.stack.topbar.LargeTitleCollapse
36
37
  import dev.apexnative.navigation.viewcontrollers.stack.topbar.TopBarPresenter
@@ -207,6 +208,17 @@ class StackController(
207
208
  screen.changeOptions { it.topBar.buttons.back.setVisible() }
208
209
  }
209
210
 
211
+ /**
212
+ * `clearOptions` took options out of the own options of [node], an entry of this stack or the options screen of
213
+ * one: the back arrow's rule ([join]) writes its part of them again, so the first screen keeps its arrow hidden
214
+ * and another shows it unless its options say otherwise.
215
+ */
216
+ internal fun ownOptionsCleared(node: ViewController<*>) {
217
+ val entries = queue.entries
218
+ val entry = entries.firstOrNull { it === node || optionsScreen(it) === node } ?: return
219
+ backRule(entry, first = entry === entries.first())
220
+ }
221
+
210
222
  /** The key of [entry]; none without one. */
211
223
  internal fun keyOf(entry: ViewController<*>): String? = keys[entry]
212
224
 
@@ -307,6 +319,9 @@ class StackController(
307
319
  // The back gesture over this stack whose preview hasn't ended yet; none.
308
320
  private var backGesture: BackGesture? = null
309
321
 
322
+ // A merge of `fab` into the top waits for the end of a back gesture's preview.
323
+ private var fabMergeWaits = false
324
+
310
325
  /**
311
326
  * A back gesture over the top of this stack ([startBackGesture]): its progress, its cancel, and its release, which
312
327
  * pops the top through the queue with the preview's end. A command accepted meanwhile, or the stack's destroy,
@@ -326,6 +341,10 @@ class StackController(
326
341
  private var revealed = false
327
342
  private var aborted = false
328
343
 
344
+ /** The preview shows the screen under the top, with its bar and its FAB. */
345
+ val isRevealed: Boolean
346
+ get() = revealed && !aborted
347
+
329
348
  /** The gesture moved: [progress] 0 … 1, the finger at [touchY]. */
330
349
  fun progress(progress: Float, touchY: Float) {
331
350
  if (!aborted) preview.progress(progress, touchY)
@@ -348,6 +367,9 @@ class StackController(
348
367
  return true
349
368
  }
350
369
  if (backGesture === this) backGesture = null
370
+ // The top's FAB, set aside, leaves with it; a merge into it no longer matters.
371
+ fab.dropAside()
372
+ fabMergeWaits = false
351
373
  queue.cut(target, null, id, "go back in the stack '$id'", null, gesture = true)
352
374
  return true
353
375
  }
@@ -360,7 +382,8 @@ class StackController(
360
382
  restore()
361
383
  }
362
384
 
363
- // The preview's picture is there: the screen under the top shows under it, and the bar shows that screen.
385
+ // The preview's picture is there: the screen under the top shows under it, and the bar and the FAB show that
386
+ // screen, at once.
364
387
  internal fun reveal() {
365
388
  val view = target.peekView() ?: return
366
389
  targetWasPlaced = view.parent === peekView() && view.visibility == View.VISIBLE
@@ -370,6 +393,8 @@ class StackController(
370
393
  views.reveal(target, top)
371
394
  view.alpha = 1f
372
395
  topBar.apply(target)
396
+ fab.setAside()
397
+ showFab(FabMotion.Move.AtOnce, target)
373
398
  }
374
399
 
375
400
  private fun restore() {
@@ -386,9 +411,14 @@ class StackController(
386
411
  views.detach(target)
387
412
  }
388
413
  }
389
- // The bar shows the top again, its components appeared again.
414
+ // The bar and the FAB show the top again, the bar's components appeared again.
390
415
  val shown = queue.currentChild
391
416
  topBar.apply(shown)
417
+ fab.bringBack()
418
+ if (fabMergeWaits) {
419
+ fabMergeWaits = false
420
+ mergeFab()
421
+ }
392
422
  shown?.let {
393
423
  topBar.screenWillAppear(optionsScreen(it))
394
424
  topBar.screenDidAppear(optionsScreen(it))
@@ -462,7 +492,8 @@ class StackController(
462
492
  ContainerBackground.apply(this, options)
463
493
  updateBar()
464
494
  takeKeptMerge(child)?.let(::applyTopMerge)
465
- if (queue.isAtRest) showFab()
495
+ // A back gesture's preview shows the FAB of the screen under the top.
496
+ if (queue.isAtRest && backGesture?.isRevealed != true) showFab()
466
497
  performOnParentController { it.applyChildOptions(withoutStackGroups(options), this) }
467
498
  }
468
499
 
@@ -670,15 +701,20 @@ class StackController(
670
701
  topBar.apply(queue.currentChild)
671
702
  }
672
703
 
673
- // The FAB follows the top at the end of a transition.
674
- private fun showFab() {
704
+ // The FAB follows [top] (the current child) from the start of a transition by [move], with the screens; at rest, a
705
+ // FAB that comes by Material's swap.
706
+ private fun showFab(move: FabMotion.Move = FabMotion.Move.Swap, top: ViewController<*>? = queue.currentChild) {
675
707
  val layout = peekView() ?: return
676
- val top = queue.currentChild
677
708
  val screen = top?.let(::optionsScreen)
678
- fab.showFor(layout, screen, top?.let(::resolvedOf)?.fab ?: FabOptions(), scrollOf(screen))
709
+ fab.showFor(layout, screen, top?.let(::resolvedOf)?.fab ?: FabOptions(), scrollOf(screen), move)
679
710
  }
680
711
 
681
712
  private fun mergeFab() {
713
+ // A back gesture's preview shows the FAB of the screen under the top: the top's merge waits for a cancel.
714
+ if (backGesture?.isRevealed == true) {
715
+ fabMergeWaits = true
716
+ return
717
+ }
682
718
  val layout = peekView() ?: return
683
719
  val top = queue.currentChild ?: return
684
720
  val screen = optionsScreen(top)
@@ -727,6 +763,9 @@ class StackController(
727
763
  parentController?.tabBarTransition(this@StackController, move, side)
728
764
  }
729
765
 
766
+ // The FAB of the new top arrives with its screen, the old one leaves with its own.
767
+ override fun fabTransition(move: FabMotion.Move) = showFab(move)
768
+
730
769
  override fun operationEnded(topSettled: Boolean) {
731
770
  BackCallbackState.update()
732
771
  if (topSettled) parentController?.tabBarSettled(this@StackController)
@@ -21,6 +21,7 @@ import dev.apexnative.navigation.viewcontrollers.stack.StackDisplay.Custom
21
21
  import dev.apexnative.navigation.viewcontrollers.stack.StackDisplay.Elements
22
22
  import dev.apexnative.navigation.viewcontrollers.stack.StackDisplay.Fade
23
23
  import dev.apexnative.navigation.viewcontrollers.stack.StackDisplay.Motion
24
+ import dev.apexnative.navigation.viewcontrollers.stack.fab.FabMotion
24
25
  import dev.apexnative.navigation.viewcontrollers.viewcontroller.ViewController
25
26
  import dev.apexnative.navigation.views.animations.AnimationRules
26
27
  import java.util.WeakHashMap
@@ -58,7 +59,7 @@ private typealias Node = ViewController<*>
58
59
  * made in the commit of that mount (its `mergeOptions`), so the transition starts with the options of that commit —
59
60
  * content mounted already goes that way too. A push or a `setStackRoot` with elements waits for its targets after
60
61
  * that, a wait like the others, within what is left of its limit without `waitForRender`: a cut or a replace during it
61
- * interrupts the push, which moves no view into a layer.
62
+ * interrupts the push, which starts no element.
62
63
  *
63
64
  * The transition is the screens' own when the options say so (`AnimationRules.stackIsOwn`): the display plays each
64
65
  * screen's side ([StackDisplay.animateCustom]) with the same owners as the default transitions; the bar's row then
@@ -103,6 +104,12 @@ internal class StackQueue(
103
104
  */
104
105
  fun tabBarTransition(move: TabBarMotion.Move, side: BottomTabsEnterExitAnimationOptions?) = Unit
105
106
 
107
+ /**
108
+ * The current child is about to change, right after [barTransition]: [move] is how the FAB goes, the new top's
109
+ * arriving with its screen and the old one's leaving with its own from the start of the transition.
110
+ */
111
+ fun fabTransition(move: FabMotion.Move) = Unit
112
+
106
113
  /** [node] was accepted to show as the top (a push, the new top of a replace). */
107
114
  fun willShow(node: Node) = Unit
108
115
 
@@ -546,6 +553,7 @@ internal class StackQueue(
546
553
  val ended = takeOver(listOfNotNull(covered, node), op)
547
554
  host.tabBarTransition(TabBarMotion.Move.AtOnce, null)
548
555
  host.barTransition(null, BarVisibility.AT_ONCE)
556
+ host.fabTransition(FabMotion.Move.AtOnce)
549
557
  host.currentChildChanged()
550
558
  finishTakenOver(ended)
551
559
  if (display.hasView && covered != null) display.detach(covered)
@@ -785,6 +793,7 @@ internal class StackQueue(
785
793
  val ended = takeOver(old + top, op)
786
794
  host.tabBarTransition(TabBarMotion.Move.AtOnce, null)
787
795
  host.barTransition(null, BarVisibility.AT_ONCE)
796
+ host.fabTransition(FabMotion.Move.AtOnce)
788
797
  host.currentChildChanged()
789
798
  finishTakenOver(ended)
790
799
  op.done = true
@@ -1124,6 +1133,7 @@ internal class StackQueue(
1124
1133
  reject(op, text)
1125
1134
  host.tabBarTransition(TabBarMotion.Move.AtOnce, null)
1126
1135
  host.barTransition(null, BarVisibility.AT_ONCE)
1136
+ host.fabTransition(FabMotion.Move.AtOnce)
1127
1137
  host.currentChildChanged()
1128
1138
  settled()
1129
1139
  drain()
@@ -1174,7 +1184,7 @@ internal class StackQueue(
1174
1184
  // The bars with a change of the current child. The row goes with the default transition; with an animation of its
1175
1185
  // own the top bar goes with the screens ([travel]). The tab bar rides with the system's [system] move, slides with an
1176
1186
  // animation of its own or a side of its own, and is where the new top wants it at once without animation; with
1177
- // elements every bar changes at once.
1187
+ // elements every bar changes at once. The FAB goes with its screen, by Material's swap with an animation of its own.
1178
1188
  private fun tellBars(
1179
1189
  animated: Boolean,
1180
1190
  own: Boolean,
@@ -1183,7 +1193,8 @@ internal class StackQueue(
1183
1193
  system: TabBarMotion.Move,
1184
1194
  row: BarMotion,
1185
1195
  travel: BarTravel,
1186
- // The top bar shows the new top already (a back gesture's preview): it changes at once, the tab bar still moves.
1196
+ // The top bar and the FAB show the new top already (a back gesture's preview): they change at once, the tab bar
1197
+ // still moves.
1187
1198
  barAtOnce: Boolean = false,
1188
1199
  ) {
1189
1200
  val tabs = animation.bottomTabs
@@ -1200,6 +1211,7 @@ internal class StackQueue(
1200
1211
  barVisibility(barMoves && !elements, animation.topBar),
1201
1212
  travel.takeIf { barMoves && own && !elements },
1202
1213
  )
1214
+ host.fabTransition(FabMotion.moveOf(animated, own, row, shown = barAtOnce))
1203
1215
  }
1204
1216
 
1205
1217
  private fun barVisibility(animated: Boolean, side: TopBarEnterExitAnimationOptions): BarVisibility =
@@ -150,8 +150,9 @@ internal class StackViews(private val stack: StackController) : StackDisplay {
150
150
  }
151
151
  }
152
152
 
153
- // The elements go into a layer over the screens, and back as the screen's animator ends or stops. A shared element
154
- // without a duration takes the screen's fade: its own single `alpha`, else 300 ms.
153
+ // The elements play in their screens and the pairs' copies fly in a layer over them, all done as the screen's
154
+ // animator ends or stops. A shared element without a duration takes the screen's fade: its own single `alpha`,
155
+ // else 300 ms.
155
156
  override fun animateCustom(screen: ViewController<*>, custom: StackDisplay.Custom, done: () -> Unit) {
156
157
  stop(screen)
157
158
  val view = screen.peekView() ?: return done()
@@ -199,7 +200,7 @@ internal class StackViews(private val stack: StackController) : StackDisplay {
199
200
 
200
201
  /**
201
202
  * Starts the preview of a back gesture over [top], [target] under it (`BackPreview`): none without the stack's view
202
- * or a window. [onReveal] shows [target] and its bar as the preview's picture is there.
203
+ * or a window. [onReveal] shows [target] with its bar and its FAB as the preview's picture is there.
203
204
  */
204
205
  fun startBackPreview(
205
206
  top: ViewController<*>,
@@ -214,7 +215,9 @@ internal class StackViews(private val stack: StackController) : StackDisplay {
214
215
  val window = stack.activity.window ?: return null
215
216
  val topView = top.peekView() ?: return null
216
217
  val targetView = target.peekView() ?: return null
217
- val started = BackPreview(layout, topView, targetView, fromLeft, touchY, bottomRoom, onReveal)
218
+ val started = BackPreview(layout, topView, targetView, fromLeft, touchY, bottomRoom, onReveal) {
219
+ stack.fab.shownView
220
+ }
218
221
  preview = started
219
222
  previewScreens = top to target
220
223
  started.start(window)
@@ -0,0 +1,142 @@
1
+ package dev.apexnative.navigation.viewcontrollers.stack.fab
2
+
3
+ import dev.apexnative.navigation.viewcontrollers.stack.StackDisplay.BarMotion
4
+ import dev.apexnative.navigation.views.animations.FadeThrough
5
+ import dev.apexnative.navigation.views.animations.SystemTransition
6
+
7
+ /**
8
+ * How a stack's FAB goes with a change of its screen on top, as the values alone: the new top's FAB arrives with its
9
+ * screen and a copy of the old one leaves with the screen it belonged to, from the start of the transition. A FAB with
10
+ * the same id stays where it is. The stack plays them on its views (`FabPresenter`).
11
+ */
12
+ internal object FabMotion {
13
+ /** How the stack's transition goes, as its queue tells it (`StackQueue.Host.fabTransition`). */
14
+ enum class Move {
15
+ /** The system's push: each FAB rides with its screen along the line, as the tab bar does. */
16
+ WithPush,
17
+
18
+ /** The system's pop: the same, the other way. */
19
+ WithPop,
20
+
21
+ /** The fade through of a `setStackRoot`: the old one fades out first, the new one fades in and grows. */
22
+ FadeThrough,
23
+
24
+ /**
25
+ * A transition of the screens' own (`content`, elements): Material's swap at its start, the old one shrinking
26
+ * away, the new one growing from 60 %.
27
+ */
28
+ Swap,
29
+
30
+ /** No animation: the new top's FAB is there at once. */
31
+ AtOnce,
32
+ }
33
+
34
+ /** The curve a ride's move and scale take; its alpha changes on a straight line. */
35
+ enum class Curve { System, FadeThrough, Material }
36
+
37
+ /**
38
+ * A FAB's ride: along the x axis in dp, mirrored right to left; its scale on [curve] over [duration]; its alpha after
39
+ * a hold of [alphaDelay] over [alphaDuration], in ms.
40
+ */
41
+ data class Ride(
42
+ val fromX: Float,
43
+ val toX: Float,
44
+ val fromAlpha: Float,
45
+ val toAlpha: Float,
46
+ val fromScale: Float,
47
+ val toScale: Float,
48
+ val alphaDelay: Long,
49
+ val alphaDuration: Long,
50
+ val duration: Long,
51
+ val curve: Curve,
52
+ )
53
+
54
+ /** How long Material's swap takes, as the FAB shrinks or grows. */
55
+ const val SWAP_MILLIS = 200L
56
+
57
+ /** Where Material's swap grows the new FAB from. */
58
+ const val GROW_FROM = 0.6f
59
+
60
+ private const val FADE_THROUGH_SCALE = 0.92f
61
+
62
+ /**
63
+ * The move of the FAB with a change of the current child: none when the change comes at once or the new top shows
64
+ * already ([shown]: a back gesture's preview showed it); Material's swap with an animation of the screens' own
65
+ * ([own]); else the default transition's [row], as the bar's row goes.
66
+ */
67
+ fun moveOf(animated: Boolean, own: Boolean, row: BarMotion, shown: Boolean): Move = when {
68
+ !animated || shown -> Move.AtOnce
69
+ own -> Move.Swap
70
+ row == BarMotion.Push -> Move.WithPush
71
+ row == BarMotion.Pop -> Move.WithPop
72
+ else -> Move.FadeThrough
73
+ }
74
+
75
+ /**
76
+ * The new top's FAB comes with its screen: a push brings it from +96 dp (−96 dp on a pop), transparent for the hold
77
+ * of the screen's fade, then fading in; the fade through fades it in after the old one went and grows it from 92 %;
78
+ * the swap grows it from 60 %. None at once.
79
+ */
80
+ fun arrival(move: Move, rtl: Boolean): Ride? {
81
+ val distance = SystemTransition.DISTANCE_DP * if (rtl) -1 else 1
82
+ return when (move) {
83
+ Move.WithPush, Move.WithPop -> Ride(
84
+ fromX = if (move == Move.WithPush) distance else -distance,
85
+ toX = 0f,
86
+ fromAlpha = 0f,
87
+ toAlpha = 1f,
88
+ fromScale = 1f,
89
+ toScale = 1f,
90
+ alphaDelay = fadeDelay(move),
91
+ alphaDuration = SystemTransition.FADE_DURATION,
92
+ duration = SystemTransition.DURATION,
93
+ curve = Curve.System,
94
+ )
95
+ Move.FadeThrough -> Ride(
96
+ fromX = 0f,
97
+ toX = 0f,
98
+ fromAlpha = 0f,
99
+ toAlpha = 1f,
100
+ fromScale = FADE_THROUGH_SCALE,
101
+ toScale = 1f,
102
+ alphaDelay = fadeThroughOut(),
103
+ alphaDuration = FadeThrough.DURATION - fadeThroughOut(),
104
+ duration = FadeThrough.DURATION,
105
+ curve = Curve.FadeThrough,
106
+ )
107
+ Move.Swap -> Ride(0f, 0f, 1f, 1f, GROW_FROM, 1f, 0L, 0L, SWAP_MILLIS, Curve.Material)
108
+ Move.AtOnce -> null
109
+ }
110
+ }
111
+
112
+ /**
113
+ * The old top's FAB leaves with its screen, from where it is: a push takes it to −96 dp with the covered screen and
114
+ * fades it out as the new screen fades in over it; a pop takes it to +96 dp, fading with the leaving screen; the
115
+ * fade through fades it out in the first 35 %; the swap shrinks it to nothing. None at once.
116
+ */
117
+ fun departure(move: Move, rtl: Boolean): Ride? {
118
+ val distance = SystemTransition.DISTANCE_DP * if (rtl) -1 else 1
119
+ return when (move) {
120
+ Move.WithPush, Move.WithPop -> Ride(
121
+ fromX = 0f,
122
+ toX = if (move == Move.WithPush) -distance else distance,
123
+ fromAlpha = 1f,
124
+ toAlpha = 0f,
125
+ fromScale = 1f,
126
+ toScale = 1f,
127
+ alphaDelay = fadeDelay(move),
128
+ alphaDuration = SystemTransition.FADE_DURATION,
129
+ duration = SystemTransition.DURATION,
130
+ curve = Curve.System,
131
+ )
132
+ Move.FadeThrough -> Ride(0f, 0f, 1f, 0f, 1f, 1f, 0L, fadeThroughOut(), fadeThroughOut(), Curve.FadeThrough)
133
+ Move.Swap -> Ride(0f, 0f, 1f, 1f, 1f, 0f, 0L, 0L, SWAP_MILLIS, Curve.Material)
134
+ Move.AtOnce -> null
135
+ }
136
+ }
137
+
138
+ private fun fadeDelay(move: Move): Long =
139
+ if (move == Move.WithPush) SystemTransition.PUSH_FADE_DELAY else SystemTransition.POP_FADE_DELAY
140
+
141
+ private fun fadeThroughOut(): Long = (FadeThrough.DURATION * FadeThrough.OUT).toLong()
142
+ }