@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
@@ -1,9 +1,16 @@
1
1
  package dev.apexnative.navigation.viewcontrollers.stack.fab
2
2
 
3
+ import android.animation.Animator
4
+ import android.animation.AnimatorListenerAdapter
5
+ import android.animation.AnimatorSet
6
+ import android.animation.ObjectAnimator
7
+ import android.animation.PropertyValuesHolder
3
8
  import android.app.Activity
4
9
  import android.view.Gravity
5
10
  import android.view.View
6
11
  import android.view.ViewGroup
12
+ import android.view.animation.AccelerateDecelerateInterpolator
13
+ import android.view.animation.LinearInterpolator
7
14
  import androidx.coordinatorlayout.widget.CoordinatorLayout
8
15
  import androidx.core.view.WindowInsetsCompat
9
16
  import dev.apexnative.navigation.options.FabOptions
@@ -11,16 +18,20 @@ import dev.apexnative.navigation.react.events.AppearanceDispatcher
11
18
  import dev.apexnative.navigation.utils.TestIds
12
19
  import dev.apexnative.navigation.viewcontrollers.viewcontroller.ScrollEventListener
13
20
  import dev.apexnative.navigation.viewcontrollers.viewcontroller.ViewController
21
+ import dev.apexnative.navigation.views.animations.FadeThrough
22
+ import dev.apexnative.navigation.views.animations.SystemTransition
14
23
  import dev.apexnative.navigation.views.stack.StackLayout
15
24
  import dev.apexnative.navigation.views.stack.fab.Fab
25
+ import dev.apexnative.navigation.views.stack.fab.FabCopy
16
26
  import dev.apexnative.navigation.views.stack.fab.FabMenu
17
27
 
18
28
  /**
19
29
  * The FAB of one stack (`fab.*`), over the module's `Fab` and `FabMenu`: one FAB or one menu at a time, from the
20
30
  * resolved `fab` of the screen on top (its options over the stack's, its parents' and the defaults). It follows the top
21
- * at the end of each transition and the merges of `fab` into it; a press sends `ANN.NavigationButtonPressed` with the
22
- * screen whose options showed it, an action with its own id. Its views are children of the stack's view, over the
23
- * screens and the bar. Main thread only.
31
+ * from the start of each transition, going with the screens by [FabMotion], and the merges of `fab` into it; a press
32
+ * sends `ANN.NavigationButtonPressed` with the screen whose options showed it, an action with its own id. Its views are
33
+ * children of the stack's view, over the screens and the bar; a FAB that leaves goes as a copy ([FabCopy]). Main
34
+ * thread only.
24
35
  */
