@apexnative/navigation 1.0.0-alpha.2 → 1.0.0-alpha.4

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 (226) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/android/CLAUDE.md +38 -9
  3. package/android/CORE.md +11 -4
  4. package/android/RECIPES.md +9 -3
  5. package/android/src/main/java/dev/apexnative/navigation/NavigationActivity.kt +29 -1
  6. package/android/src/main/java/dev/apexnative/navigation/NavigationApplication.kt +34 -13
  7. package/android/src/main/java/dev/apexnative/navigation/layout/LayoutChecks.kt +5 -1
  8. package/android/src/main/java/dev/apexnative/navigation/layout/LayoutNode.kt +6 -2
  9. package/android/src/main/java/dev/apexnative/navigation/nativescreen/NativeScreenContext.kt +2 -2
  10. package/android/src/main/java/dev/apexnative/navigation/options/ButtonMenuOptions.kt +2 -2
  11. package/android/src/main/java/dev/apexnative/navigation/options/ButtonOptions.kt +2 -2
  12. package/android/src/main/java/dev/apexnative/navigation/options/OptionsCache.kt +8 -4
  13. package/android/src/main/java/dev/apexnative/navigation/options/StatusBarOptions.kt +7 -3
  14. package/android/src/main/java/dev/apexnative/navigation/options/UserChoices.kt +159 -0
  15. package/android/src/main/java/dev/apexnative/navigation/react/CommandAnswer.kt +2 -24
  16. package/android/src/main/java/dev/apexnative/navigation/react/CommandContract.kt +59 -0
  17. package/android/src/main/java/dev/apexnative/navigation/react/Hinge.kt +27 -19
  18. package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +5 -4
  19. package/android/src/main/java/dev/apexnative/navigation/react/PendingCalls.kt +48 -0
  20. package/android/src/main/java/dev/apexnative/navigation/react/events/AppearanceDispatcher.kt +17 -7
  21. package/android/src/main/java/dev/apexnative/navigation/slots/SlotContainers.kt +10 -7
  22. package/android/src/main/java/dev/apexnative/navigation/slots/SlotView.kt +4 -3
  23. package/android/src/main/java/dev/apexnative/navigation/slots/SlotViews.kt +40 -0
  24. package/android/src/main/java/dev/apexnative/navigation/utils/ImageKey.kt +66 -0
  25. package/android/src/main/java/dev/apexnative/navigation/utils/ImageLoader.kt +7 -89
  26. package/android/src/main/java/dev/apexnative/navigation/utils/ImageRequests.kt +76 -0
  27. package/android/src/main/java/dev/apexnative/navigation/utils/OptionImages.kt +262 -0
  28. package/android/src/main/java/dev/apexnative/navigation/utils/ViewController.kt +3 -1
  29. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/arrangement/SecondaryScreenPresenter.kt +13 -5
  30. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/AppliedTabCalls.kt +49 -0
  31. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsController.kt +92 -19
  32. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsPresenter.kt +25 -24
  33. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ComponentPresenter.kt +28 -7
  34. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ContentReadiness.kt +1 -1
  35. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/RenderWait.kt +27 -7
  36. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ScreenFrame.kt +1 -1
  37. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalAnimations.kt +22 -24
  38. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalCoordinator.kt +79 -13
  39. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalRecord.kt +21 -6
  40. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalSheet.kt +29 -12
  41. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +22 -18
  42. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/BackCallbackState.kt +4 -0
  43. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt +45 -6
  44. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/RootCoordinator.kt +35 -9
  45. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/overlay/OverlayCoordinator.kt +10 -8
  46. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/Capabilities.kt +11 -4
  47. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/ContainerBackground.kt +3 -3
  48. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/ParentController.kt +3 -0
  49. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuController.kt +5 -1
  50. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitPanes.kt +23 -0
  51. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitViewController.kt +72 -16
  52. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/BackPreview.kt +287 -0
  53. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackCommands.kt +28 -18
  54. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +164 -13
  55. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackDisplay.kt +26 -1
  56. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackQueue.kt +136 -40
  57. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackTransitions.kt +26 -8
  58. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +107 -4
  59. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabPresenter.kt +7 -5
  60. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/StackSearchPresenter.kt +3 -3
  61. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/MenuChecks.kt +13 -56
  62. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +131 -9
  63. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/viewcontroller/ViewController.kt +75 -21
  64. package/android/src/main/java/dev/apexnative/navigation/views/animations/Curves.kt +29 -11
  65. package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/BottomTabs.kt +4 -2
  66. package/android/src/main/java/dev/apexnative/navigation/views/stack/ScreenEdge.kt +15 -0
  67. package/android/src/main/java/dev/apexnative/navigation/views/stack/StackLayout.kt +137 -1
  68. package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/Fab.kt +14 -10
  69. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TitleBar.kt +8 -1
  70. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TopBar.kt +26 -4
  71. package/cli/doctor/checks/app.js +61 -13
  72. package/cli/migrate/native.js +18 -0
  73. package/cli/setup/source.js +2 -1
  74. package/cli/setup/steps/mainApplication.js +134 -19
  75. package/docs-md/architecture/transitions.md +12 -7
  76. package/docs-md/containers/modals.md +4 -2
  77. package/docs-md/containers/overlays.md +3 -1
  78. package/docs-md/containers/side-menu.md +4 -2
  79. package/docs-md/containers/tabs.md +2 -0
  80. package/docs-md/flows/forms-in-a-modal.md +1 -1
  81. package/docs-md/help/glossary.md +2 -2
  82. package/docs-md/help/troubleshooting.md +20 -3
  83. package/docs-md/index.json +12 -0
  84. package/docs-md/migration/from-react-native-navigation.md +8 -2
  85. package/docs-md/screen-ui/back-and-gestures.md +8 -2
  86. package/docs-md/screen-ui/system-ui.md +1 -1
  87. package/docs-md/start/installation.md +3 -1
  88. package/ios/CLAUDE.md +17 -5
  89. package/ios/CORE.md +13 -8
  90. package/ios/Contract/ANNCommandAnswer.mm +5 -20
  91. package/ios/Contract/ANNCommandContract.h +15 -0
  92. package/ios/Contract/ANNCommandContract.mm +78 -0
  93. package/ios/Core/ANNAppearanceDispatcher.mm +1 -1
  94. package/ios/Core/ANNErrorHandler.h +1 -1
  95. package/ios/Core/ANNErrorHandler.mm +3 -1
  96. package/ios/Core/ANNEventEmitter.h +1 -1
  97. package/ios/Core/ANNEventEmitter.mm +2 -2
  98. package/ios/Core/ANNJavaScriptCalls.h +34 -0
  99. package/ios/Core/ANNJavaScriptCalls.mm +61 -0
  100. package/ios/Core/ANNNavigationState.h +7 -3
  101. package/ios/Core/ANNNavigationState.mm +10 -5
  102. package/ios/Core/ANNNodeHelper.h +32 -12
  103. package/ios/Core/ANNNodeHelper.mm +100 -3
  104. package/ios/Core/ANNRenderWait.h +15 -1
  105. package/ios/Core/ANNRenderWait.mm +119 -17
  106. package/ios/Core/UIViewController+LayoutProtocol.h +9 -2
  107. package/ios/Core/UIViewController+LayoutProtocol.mm +8 -0
  108. package/ios/Layouts/Modal/ANNModalCoordinator.h +1 -1
  109. package/ios/Layouts/Modal/ANNModalCoordinator.mm +55 -14
  110. package/ios/Layouts/Screen/ANNComponentViewController.h +8 -0
  111. package/ios/Layouts/Screen/ANNComponentViewController.mm +32 -5
  112. package/ios/Layouts/Screen/ANNScreenPlace.h +41 -0
  113. package/ios/Layouts/Screen/ANNScreenPlace.mm +175 -0
  114. package/ios/Layouts/SplitView/ANNSplitViewCommands.h +3 -2
  115. package/ios/Layouts/SplitView/ANNSplitViewCommands.mm +105 -39
  116. package/ios/Layouts/SplitView/ANNSplitViewController.mm +4 -2
  117. package/ios/Layouts/Stack/ANNStackCommands.mm +1 -0
  118. package/ios/Layouts/Stack/ANNStackController.mm +26 -3
  119. package/ios/Layouts/Stack/ANNStackQueue.h +3 -1
  120. package/ios/Layouts/Stack/ANNStackQueue.mm +56 -43
  121. package/ios/Layouts/Stack/UINavigationController+ANNCommands.h +4 -2
  122. package/ios/Layouts/Stack/UINavigationController+ANNCommands.mm +2 -3
  123. package/ios/Layouts/Tabs/ANNBottomTabItemBuilder.h +4 -0
  124. package/ios/Layouts/Tabs/ANNBottomTabItemBuilder.mm +11 -0
  125. package/ios/Layouts/Tabs/ANNBottomTabsController.mm +13 -5
  126. package/ios/Layouts/Tabs/ANNBottomTabsPresenter.h +2 -2
  127. package/ios/Layouts/Tabs/ANNBottomTabsPresenter.mm +109 -7
  128. package/ios/Options/ANNOptionsCache.h +7 -6
  129. package/ios/Options/ANNSearchBarOptions.h +4 -3
  130. package/ios/Options/ANNUserChoice.h +57 -0
  131. package/ios/Options/ANNUserChoice.mm +44 -0
  132. package/ios/Options/Params/ANNImageSource.h +61 -0
  133. package/ios/Options/Params/ANNImageSource.mm +101 -0
  134. package/ios/Options/Params/ANNOptionImages.h +37 -0
  135. package/ios/Options/Params/ANNOptionImages.mm +284 -0
  136. package/ios/Options/Params/Image.h +11 -2
  137. package/ios/Options/Params/Image.mm +15 -2
  138. package/ios/Options/Params/ImageParser.h +5 -5
  139. package/ios/Options/Params/ImageParser.mm +28 -46
  140. package/ios/Public/ApexNavigation/ApexNavigation.h +1 -1
  141. package/ios/RECIPES.md +11 -5
  142. package/ios/React/Slots/ANNSlotComponentView.mm +24 -17
  143. package/ios/React/Slots/ANNSlotContainers.h +14 -6
  144. package/ios/React/Slots/ANNSlotContainers.mm +75 -2
  145. package/ios/TopBar/ANNBarButtons.h +9 -5
  146. package/ios/TopBar/ANNBarButtons.mm +211 -63
  147. package/ios/TopBar/ANNIconButtonImage.h +1 -0
  148. package/ios/TopBar/ANNIconButtonImage.mm +34 -1
  149. package/ios/TopBar/ANNLargeTitlePresenter.h +13 -0
  150. package/ios/TopBar/ANNLargeTitlePresenter.mm +26 -3
  151. package/ios/TopBar/ANNSearchPresenter.mm +170 -19
  152. package/ios/TopBar/ANNTopBarPresenter.h +5 -0
  153. package/ios/TopBar/ANNTopBarPresenter.mm +60 -11
  154. package/ios/TurboModules/ANNTurboModule.mm +39 -21
  155. package/lib/module/ARCHITECTURE.md +18 -18
  156. package/lib/module/adapters/NativeANNTurboModule.js.map +1 -1
  157. package/lib/module/api/hooks/useNavigation.js +8 -3
  158. package/lib/module/api/hooks/useNavigation.js.map +1 -1
  159. package/lib/module/api/navigation.js.map +1 -1
  160. package/lib/module/engine/commands.js +9 -7
  161. package/lib/module/engine/commands.js.map +1 -1
  162. package/lib/module/engine/layout.js +13 -9
  163. package/lib/module/engine/layout.js.map +1 -1
  164. package/lib/module/engine/options.js +44 -11
  165. package/lib/module/engine/options.js.map +1 -1
  166. package/lib/module/engine/places.js +44 -7
  167. package/lib/module/engine/places.js.map +1 -1
  168. package/lib/module/engine/props.js +30 -1
  169. package/lib/module/engine/props.js.map +1 -1
  170. package/lib/module/engine/rememberedOptions.js +1 -1
  171. package/lib/module/engine/rememberedOptions.js.map +1 -1
  172. package/lib/module/engine/screens.js +22 -12
  173. package/lib/module/engine/screens.js.map +1 -1
  174. package/lib/module/interfaces/contract/commands.json +101 -0
  175. package/lib/typescript/adapters/NativeANNTurboModule.d.ts +5 -2
  176. package/lib/typescript/adapters/NativeANNTurboModule.d.ts.map +1 -1
  177. package/lib/typescript/api/events/navigationEvents.d.ts +1 -1
  178. package/lib/typescript/api/events/navigationEvents.d.ts.map +1 -1
  179. package/lib/typescript/api/hooks/useNavigation.d.ts +1 -1
  180. package/lib/typescript/api/hooks/useNavigation.d.ts.map +1 -1
  181. package/lib/typescript/api/navigation.d.ts +4 -1
  182. package/lib/typescript/api/navigation.d.ts.map +1 -1
  183. package/lib/typescript/engine/commands.d.ts.map +1 -1
  184. package/lib/typescript/engine/options.d.ts +5 -2
  185. package/lib/typescript/engine/options.d.ts.map +1 -1
  186. package/lib/typescript/engine/places.d.ts +5 -2
  187. package/lib/typescript/engine/places.d.ts.map +1 -1
  188. package/lib/typescript/engine/props.d.ts +8 -1
  189. package/lib/typescript/engine/props.d.ts.map +1 -1
  190. package/lib/typescript/engine/rememberedOptions.d.ts.map +1 -1
  191. package/lib/typescript/engine/screens.d.ts.map +1 -1
  192. package/lib/typescript/interfaces/ComponentEvents.d.ts +2 -2
  193. package/lib/typescript/interfaces/ComponentEvents.d.ts.map +1 -1
  194. package/lib/typescript/interfaces/Events.d.ts +19 -10
  195. package/lib/typescript/interfaces/Events.d.ts.map +1 -1
  196. package/lib/typescript/interfaces/options/animations.d.ts +15 -7
  197. package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
  198. package/lib/typescript/interfaces/options/buttons.d.ts +13 -5
  199. package/lib/typescript/interfaces/options/buttons.d.ts.map +1 -1
  200. package/lib/typescript/interfaces/options/modals.d.ts +18 -13
  201. package/lib/typescript/interfaces/options/modals.d.ts.map +1 -1
  202. package/lib/typescript/interfaces/options/system.d.ts +6 -3
  203. package/lib/typescript/interfaces/options/system.d.ts.map +1 -1
  204. package/lib/typescript/interfaces/options/topBar.d.ts +22 -9
  205. package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
  206. package/package.json +1 -1
  207. package/src/ARCHITECTURE.md +18 -18
  208. package/src/adapters/NativeANNTurboModule.ts +5 -2
  209. package/src/api/events/navigationEvents.ts +1 -1
  210. package/src/api/hooks/useNavigation.ts +9 -4
  211. package/src/api/navigation.ts +4 -1
  212. package/src/engine/commands.ts +19 -7
  213. package/src/engine/layout.ts +16 -9
  214. package/src/engine/options.ts +39 -11
  215. package/src/engine/places.ts +56 -7
  216. package/src/engine/props.ts +30 -1
  217. package/src/engine/rememberedOptions.ts +4 -1
  218. package/src/engine/screens.tsx +23 -13
  219. package/src/interfaces/ComponentEvents.ts +2 -2
  220. package/src/interfaces/Events.ts +19 -10
  221. package/src/interfaces/contract/commands.json +101 -0
  222. package/src/interfaces/options/animations.ts +15 -7
  223. package/src/interfaces/options/buttons.ts +13 -5
  224. package/src/interfaces/options/modals.ts +18 -13
  225. package/src/interfaces/options/system.ts +6 -3
  226. package/src/interfaces/options/topBar.ts +22 -9