25
36
  internal class FabPresenter(
26
37
  private val activity: Activity,
@@ -38,38 +49,75 @@ internal class FabPresenter(
38
49
  var source: ViewController<*>? = null
39
50
  private set
40
51
 
52
+ // The ride of the FAB that shows as it arrives; none at rest.
53
+ private var ride: Animator? = null
54
+ // The copies of the FABs that leave, each with its ride.
55
+ private val copies = LinkedHashMap<FabCopy, Animator>()
56
+
57
+ // The top's FAB while a back gesture's preview shows the screen under the top with its own: out of the stack, as
58
+ // it was (hidden by the scroll, say).
59
+ private class Aside(
60
+ val fab: Fab?,
61
+ val menu: FabMenu?,
62
+ val shownId: String?,
63
+ var source: ViewController<*>?,
64
+ val alignStart: Boolean,
65
+ )
66
+
67
+ private var aside: Aside? = null
68
+
41
69
  private val view: View?
42
70
  get() = menu ?: fab
43
71
 
72
+ /** The view of the FAB or of its menu that shows; none. */
73
+ val shownView: View?
74
+ get() = view
75
+
44
76
  /**
45
- * The screen on top became [source], with its resolved [options]: the same id updates the FAB that shows and grows
46
- * it again, another id replaces it, none takes it away; the scroll of [scroll] hides it with `hideOnScroll`.
47
- * Nothing when [source] already shows it.
77
+ * The screen on top became [source], with its resolved [options], in a transition that goes by [move]: the same id
78
+ * updates the FAB that shows, where it stays as it is, another id replaces it, none takes it away — the new FAB
79
+ * arrives and a copy of the old one leaves by [move]; the scroll of [scroll] hides it with `hideOnScroll`. An open
80
+ * menu closes. The copies of an older transition go, and with [FabMotion.Move.AtOnce] a FAB still riding comes to
81
+ * rest. Nothing when [source] already shows it.
48
82
  */
49
- fun showFor(layout: StackLayout, source: ViewController<*>?, options: FabOptions, scroll: ScrollEventListener?) {
83
+ fun showFor(
84
+ layout: StackLayout,
85
+ source: ViewController<*>?,
86
+ options: FabOptions,
87
+ scroll: ScrollEventListener?,
88
+ move: FabMotion.Move = FabMotion.Move.Swap,
89
+ ) {
90
+ // At once, what still moves comes to rest, also when the top stays (a failed push).
91
+ if (move == FabMotion.Move.AtOnce) {
92
+ dropCopies()
93
+ stopRide(rest = true)
94
+ }
50
95
  if (source === this.source && this.layout === layout) return
51
96
  this.layout = layout
52
- disableCollapse()
97
+ dropCopies()
53
98
  this.source = source
54
99
  when (FabPlan.step(shownId, menu != null, options)) {
55
100
  FabPlan.Step.None -> Unit
56
- FabPlan.Step.Remove -> remove()
57
- FabPlan.Step.Create -> create(layout, options, scroll)
101
+ FabPlan.Step.Remove -> leave(layout, move)
102
+ FabPlan.Step.Create -> {
103
+ create(layout, options, scroll)
104
+ arrive(move)
105
+ }
58
106
  FabPlan.Step.Replace -> {
59
- remove()
107
+ leave(layout, move)
60
108
  create(layout, options, scroll)
109
+ arrive(move)
61
110
  }
62
111
  FabPlan.Step.Update -> {
112
+ disableCollapse()
113
+ menu?.close()
63
114
  apply(options, scroll)
64
115
  // A new top whose options don't hide it shows it, even where the screen before hid it.
65
116
  if (!options.visible.isFalse) {
66
117
  fab?.show()
67
118
  menu?.show()
68
119
  }
69
- view?.let {
70
- it.bringToFront()
71
- grow(it)
72
- }
120
+ view?.bringToFront()
73
121
  }
74
122
  }
75
123
  }
@@ -83,11 +131,15 @@ internal class FabPresenter(
83
131
  this.source = source
84
132
  when (FabPlan.step(shownId, menu != null, options)) {
85
133
  FabPlan.Step.None -> Unit
86
- FabPlan.Step.Remove -> remove()
87
- FabPlan.Step.Create -> create(layout, options, scroll)
134
+ FabPlan.Step.Remove -> leave(layout, FabMotion.Move.Swap)
135
+ FabPlan.Step.Create -> {
136
+ create(layout, options, scroll)
137
+ arrive(FabMotion.Move.Swap)
138
+ }
88
139
  FabPlan.Step.Replace -> {
89
- remove()
140
+ leave(layout, FabMotion.Move.Swap)
90
141
  create(layout, options, scroll)
142
+ arrive(FabMotion.Move.Swap)
91
143
  }
92
144
  FabPlan.Step.Update -> apply(options, scroll)
93
145
  }
@@ -130,13 +182,57 @@ internal class FabPresenter(
130
182
  view.layoutParams = params
131
183
  }
132
184
 
185
+ /**
186
+ * A back gesture's preview shows the screen under the top: the FAB that shows goes aside, out of the stack but as it
187
+ * is, its menu closing, until [bringBack] or [dropAside]; the screen's own FAB shows next ([showFor]).
188
+ */
189
+ fun setAside() {
190
+ if (aside != null) return
191
+ dropCopies()
192
+ stopRide(rest = true)
193
+ menu?.close()
194
+ view?.let { (it.parent as? ViewGroup)?.removeView(it) }
195
+ aside = Aside(fab, menu, shownId, source, alignStart)
196
+ fab = null
197
+ menu = null
198
+ shownId = null
199
+ source = null
200
+ }
201
+
202
+ /** The gesture was cancelled: the FAB the preview showed goes, and the one set aside is back as it was. */
203
+ fun bringBack() {
204
+ val kept = aside ?: return
205
+ aside = null
206
+ val layout = layout ?: return discard(kept)
207
+ leave(layout, FabMotion.Move.AtOnce)
208
+ fab = kept.fab
209
+ menu = kept.menu
210
+ shownId = kept.shownId
211
+ source = kept.source
212
+ alignStart = kept.alignStart
213
+ view?.let { layout.addOverAll(it, it.layoutParams as? CoordinatorLayout.LayoutParams ?: params()) }
214
+ place()
215
+ }
216
+
217
+ /** The gesture was released: the FAB set aside goes with the top that leaves. */
218
+ fun dropAside() {
219
+ val kept = aside ?: return
220
+ aside = null
221
+ discard(kept)
222
+ }
223
+
133
224
  /** [node] was destroyed: a FAB it showed sends nothing more. */
134
225
  fun forget(node: ViewController<*>) {
135
226
  if (source === node) source = null
227
+ aside?.let { if (it.source === node) it.source = null }
136
228
  }
137
229
 
138
- /** The stack goes: the FAB and its menu go at once, the scrim first. */
230
+ /** The stack goes: the FAB and its menu go at once, the scrim first, and the copies that leave. */
139
231
  fun destroy() {
232
+ aside?.let(::discard)
233
+ aside = null
234
+ dropCopies()
235
+ stopRide(rest = false)
140
236
  disableCollapse()
141
237
  menu?.let {
142
238
  it.removeScrim()
@@ -153,7 +249,7 @@ internal class FabPresenter(
153
249
  private fun create(layout: StackLayout, options: FabOptions, scroll: ScrollEventListener?) {
154
250
  val id = FabPlan.idOf(options) ?: return
155
251
  shownId = id
156
- val params = CoordinatorLayout.LayoutParams(WRAP, WRAP)
252
+ val params = params()
157
253
  if (FabPlan.isMenu(options)) {
158
254
  val menu = FabMenu(activity, id)
159
255
  this.menu = menu
@@ -164,7 +260,6 @@ internal class FabPresenter(
164
260
  layout.addOverAll(fab, params)
165
261
  }
166
262
  apply(options, scroll)
167
- view?.let(::grow)
168
263
  }
169
264
 
170
265
  // Every option of the FAB that shows; the collapse before the visibility, which `disableCollapse` would undo.
@@ -235,43 +330,120 @@ internal class FabPresenter(
235
330
  menu?.disableCollapse()
236
331
  }
237
332
 
238
- // The FAB that shows goes: a button shrinks to 0 in 200 ms; a menu goes at once, its scrim first.
239
- private fun remove() {
333
+ // The FAB that shows goes with its screen: a copy of it leaves by [move] while the button, or the menu, goes from the
334
+ // stack at once; an open menu closes, its scrim fading out by itself. At once nothing leaves, the scrim neither.
335
+ private fun leave(layout: StackLayout, move: FabMotion.Move) {
336
+ val view = view ?: return
337
+ val departure = FabMotion.departure(move, isRtl(layout))
338
+ val copy = departure?.let { FabCopy.of((view as? FabMenu)?.main ?: view, view, layout) }
339
+ stopRide(rest = false)
340
+ disableCollapse()
240
341
  menu?.let {
241
- it.removeScrim()
342
+ if (departure != null) it.closeToLeave() else it.removeScrim()
242
343
  (it.parent as? ViewGroup)?.removeView(it)
243
344
  }
244
- fab?.let { fab ->
245
- fab.animate().cancel()
246
- if (!fab.isAttachedToWindow) {
247
- (fab.parent as? ViewGroup)?.removeView(fab)
248
- } else {
249
- fab.animate().scaleX(0f).scaleY(0f).setDuration(ANIMATION_MILLIS)
250
- .withEndAction { (fab.parent as? ViewGroup)?.removeView(fab) }.start()
251
- }
252
- }
345
+ fab?.let { (it.parent as? ViewGroup)?.removeView(it) }
253
346
  menu = null
254
347
  fab = null
255
348
  shownId = null
349
+ if (copy == null) return
350
+ copies[copy] = play(copy, departure, fromCurrent = true) { copies.remove(copy)?.let { removeCopy(copy) } }
351
+ }
352
+
353
+ // The new FAB arrives with its screen by [move]; one its options hide (`visible: false`) has nothing to show.
354
+ private fun arrive(move: FabMotion.Move) {
355
+ val view = view ?: return
356
+ if (((view as? FabMenu)?.main ?: view).visibility != View.VISIBLE) return
357
+ val arrival = FabMotion.arrival(move, layout?.let(::isRtl) ?: false) ?: return
358
+ ride = play(view, arrival, fromCurrent = false) { animator -> if (ride === animator) ride = null }
359
+ }
360
+
361
+ // A ride stops where it is; [rest]: the FAB is in its place again, opaque and whole.
362
+ private fun stopRide(rest: Boolean) {
363
+ val running = ride ?: return
364
+ ride = null
365
+ running.cancel()
366
+ if (!rest) return
367
+ view?.apply {
368
+ translationX = 0f
369
+ alpha = 1f
370
+ scaleX = 1f
371
+ scaleY = 1f
372
+ }
373
+ }
374
+
375
+ // The copies of an older transition go at once.
376
+ private fun dropCopies() {
377
+ if (copies.isEmpty()) return
378
+ val dropped = copies.toList()
379
+ copies.clear()
380
+ dropped.forEach { (copy, animator) ->
381
+ animator.cancel()
382
+ removeCopy(copy)
383
+ }
256
384
  }
257
385
 
258
- // It comes to the top and grows from 60 % in 200 ms.
259
- private fun grow(view: View) {
260
- val target = (view as? FabMenu)?.main ?: view
261
- target.animate().cancel()
262
- target.scaleX = GROW_FROM
263
- target.scaleY = GROW_FROM
264
- target.animate().scaleX(1f).scaleY(1f).setDuration(ANIMATION_MILLIS).start()
386
+ private fun removeCopy(copy: FabCopy) {
387
+ (copy.parent as? ViewGroup)?.removeView(copy)
265
388
  }
266
389
 
390
+ // Plays [ride] on [view], from its start values or [fromCurrent] from where the view is; [end] at its end or stop.
391
+ private fun play(view: View, ride: FabMotion.Ride, fromCurrent: Boolean, end: (Animator) -> Unit): Animator {
392
+ val density = activity.resources.displayMetrics.density
393
+ if (!fromCurrent) {
394
+ view.translationX = ride.fromX * density
395
+ view.alpha = ride.fromAlpha
396
+ view.scaleX = ride.fromScale
397
+ view.scaleY = ride.fromScale
398
+ }
399
+ val move = ObjectAnimator.ofPropertyValuesHolder(
400
+ view,
401
+ PropertyValuesHolder.ofFloat(View.TRANSLATION_X, ride.toX * density),
402
+ PropertyValuesHolder.ofFloat(View.SCALE_X, ride.toScale),
403
+ PropertyValuesHolder.ofFloat(View.SCALE_Y, ride.toScale),
404
+ ).apply {
405
+ duration = ride.duration
406
+ interpolator = when (ride.curve) {
407
+ FabMotion.Curve.System -> SystemTransition.CURVE
408
+ FabMotion.Curve.FadeThrough -> FadeThrough.CURVE
409
+ FabMotion.Curve.Material -> AccelerateDecelerateInterpolator()
410
+ }
411
+ }
412
+ val animator = AnimatorSet()
413
+ if (ride.alphaDuration > 0) {
414
+ val fade = ObjectAnimator.ofFloat(view, View.ALPHA, ride.toAlpha).apply {
415
+ startDelay = ride.alphaDelay
416
+ duration = ride.alphaDuration
417
+ interpolator = LinearInterpolator()
418
+ }
419
+ animator.playTogether(move, fade)
420
+ } else {
421
+ animator.play(move)
422
+ }
423
+ animator.addListener(object : AnimatorListenerAdapter() {
424
+ override fun onAnimationEnd(animation: Animator) = end(animation)
425
+ })
426
+ animator.start()
427
+ return animator
428
+ }
429
+
430
+ // A FAB set aside that won't come back: it follows no scroll, its scrim goes.
431
+ private fun discard(kept: Aside) {
432
+ kept.fab?.disableCollapse()
433
+ kept.menu?.disableCollapse()
434
+ kept.menu?.removeScrim()
435
+ }
436
+
437
+ private fun params() = CoordinatorLayout.LayoutParams(WRAP, WRAP)
438
+
439
+ private fun isRtl(layout: StackLayout): Boolean = layout.layoutDirection == View.LAYOUT_DIRECTION_RTL
440
+
267
441
  companion object {
268
442
  private const val WRAP = ViewGroup.LayoutParams.WRAP_CONTENT
269
- private const val ANIMATION_MILLIS = 200L
270
- private const val GROW_FROM = 0.6f
271
443
 
272
444
  /**
273
445
  * A press on the FAB, or on an action of its menu, of [source] (EVT-NavigationButtonPressed): nothing once
274
- * [source] is destroyed — its FAB still fades for 200 ms.
446
+ * [source] is destroyed.
275
447
  */
276
448
  fun press(source: ViewController<*>?, buttonId: String) {
277
449
  if (source == null || source.isDestroyed) return
@@ -3,8 +3,6 @@ package dev.apexnative.navigation.viewcontrollers.stack.search
3
3
  import android.app.Activity
4
4
  import android.content.Context
5
5
  import android.graphics.drawable.Drawable
6
- import android.view.Menu
7
- import android.view.MenuItem
8
6
  import android.view.View
9
7
  import android.view.ViewGroup
10
8
  import androidx.appcompat.R as AppCompatR
@@ -15,12 +13,14 @@ import androidx.core.view.WindowInsetsCompat
15
13
  import dev.apexnative.navigation.options.SearchBarOptions
16
14
  import dev.apexnative.navigation.options.params.ThemeColour
17
15
  import dev.apexnative.navigation.react.events.EventEmitter
16
+ import dev.apexnative.navigation.utils.TestIds
18
17
  import dev.apexnative.navigation.utils.dp
19
18
  import dev.apexnative.navigation.viewcontrollers.parent.OptionsScreen
20
19
  import dev.apexnative.navigation.viewcontrollers.viewcontroller.ViewController
21
20
  import dev.apexnative.navigation.views.stack.topbar.TopBar
22
21
  import dev.apexnative.navigation.views.stack.topbar.TopBarDefaults
23
22
  import dev.apexnative.navigation.views.stack.topbar.search.SearchStyle
23
+ import dev.apexnative.navigation.views.stack.topbar.titlebar.BarButtonView
24
24
 
25
25
  /**
26
26
  * The search of the screens of a stack (`topBar.searchBar`): each screen has its own while it is in the stack, and the
@@ -38,6 +38,7 @@ class StackSearchPresenter(
38
38
  private var suggestions: View? = null
39
39
  private var wasActive = false
40
40
  private var onActiveChanged: Runnable? = null
41
+ private var magnifier: BarButtonView? = null
41
42
 
42
43
  /** The search of the screen on top opened or closed: the bar's hideOnScroll stops or goes on. */
43
44
  fun setOnActiveChanged(listener: Runnable?) {
@@ -49,6 +50,7 @@ class StackSearchPresenter(
49
50
 
50
51
  fun bindTopBar(topBar: TopBar) {
51
52
  this.topBar = topBar
53
+ magnifier = null
52
54
  // The suggestions hang from the bar: they follow its height as the search opens and its scopes show.
53
55
  topBar.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> topBar.post { positionSuggestions(topBar) } }
54
56
  }
@@ -91,28 +93,29 @@ class StackSearchPresenter(
91
93
  return true
92
94
  }
93
95
 
94
- /** After the right buttons change: the magnifier of an integrated search goes back among them, last. */
96
+ /**
97
+ * After the right buttons change: the magnifier of an integrated search stands after them, before "⋮", as a button
98
+ * of the bar, with the id `<topBar.searchBar.testID>.button`.
99
+ */
95
100
  fun applyMagnifier() {
96
101
  val bar = topBar ?: return
97
- val menu = bar.rightButtonBar.menu
98
- val item = menu.findItem(MAGNIFIER_ID)
99
- if (shown?.showsMagnifier != true) {
100
- if (item != null) menu.removeItem(MAGNIFIER_ID)
102
+ val row = bar.rightButtonBar
103
+ val search = shown
104
+ if (search?.showsMagnifier != true) {
105
+ row.setTrailing(null)
101
106
  return
102
107
  }
103
- if (item != null) return
104
- val context = styleContext()
105
- val label = context.getString(AppCompatR.string.abc_searchview_description_search)
106
- menu.add(Menu.NONE, MAGNIFIER_ID, MAGNIFIER_ORDER, label).apply {
107
- icon = AppCompatResources.getDrawable(context, AppCompatR.drawable.abc_ic_search_api_material)?.mutate()?.also {
108
- it.setTint(TopBarDefaults.overflowIconColor(context))
109
- }
110
- setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
111
- setOnMenuItemClickListener {
112
- shown?.activate()
113
- true
114
- }
108
+ val button = magnifier ?: BarButtonView(row.context).also { button ->
109
+ val context = styleContext()
110
+ val icon = AppCompatResources.getDrawable(context, AppCompatR.drawable.abc_ic_search_api_material)?.mutate()
111
+ icon?.setTint(TopBarDefaults.overflowIconColor(context))
112
+ val label = context.getString(AppCompatR.string.abc_searchview_description_search)
113
+ button.show(label, icon, null, true)
114
+ button.setOnClickListener { shown?.activate() }
115
+ magnifier = button
115
116
  }
117
+ TestIds.apply(button, search.options.testID.get(null)?.let { "$it.button" })
118
+ row.setTrailing(button)
116
119
  }
117
120
 
118
121
  override fun refresh(search: ScreenSearch) {
@@ -132,7 +135,9 @@ class StackSearchPresenter(
132
135
  fun onConfigurationChanged(tint: ThemeColour) {
133
136
  shown?.takeTint(tint)
134
137
  searches.values.forEach { it.restyle() }
135
- topBar?.rightButtonBar?.menu?.removeItem(MAGNIFIER_ID)
138
+ // The magnifier is made again in the new mode's colors.
139
+ magnifier = null
140
+ topBar?.rightButtonBar?.setTrailing(null)
136
141
  refresh()
137
142
  }
138
143
 
@@ -231,10 +236,4 @@ class StackSearchPresenter(
231
236
  lp.topMargin = top
232
237
  card.layoutParams = lp
233
238
  }
234
-
235
- private companion object {
236
- val MAGNIFIER_ID = View.generateViewId()
237
- // The highest a menu order goes: after the screen's own buttons.
238
- const val MAGNIFIER_ORDER = 0xFFFF
239
- }
240
239
  }
@@ -7,14 +7,15 @@ import dev.apexnative.navigation.viewcontrollers.stack.StackDisplay.BarMotion
7
7
  * row and the large title lies over them and the real row turns transparent; then the picture goes with the old screen
8
8
  * and the row comes with the new one ([RowTimeline]). Each change of the stack's current child owns the bar: a newer
9
9
  * one takes it over (the older picture goes, its animator stops), and only the end of the transition that owns it puts
10
- * the row back at rest. Without a picture (a `HARDWARE` bitmap in the bar) the old row just hides. Main thread only.
10
+ * the row back at rest. Without a picture (a `HARDWARE` bitmap in the bar on Android 8) the old row just hides. Main
11
+ * thread only.
11
12
  */
12
13
  internal class BarRowTransition(private val target: Target) {
13
14
  /** What the transition moves: the bar's views, or the unit tests' fake. */
14
15
  interface Target {
15
16
  /**
16
17
  * Lays a picture of the row and the large title over them, laid out at once and taking the touches; false when
17
- * the bar can't be drawn in software (a `HARDWARE` bitmap): no picture then.
18
+ * the bar can't be recorded (a `HARDWARE` bitmap on Android 8): no picture then.
18
19
  */
19
20
  fun takeSnapshot(): Boolean
20
21
 
@@ -1,11 +1,11 @@
1
1
  package dev.apexnative.navigation.viewcontrollers.stack.topbar
2
2
 
3
3
  /**
4
- * Where the right buttons of the bar go: the bar has as many places as AppCompat gives a toolbar for the window now
5
- * ([maxActionButtons]), one less for "⋮" when it shows; the pinned buttons take theirs first, then the buttons with the
6
- * highest `priority` (ties by the list's order); the rest go into "⋮". The core counts it, not AppCompat: AppCompat
7
- * would share the places in the menu's order, where the first button of the list is the last, and keep the count of the
8
- * orientation before a rotation. A pure function.
4
+ * Where the buttons of the bar go. The right ones: the bar has as many places as AppCompat gives a toolbar for the
5
+ * window now ([maxActionButtons]), one less for "⋮" when it shows; the pinned buttons take theirs first, then the
6
+ * buttons with the highest `priority` (ties by the list's order); the rest go into "⋮". In the line ([line]) the left
7
+ * buttons stand from its start in the list's order, and the right ones toward its end, the first one last. A pure
8
+ * function.
9
9
  */
10
10
  internal object ButtonPlaces {
11
11
  /** A right button as the count sees it: its `priority` ([HIGH], [STANDARD], [LOW]) and `pinned`. */
@@ -36,6 +36,12 @@ internal object ButtonPlaces {
36
36
  else -> 2
37
37
  }
38
38
 
39
+ /**
40
+ * The buttons of a side in the bar, [inBar] in the list's order, in the line's order from its start: the left ones
41
+ * as they are, the right ones the other way round, so the first right button stands at the end of the line.
42
+ */
43
+ fun <T> line(inBar: List<T>, left: Boolean): List<T> = if (left) inBar else inBar.reversed()
44
+
39
45
  /**
40
46
  * The plan for [buttons] with [maxPlaces] places, [taken] of them already used by what the core always puts in the
41
47
  * bar (the search's magnifier), and [overflowItems] items of `overflowMenu`, which put "⋮" in the bar whatever