@@ -15,6 +15,7 @@ import dev.apexnative.navigation.viewcontrollers.component.Readiness
15
15
  import dev.apexnative.navigation.viewcontrollers.component.RenderWait
16
16
  import dev.apexnative.navigation.viewcontrollers.parent.OptionsScreen
17
17
  import dev.apexnative.navigation.viewcontrollers.stack.StackDisplay.BarMotion
18
+ import dev.apexnative.navigation.viewcontrollers.stack.StackDisplay.BarTravel
18
19
  import dev.apexnative.navigation.viewcontrollers.stack.StackDisplay.BarVisibility
19
20
  import dev.apexnative.navigation.viewcontrollers.stack.StackDisplay.Custom
20
21
  import dev.apexnative.navigation.viewcontrollers.stack.StackDisplay.Elements
@@ -51,8 +52,12 @@ private typealias Node = ViewController<*>
51
52
  *
52
53
  * Its waits for a screen to render are the core's one wait ([RenderWait]), on the screen it pushes or sets as the top;
53
54
  * the readiness signals reach it through [deliver] (the stack posts them to the next turn of the main loop: a signal
54
- * that comes inside a layout pass or a view's attach moves no view of the container there). A push or a
55
- * `setStackRoot` with elements waits for its targets after that, a wait like the others: a cut or a replace during it
55
+ * that comes inside a layout pass or a view's attach moves no view of the container there). A push of a screen with
56
+ * React content waits for its first mount: [PUSH_WAIT_MILLIS] at most without `waitForRender`, as long as it takes
57
+ * with `true`, not at all with `false`; its signal reaches the wait through [afterCalls], behind the calls JavaScript
58
+ * made in the commit of that mount (its `mergeOptions`), so the transition starts with the options of that commit —
59
+ * content mounted already goes that way too. A push or a `setStackRoot` with elements waits for its targets after
60
+ * that, a wait like the others, within what is left of its limit without `waitForRender`: a cut or a replace during it
56
61
  * interrupts the push, which moves no view into a layer.
57
62
  *
58
63
  * The transition is the screens' own when the options say so (`AnimationRules.stackIsOwn`): the display plays each
@@ -65,18 +70,31 @@ internal class StackQueue(
65
70
  private val scheduler: AppearanceDispatcher.Scheduler,
66
71
  private val host: Host,
67
72
  private val deliver: (() -> Unit) -> Unit = { it() },
73
+ /**
74
+ * How the mount of a pushed screen reaches the push's wait: on the main thread behind every call JavaScript made
75
+ * before it (the stack: `PendingCalls`, on a later turn of the main loop); at once by default.
76
+ */
77
+ private val afterCalls: (() -> Unit) -> Unit = { it() },
68
78
  ) {
69
79
  /** What the queue tells the stack. */
70
80
  interface Host {
71
81
  /** The current child (the top of [shown]) changed: a transition started, or an operation ran at once. */
72
82
  fun currentChildChanged()
73
83
 
84
+ /** An operation was accepted: a back gesture's preview that shows ends, the stack as it was. */
85
+ fun operationAccepted() = Unit
86
+
74
87
  /**
75
88
  * The current child is about to change, right before [currentChildChanged]: [motion] is the default transition
76
89
  * the bar's row goes with, none when the change comes at once or with an animation of its own; [visibility] is
77
- * how the bar shows or hides when the new top wants it otherwise.
90
+ * how the bar shows or hides when the new top wants it otherwise; [travel]: the bar goes with the screens of an
91
+ * animation of its own without elements.
78
92
  */
79
- fun barTransition(motion: StackDisplay.BarMotion?, visibility: BarVisibility) = Unit
93
+ fun barTransition(
94
+ motion: StackDisplay.BarMotion?,
95
+ visibility: BarVisibility,
96
+ travel: StackDisplay.BarTravel? = null,
97
+ ) = Unit
80
98
 
81
99
  /**
82
100
  * The current child is about to change, right before [barTransition]: [move] is how the transition goes, which
@@ -95,10 +113,10 @@ internal class StackQueue(
95
113
  fun operationEnded(topSettled: Boolean)
96
114
 
97
115
  /**
98
- * Whether [node] is a React screen with a root of its own, which a push prepares when its switch is on
99
- * ([StackSwitches.preparesOwnRootPushes]).
116
+ * Whether [node] shows React content whose first mount its push waits for: a React screen (a root of its own or
117
+ * a slot), or an entry that shows one. A native screen has none: its push starts at once.
100
118
  */
101
- fun isOwnRootScreen(node: Node): Boolean = false
119
+ fun hasReactContent(node: Node): Boolean = false
102
120
 
103
121
  /** One line for the log: a failure that has no command to answer. */
104
122
  fun log(text: String) = Unit
@@ -140,6 +158,9 @@ internal class StackQueue(
140
158
  private var takenOver = false
141
159
  private var destroyed = false
142
160
 
161
+ /** [node]'s content is made and nothing waits for it to render as a lower screen. */
162
+ fun isRendered(node: Node): Boolean = node !in unrendered && node !in lowerWaits
163
+
143
164
  /** The stack as JavaScript sees it, bottom to top. */
144
165
  val entries: List<Node>
145
166
  get() = entryList
@@ -199,7 +220,9 @@ internal class StackQueue(
199
220
 
200
221
  /**
201
222
  * A cut to [target], an entry that becomes the top: every entry above it leaves. [answer] gets [value]; a back of
202
- * the user has none. [silent]: no animation and no appearance events (`popToRootOnBlur`).
223
+ * the user has none. [silent]: no animation and no appearance events (`popToRootOnBlur`). [gesture]: the release of
224
+ * a back gesture whose preview shows the pop already (`StackDisplay.finishBackGesture` ends it, the bar shows the
225
+ * target at once).
203
226
  */
204
227
  fun cut(
205
228
  target: Node,
@@ -208,6 +231,7 @@ internal class StackQueue(
208
231
  what: String,
209
232
  value: String?,
210
233
  silent: Boolean = false,
234
+ gesture: Boolean = false,
211
235
  ) {
212
236
  val index = entryList.indexOf(target)
213
237
  if (destroyed || index < 0) return refuse(answer, null, componentId, emptyList())
@@ -219,7 +243,7 @@ internal class StackQueue(
219
243
  (waiting as? ReplaceOp)?.let(::completeAtOnce)
220
244
  val showsAbove = target in shownList && shownList.last() !== target
221
245
  val op = if (showsAbove) {
222
- CutOp(seq, answer, componentId, what, target, value, silent)
246
+ CutOp(seq, answer, componentId, what, target, value, silent, gesture)
223
247
  } else {
224
248
  // A cut that moves nothing more: its answer at its turn.
225
249
  NoneOp(seq, answer, null, componentId, what, value, null)
@@ -341,6 +365,8 @@ internal class StackQueue(
341
365
  // The table.
342
366
 
343
367
  private fun enqueue(op: Op) {
368
+ // Anything but the release of a back gesture ends its preview first.
369
+ if (!(op is CutOp && op.gesture)) host.operationAccepted()
344
370
  queued.addLast(op)
345
371
  drain()
346
372
  }
@@ -410,11 +436,12 @@ internal class StackQueue(
410
436
  val node = op.node
411
437
  val push = resolved(node).animations.push
412
438
  op.awaitsTargets = awaitsTargets(push, !push.enabled.isFalse)
439
+ // A prepared screen rendered before its push; `false`, and a node without React content, start at once.
413
440
  val wait = when {
414
441
  op.prepared -> Wait.Prepared
415
442
  push.waitForRender.isTrue -> Wait.Content
416
- StackSwitches.preparesOwnRootPushes && host.isOwnRootScreen(node) -> Wait.Limited
417
- else -> Wait.None
443
+ push.waitForRender.isFalse || !host.hasReactContent(node) -> Wait.None
444
+ else -> Wait.Limited
418
445
  }
419
446
  if (wait == Wait.None) return if (op.awaitsTargets) awaitTargets(op) else beginPush(op)
420
447
  waiting = op
@@ -422,18 +449,37 @@ internal class StackQueue(
422
449
  Wait.Prepared -> awaitPrepared(op)
423
450
  Wait.Content -> {
424
451
  // Then the components of the bar drawn in advance, 0.5 s at most after the content (a title or a
425
- // background with its own `waitForRender: false` not).
452
+ // background with its own `waitForRender: false` not). React content mounted already goes behind the
453
+ // calls too; a native screen starts at once.
426
454
  display.placeWaiting(node)
427
- awaitContent(op, node, limit = null, bar = RenderWait.Bar.AfterContent)
455
+ awaitContent(
456
+ op,
457
+ node,
458
+ limit = null,
459
+ bar = RenderWait.Bar.AfterContent,
460
+ deliver = afterCalls,
461
+ deliversReady = host.hasReactContent(node),
462
+ )
428
463
  }
429
464
  else -> {
465
+ // The screen renders hidden under the top, its signal (or its content mounted already) behind the
466
+ // calls; the bar's components don't count. The 0.5 s hold for the content and the targets together.
430
467
  display.placeWaiting(node)
431
- awaitContent(op, node, limit = OWN_ROOT_WAIT_MILLIS, bar = RenderWait.Bar.None)
468
+ op.cancelLimit = scheduler.schedule(PUSH_WAIT_MILLIS) { op.overLimit = true }
469
+ awaitContent(
470
+ op,
471
+ node,
472
+ limit = PUSH_WAIT_MILLIS,
473
+ bar = RenderWait.Bar.None,
474
+ deliver = afterCalls,
475
+ deliversReady = true,
476
+ )
432
477
  }
433
478
  }
434
479
  }
435
480
 
436
481
  private fun beginPush(op: PushOp) {
482
+ op.cancelLimit?.invoke()
437
483
  val node = op.node
438
484
  val covered = shownList.lastOrNull()
439
485
  // The views first: a failure here leaves the lists as they were.
@@ -450,7 +496,7 @@ internal class StackQueue(
450
496
  val elements = own && AnimationRules.hasElements(push)
451
497
  // The bar's row goes with the default transition only; with an animation of its own it changes at once. The tab
452
498
  // bar moves with its screens or by its own side.
453
- tellBars(animated, own, elements, push, TabBarMotion.Move.WithPush, BarMotion.Push)
499
+ tellBars(animated, own, elements, push, TabBarMotion.Move.WithPush, BarMotion.Push, BarTravel.Forward)
454
500
  host.currentChildChanged()
455
501
  finishTakenOver(ended)
456
502
  running += op
@@ -488,6 +534,7 @@ internal class StackQueue(
488
534
 
489
535
  // Off screen or without a view: at once, without a transition; the top appears when the stack shows.
490
536
  private fun pushAtOnce(op: PushOp) {
537
+ op.cancelLimit?.invoke()
491
538
  val node = op.node
492
539
  val covered = shownList.lastOrNull()
493
540
  if (display.hasView) {
@@ -516,6 +563,7 @@ internal class StackQueue(
516
563
  if (op.done) return
517
564
  op.done = true
518
565
  if (waiting === op) stopWaiting(op)
566
+ op.cancelLimit?.invoke()
519
567
  queued.remove(op)
520
568
  val node = op.node
521
569
  entryList.remove(node)
@@ -581,7 +629,9 @@ internal class StackQueue(
581
629
  val animated = !atOnce && !op.silent && display.canAnimate && !pop.enabled.isFalse
582
630
  val own = animated && AnimationRules.stackIsOwn(pop)
583
631
  val elements = own && AnimationRules.hasElements(pop)
584
- tellBars(animated, own, elements, pop, TabBarMotion.Move.WithPop, BarMotion.Pop)
632
+ // A back gesture's preview shows the target and its bar already: the bar stays as it is.
633
+ val gesture = op.gesture && animated && !own && leaving.size == 1 && display.hasBackPreview(top, target)
634
+ tellBars(animated, own, elements, pop, TabBarMotion.Move.WithPop, BarMotion.Pop, BarTravel.Back, gesture)
585
635
  host.currentChildChanged()
586
636
  finishTakenOver(ended)
587
637
  if (display.hasView) target.onViewBroughtToFront()
@@ -592,6 +642,17 @@ internal class StackQueue(
592
642
  // The screens between the top and the target leave without animation.
593
643
  leaving.drop(1).forEach(display::hide)
594
644
  if (!animated) return finishCut(op)
645
+ if (gesture) {
646
+ op.animating += top
647
+ op.animating += target
648
+ display.finishBackGesture(
649
+ top,
650
+ target,
651
+ { step(op) { screenDone(op, top) } },
652
+ { step(op) { screenDone(op, target) } },
653
+ )
654
+ return
655
+ }
595
656
  // Its own: no wait on a pop; the leaving screen starts from where it is.
596
657
  if (own) return animate(op, backMoves(top, target, pop, elements, moving))
597
658
  animate(op, listOf(Move(top, Motion.PopOut, true), Move(target, Motion.PopReveal, moving)))
@@ -657,7 +718,7 @@ internal class StackQueue(
657
718
  val elements = own && !op.asPush && AnimationRules.hasElements(played)
658
719
  val motion = if (op.asPush) BarMotion.Push else BarMotion.FadeThrough
659
720
  val system = if (op.asPush) TabBarMotion.Move.WithPush else TabBarMotion.Move.Slide
660
- tellBars(animated, own, elements, played, system, motion)
721
+ tellBars(animated, own, elements, played, system, motion, BarTravel.Forward)
661
722
  host.currentChildChanged()
662
723
  finishTakenOver(ended)
663
724
  op.owned += moved
@@ -769,8 +830,16 @@ internal class StackQueue(
769
830
 
770
831
  // The one wait (RenderWait) for [screen], the node the operation brings: at [limit] the content not ready counts
771
832
  // as mounted; without one, `waitForRender: true` waits as long as it takes, except for content the readiness store
772
- // knows nothing of after 1 s, which counts as ready (the same rule as iOS).
773
- private fun awaitContent(op: Op, screen: Node, limit: Long?, bar: RenderWait.Bar) {
833
+ // knows nothing of after 1 s, which counts as ready (the same rule as iOS). A push's signals come through
834
+ // [afterCalls] ([deliver]); with [deliversReady], so does content ready at the start.
835
+ private fun awaitContent(
836
+ op: Op,
837
+ screen: Node,
838
+ limit: Long?,
839
+ bar: RenderWait.Bar,
840
+ deliver: (() -> Unit) -> Unit = this.deliver,
841
+ deliversReady: Boolean = false,
842
+ ) {
774
843
  val wait = RenderWait(
775
844
  readiness = readiness,
776
845
  scheduler = scheduler,
@@ -778,6 +847,7 @@ internal class StackQueue(
778
847
  bar = bar,
779
848
  countsUnknownContent = true,
780
849
  deliver = deliver,
850
+ deliversReadyContent = deliversReady,
781
851
  step = { work -> step(op) { work() } },
782
852
  onEnd = { endWait(op) },
783
853
  )
@@ -798,15 +868,32 @@ internal class StackQueue(
798
868
  step(op) { wait.retake(RenderWait.waitedScreens(node)) }
799
869
  }
800
870
 
871
+ // The prepared screen's slot mounts (or the display's limit passes), then its transition starts behind the calls
872
+ // JavaScript made before ([afterCalls]): a commit that came after the push (its JavaScript's own limit) brings its
873
+ // `mergeOptions` first. The same 0.5 s from the start hold for both: the transition starts then anyway.
801
874
  private fun awaitPrepared(op: PushOp) {
802
875
  val token = Any()
803
876
  op.waitToken = token
804
877
  val node = op.node
878
+ val still = { !destroyed && !op.done && op.waitToken === token && waiting === op && op.isCurrent() }
879
+ var mounted = false
880
+ val markMounted = {
881
+ if (!mounted && !readiness.isReady(node)) readiness.markReady(node)
882
+ mounted = true
883
+ }
884
+ val start = { step(op) { if (still()) endWait(op) } }
885
+ op.cancelCap = scheduler.schedule(PUSH_WAIT_MILLIS) {
886
+ step(op) {
887
+ if (!still()) return@step
888
+ markMounted()
889
+ endWait(op)
890
+ }
891
+ }
805
892
  display.awaitPrepared(
806
893
  node,
807
- waiting = { !destroyed && !op.done && op.waitToken === token && waiting === op && op.isCurrent() },
808
- mounted = { step(op) { if (!readiness.isReady(node)) readiness.markReady(node) } },
809
- start = { step(op) { if (waiting === op) endWait(op) } },
894
+ waiting = still,
895
+ mounted = { step(op) { markMounted() } },
896
+ start = { afterCalls(start) },
810
897
  )
811
898
  }
812
899
 
@@ -835,7 +922,8 @@ internal class StackQueue(
835
922
  val token = Any()
836
923
  op.waitToken = token
837
924
  val still = { !destroyed && !op.done && op.waitToken === token && waiting === op && op.isCurrent() }
838
- // Without `waitForRender` a `setStackRoot`'s 0.5 s holds for its content and its targets together.
925
+ // Without `waitForRender` the 0.5 s of a `setStackRoot` and of a push of React content hold for the content
926
+ // and the targets together.
839
927
  val from = currentChild
840
928
  op.stopTargets = display.awaitTargets(animation, from, to, { still() && !op.overLimit }) {
841
929
  step(op) { if (still()) endWait(op) }
@@ -844,6 +932,8 @@ internal class StackQueue(
844
932
 
845
933
  private fun stopWaiting(op: Op) {
846
934
  if (waiting === op) waiting = null
935
+ op.cancelCap?.invoke()
936
+ op.cancelCap = null
847
937
  op.waitToken = null
848
938
  op.wait?.cancel()
849
939
  op.wait = null
@@ -1081,9 +1171,10 @@ internal class StackQueue(
1081
1171
  private fun awaitsTargets(animation: StackAnimationOptions, enabled: Boolean): Boolean = enabled &&
1082
1172
  display.canAnimate && !animation.waitForRender.isFalse && AnimationRules.hasElements(animation)
1083
1173
 
1084
- // The bars with a change of the current child. The row goes with the default transition only; the tab bar rides
1085
- // with the system's [system] move, slides with an animation of its own or a side of its own, and is where the new
1086
- // top wants it at once without animation; with elements every bar changes at once.
1174
+ // The bars with a change of the current child. The row goes with the default transition; with an animation of its
1175
+ // own the top bar goes with the screens ([travel]). The tab bar rides with the system's [system] move, slides with an
1176
+ // 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.
1087
1178
  private fun tellBars(
1088
1179
  animated: Boolean,
1089
1180
  own: Boolean,
@@ -1091,6 +1182,9 @@ internal class StackQueue(
1091
1182
  animation: StackAnimationOptions,
1092
1183
  system: TabBarMotion.Move,
1093
1184
  row: BarMotion,
1185
+ 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.
1187
+ barAtOnce: Boolean = false,
1094
1188
  ) {
1095
1189
  val tabs = animation.bottomTabs
1096
1190
  val tabsSide = AnimationRules.barSideGiven(tabs.enter) || AnimationRules.barSideGiven(tabs.exit)
@@ -1100,7 +1194,12 @@ internal class StackQueue(
1100
1194
  else -> system
1101
1195
  }
1102
1196
  host.tabBarTransition(move, tabs.takeIf { move == TabBarMotion.Move.Slide })
1103
- host.barTransition(if (animated && !own) row else null, barVisibility(animated && !elements, animation.topBar))
1197
+ val barMoves = animated && !barAtOnce
1198
+ host.barTransition(
1199
+ if (barMoves && !own) row else null,
1200
+ barVisibility(barMoves && !elements, animation.topBar),
1201
+ travel.takeIf { barMoves && own && !elements },
1202
+ )
1104
1203
  }
1105
1204
 
1106
1205
  private fun barVisibility(animated: Boolean, side: TopBarEnterExitAnimationOptions): BarVisibility =
@@ -1179,9 +1278,12 @@ internal class StackQueue(
1179
1278
  // It waits for its elements' targets after its content; what stops that wait.
1180
1279
  var awaitsTargets = false
1181
1280
  var stopTargets: (() -> Unit)? = null
1182
- // The limit of a `setStackRoot` without `waitForRender` passed: its targets get nothing of it any more.
1281
+ // The limit of a `setStackRoot` or a push without `waitForRender` passed: its targets get nothing of it any
1282
+ // more.
1183
1283
  var overLimit = false
1184
1284
  var cancelLimit: (() -> Unit)? = null
1285
+ // The limit of a prepared push's wait, which ends with the wait.
1286
+ var cancelCap: (() -> Unit)? = null
1185
1287
  // Its screen came by an animation of its own: it stays at that animation's end values.
1186
1288
  var keepsEnd = false
1187
1289
  // The listener of a module heard its answer: it hears one only.
@@ -1209,6 +1311,7 @@ internal class StackQueue(
1209
1311
  val target: Node,
1210
1312
  val value: String?,
1211
1313
  val silent: Boolean,
1314
+ val gesture: Boolean,
1212
1315
  ) : Op(seq, answer, null, componentId, what) {
1213
1316
  var leaving: List<Node> = emptyList()
1214
1317
  }
@@ -1255,20 +1358,13 @@ internal class StackQueue(
1255
1358
  /** How long a lower screen stays placed in the container to make its content, at most. */
1256
1359
  const val LOWER_SCREEN_MILLIS = 500L
1257
1360
 
1258
- /** How long the push of a screen with its own root waits for its content, with the switch on. */
1259
- const val OWN_ROOT_WAIT_MILLIS = 500L
1361
+ /**
1362
+ * How long a push waits for its screen's first mount without `waitForRender`, and a prepared push for its slot
1363
+ * and the calls before it.
1364
+ */
1365
+ const val PUSH_WAIT_MILLIS = 500L
1260
1366
 
1261
1367
  // The alpha an arriving screen shows with: its own at rest, never the 0 of its wait.
1262
1368
  private const val ARRIVING_ALPHA = 1f
1263
1369
  }
1264
1370
  }
1265
-
1266
- /**
1267
- * The internal switches of the stack. [preparesOwnRootPushes]: the push of a React screen with a root of its own starts
1268
- * its surface hidden in the stack at its size and insets, and starts its transition once the content rendered, 0.5 s at
1269
- * most. Off by default («б»: the push starts at once); it is measured at the end on a phone.
1270
- */
1271
- internal object StackSwitches {
1272
- @Volatile
1273
- var preparesOwnRootPushes = false
1274
- }
@@ -16,11 +16,15 @@ import dev.apexnative.navigation.views.animations.SystemTransition
16
16
  * system's transition between activities (`SystemTransition`: 96 dp along the axis in 450 ms on
17
17
  * `fast_out_extra_slow_in`, mirrored right to left), `setStackRoot` is Material's fade through (`FadeThrough`). What
18
18
  * leaves starts from where it is; what comes in on a push is hidden explicitly until it fades in, as a delay doesn't
19
- * apply an animator's first value. Main thread only.
19
+ * apply an animator's first value. The system's windows also extend their edge by the distance they move
20
+ * ([extendsStart], `StackLayout.extendEdge`). Main thread only.
20
21
  */
21
22
  internal object StackTransitions {
22
- /** The animator of [view] for [motion]; [fromCurrent] for a screen taken over from a transition still running. */
23
- fun animator(view: View, motion: Motion, fromCurrent: Boolean): Animator {
23
+ /**
24
+ * The animator of [view] for [motion]; [fromCurrent] for a screen taken over from a transition still running.
25
+ * [onSlide] runs at each frame of its slide.
26
+ */
27
+ fun animator(view: View, motion: Motion, fromCurrent: Boolean, onSlide: () -> Unit = {}): Animator {
24
28
  val distance = SystemTransition.DISTANCE_DP * view.resources.displayMetrics.density
25
29
  // Forward is to the end of the line: right to left, the screens move the other way. The stack's view inherits
26
30
  // the app's direction, which `layout.direction` sets on the activity's content (`AppDirection`).
@@ -30,24 +34,37 @@ internal object StackTransitions {
30
34
  view.translationX = forward
31
35
  view.alpha = 0f
32
36
  together(
33
- slide(view, 0f),
37
+ slide(view, 0f, onSlide),
34
38
  fade(view, 1f, SystemTransition.PUSH_FADE_DELAY),
35
39
  )
36
40
  }
37
- Motion.PushCover -> slide(view, -forward)
41
+ Motion.PushCover -> slide(view, -forward, onSlide)
38
42
  Motion.PopOut -> together(
39
- slide(view, forward),
43
+ slide(view, forward, onSlide),
40
44
  fade(view, 0f, SystemTransition.POP_FADE_DELAY),
41
45
  )
42
46
  Motion.PopReveal -> {
43
47
  if (!fromCurrent) view.translationX = -forward
44
- if (view.alpha < 1f) together(slide(view, 0f), fade(view, 1f, 0L)) else slide(view, 0f)
48
+ if (view.alpha < 1f) together(slide(view, 0f, onSlide), fade(view, 1f, 0L)) else slide(view, 0f, onSlide)
45
49
  }
46
50
  Motion.FadeIn -> FadeThrough.enter(view)
47
51
  Motion.FadeOut -> FadeThrough.exit(view)
48
52
  }
49
53
  }
50
54
 
55
+ /**
56
+ * Whether a screen moving by [motion] extends its start edge (the side it moves away from on a push in and a pop
57
+ * out), its end edge (a push cover, a pop reveal), or none (a fade through: null), as the system's windows do.
58
+ */
59
+ fun extendsStart(motion: Motion): Boolean? = when (motion) {
60
+ Motion.PushIn, Motion.PopOut -> true
61
+ Motion.PushCover, Motion.PopReveal -> false
62
+ Motion.FadeIn, Motion.FadeOut -> null
63
+ }
64
+
65
+ /** Whether [motion] animates the screen's alpha: it draws through a hardware layer meanwhile. */
66
+ fun fades(motion: Motion): Boolean = motion != Motion.PushCover && motion != Motion.PopReveal
67
+
51
68
  /**
52
69
  * The fade of a screen around elements: in from 0 to 1, out from where it is to 0, linearly in
53
70
  * [AnimationRules.SCREEN_FADE]; none for [Fade.None].
@@ -65,10 +82,11 @@ internal object StackTransitions {
65
82
  }
66
83
  }
67
84
 
68
- private fun slide(view: View, to: Float): Animator =
85
+ private fun slide(view: View, to: Float, onFrame: () -> Unit): Animator =
69
86
  ObjectAnimator.ofFloat(view, View.TRANSLATION_X, view.translationX, to).apply {
70
87
  duration = SystemTransition.DURATION
71
88
  interpolator = SystemTransition.CURVE
89
+ addUpdateListener { onFrame() }
72
90
  }
73
91
 
74
92
  private fun fade(view: View, to: Float, delay: Long): Animator =
@@ -9,6 +9,7 @@ import androidx.core.view.WindowInsetsCompat
9
9
  import androidx.lifecycle.Lifecycle
10
10
  import androidx.lifecycle.LifecycleOwner
11
11
  import dev.apexnative.navigation.options.LayoutAnimation
12
+ import dev.apexnative.navigation.utils.UiThread
12
13
  import dev.apexnative.navigation.utils.whenContentMounted
13
14
  import dev.apexnative.navigation.viewcontrollers.component.ComponentViewController
14
15
  import dev.apexnative.navigation.viewcontrollers.externalcomponent.ExternalComponentViewController
@@ -16,6 +17,7 @@ import dev.apexnative.navigation.viewcontrollers.stack.StackDisplay.Motion
16
17
  import dev.apexnative.navigation.viewcontrollers.viewcontroller.ViewController
17
18
  import dev.apexnative.navigation.views.animations.AnimationRules
18
19
  import dev.apexnative.navigation.views.animations.SideAnimator
20
+ import dev.apexnative.navigation.views.animations.SystemTransition
19
21
  import dev.apexnative.navigation.views.element.ElementTransition
20
22
  import dev.apexnative.navigation.views.element.TargetsWait
21
23
  import dev.apexnative.navigation.views.stack.StackLayout
@@ -29,6 +31,11 @@ import dev.apexnative.navigation.views.stack.StackLayout
29
31
  */
30
32
  internal class StackViews(private val stack: StackController) : StackDisplay {
31
33
  private val animators = HashMap<ViewController<*>, Animator>()
34
+ // The preview of the back gesture that runs or ends now, and its two screens.
35
+ private var preview: BackPreview? = null
36
+ private var previewScreens: Pair<ViewController<*>, ViewController<*>>? = null
37
+ // The ends of the two screens of a released gesture whose preview plays its end.
38
+ private var committing: Pair<() -> Unit, () -> Unit>? = null
32
39
  // What goes back when a screen's animator ends or stops: the elements its transition carries.
33
40
  private val cleanups = HashMap<ViewController<*>, () -> Unit>()
34
41
 
@@ -110,11 +117,16 @@ internal class StackViews(private val stack: StackController) : StackDisplay {
110
117
  override fun animate(screen: ViewController<*>, motion: Motion, fromCurrent: Boolean, done: () -> Unit) {
111
118
  stop(screen)
112
119
  val view = screen.peekView() ?: return done()
113
- val animator = StackTransitions.animator(view, motion, fromCurrent)
120
+ val layout = layout
121
+ val animator = StackTransitions.animator(view, motion, fromCurrent) { layout?.edgesMoved() }
122
+ cleanups[screen] = dress(view, motion, layout)
114
123
  animator.addListener(object : AnimatorListenerAdapter() {
115
124
  // Ended or cancelled by anything else than [stop]: the screen's part is done.
116
125
  override fun onAnimationEnd(animation: Animator) {
117
- if (animators[screen] === animator) animators.remove(screen)
126
+ if (animators[screen] === animator) {
127
+ animators.remove(screen)
128
+ cleanups.remove(screen)?.invoke()
129
+ }
118
130
  done()
119
131
  }
120
132
  })
@@ -122,6 +134,21 @@ internal class StackViews(private val stack: StackController) : StackDisplay {
122
134
  animator.start()
123
135
  }
124
136
 
137
+ // For the default transition [view] moves as the system's windows do: its edge extended by the distance it moves,
138
+ // and its fade drawn through a hardware layer, so the whole screen isn't drawn again at every frame of it. What
139
+ // undoes both.
140
+ private fun dress(view: View, motion: Motion, layout: StackLayout?): () -> Unit {
141
+ StackTransitions.extendsStart(motion)?.let { start ->
142
+ layout?.extendEdge(view, start, (SystemTransition.DISTANCE_DP * view.resources.displayMetrics.density).toInt())
143
+ }
144
+ val layered = StackTransitions.fades(motion) && view.layerType == View.LAYER_TYPE_NONE
145
+ if (layered) view.setLayerType(View.LAYER_TYPE_HARDWARE, null)
146
+ return {
147
+ layout?.dropEdge(view)
148
+ if (layered && view.layerType == View.LAYER_TYPE_HARDWARE) view.setLayerType(View.LAYER_TYPE_NONE, null)
149
+ }
150
+ }
151
+
125
152
  // The elements go into a layer over the screens, and back as the screen's animator ends or stops. A shared element
126
153
  // without a duration takes the screen's fade: its own single `alpha`, else 300 ms.
127
154
  override fun animateCustom(screen: ViewController<*>, custom: StackDisplay.Custom, done: () -> Unit) {
@@ -167,7 +194,83 @@ internal class StackViews(private val stack: StackController) : StackDisplay {
167
194
  done: () -> Unit,
168
195
  ): () -> Unit = TargetsWait.start(animation, from, to, waiting, done)
169
196
 
197
+ /**
198
+ * Starts the preview of a back gesture over [top], [target] under it (`BackPreview`): none without the stack's view
199
+ * or a window. [onReveal] shows [target] and its bar as the preview's picture is there.
200
+ */
201
+ fun startBackPreview(
202
+ top: ViewController<*>,
203
+ target: ViewController<*>,
204
+ fromLeft: Boolean,
205
+ touchY: Float,
206
+ onReveal: () -> Unit,
207
+ ): BackPreview? {
208
+ val bottomRoom = top.getBottomInset()
209
+ endPreview()
210
+ val layout = layout ?: return null
211
+ val window = stack.activity.window ?: return null
212
+ val topView = top.peekView() ?: return null
213
+ val targetView = target.peekView() ?: return null
214
+ val started = BackPreview(layout, topView, targetView, fromLeft, touchY, bottomRoom, onReveal)
215
+ preview = started
216
+ previewScreens = top to target
217
+ started.start(window)
218
+ return started
219
+ }
220
+
221
+ /** The preview of the back gesture went: cancelled and put back, or never shown. */
222
+ fun previewEnded(ended: BackPreview) {
223
+ if (preview !== ended) return
224
+ preview = null
225
+ previewScreens = null
226
+ }
227
+
228
+ override fun hasBackPreview(top: ViewController<*>, target: ViewController<*>): Boolean =
229
+ preview?.isReady == true && previewScreens == top to target
230
+
231
+ override fun finishBackGesture(
232
+ top: ViewController<*>,
233
+ target: ViewController<*>,
234
+ topDone: () -> Unit,
235
+ targetDone: () -> Unit,
236
+ ) {
237
+ val running = preview?.takeIf { hasBackPreview(top, target) } ?: run {
238
+ topDone()
239
+ targetDone()
240
+ return
241
+ }
242
+ committing = topDone to targetDone
243
+ running.commit {
244
+ committing = null
245
+ previewEnded(running)
246
+ topDone()
247
+ targetDone()
248
+ }
249
+ }
250
+
251
+ private fun endPreview() {
252
+ val ended = preview ?: return
253
+ preview = null
254
+ previewScreens = null
255
+ committing = null
256
+ ended.endAtOnce()
257
+ }
258
+
259
+ // A later transition takes one screen of a released gesture's preview: the preview ends where it is, the popped
260
+ // screen stays out of sight until the pop takes it away, and the other screen's part ends on the next turn.
261
+ private fun stopPreview(screen: ViewController<*>) {
262
+ val (top, target) = previewScreens ?: return
263
+ if (screen !== top && screen !== target) return
264
+ val dones = committing
265
+ endPreview()
266
+ if (dones == null) return
267
+ top.peekView()?.alpha = 0f
268
+ val other = if (screen === top) dones.second else dones.first
269
+ UiThread.post { other() }
270
+ }
271
+
170
272
  override fun stop(screen: ViewController<*>) {
273
+ stopPreview(screen)
171
274
  val animator = animators.remove(screen)
172
275
  if (animator != null) {
173
276
  animator.removeAllListeners()
@@ -185,8 +288,8 @@ internal class StackViews(private val stack: StackController) : StackDisplay {
185
288
  screen.whenContentMounted(
186
289
  waiting = waiting,
187
290
  mounted = {
188
- // The frame of the mount: the screen shows transparent and lays out; its transition starts on the
189
- // pre-draw of that frame.
291
+ // The frame of the mount: the screen shows transparent and lays out. The pre-draw of that frame hands
292
+ // the start to the queue, whose transition begins behind the calls JavaScript made before.
190
293
  screen.peekView()?.let {
191
294
  it.alpha = 0f
192
295
  it.visibility = View.VISIBLE