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

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 (242) hide show
  1. package/ApexNavigation.podspec +2 -1
  2. package/CHANGELOG.md +15 -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 +86 -39
  39. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarVisibility.kt +9 -2
  40. package/android/src/main/java/dev/apexnative/navigation/views/element/ElementTransitions.kt +124 -181
  41. package/android/src/main/java/dev/apexnative/navigation/views/element/TargetsWait.kt +8 -1
  42. package/android/src/main/java/dev/apexnative/navigation/views/element/TransitionLayer.kt +37 -112
  43. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ElementPictures.kt +56 -3
  44. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/FlightGeometry.kt +170 -153
  45. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMorph.kt +199 -0
  46. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/SharedAnimatorFactory.kt +55 -45
  47. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/{TextChangeAnimator.kt → TextMorph.kt} +79 -129
  48. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewMorph.kt +108 -0
  49. package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/FabCopy.kt +85 -0
  50. package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/FabMenu.kt +13 -0
  51. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TitleBar.kt +5 -24
  52. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TopBar.kt +51 -34
  53. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarButtonComponentCell.kt +6 -6
  54. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarButtonView.kt +113 -0
  55. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarMenu.kt +219 -0
  56. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonBar.kt +121 -175
  57. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonMenus.kt +34 -64
  58. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonRow.kt +216 -0
  59. package/android/src/main/jni/ANNContentHeights.cpp +82 -0
  60. package/android/src/main/res/drawable/apex_navigation_ic_arrow_right.xml +14 -0
  61. package/android/src/main/res/layout/apex_navigation_menu_header.xml +19 -0
  62. package/android/src/main/res/layout/apex_navigation_menu_item.xml +68 -0
  63. package/android/src/main/res/values/styles.xml +1 -1
  64. package/android/src/main/res/values-night/styles.xml +1 -1
  65. package/cli/doctor/checks/app.js +16 -1
  66. package/cli/project.js +4 -0
  67. package/cli/setup/index.js +5 -0
  68. package/cli/setup/steps/metro.js +46 -0
  69. package/docs-md/architecture/native-screens.md +4 -0
  70. package/docs-md/architecture/theme-and-context.md +131 -12
  71. package/docs-md/architecture/transitions.md +43 -4
  72. package/docs-md/flows/bottom-sheets.md +4 -2
  73. package/docs-md/fundamentals/screens-and-props.md +9 -1
  74. package/docs-md/guides/cli.md +3 -1
  75. package/docs-md/help/troubleshooting.md +1 -1
  76. package/docs-md/index.json +29 -1
  77. package/docs-md/screen-ui/back-and-gestures.md +3 -1
  78. package/docs-md/screen-ui/bar-buttons.md +1 -1
  79. package/docs-md/screen-ui/toolbar-and-fab.md +2 -0
  80. package/docs-md/start/installation.md +16 -1
  81. package/ios/Animations/ANNFrameDriver.h +55 -15
  82. package/ios/Animations/ANNFrameDriver.mm +214 -60
  83. package/ios/Animations/ANNTransitionAnimator.h +26 -5
  84. package/ios/Animations/ANNTransitionAnimator.mm +49 -3
  85. package/ios/Animations/ANNTransitionPlan.h +2 -2
  86. package/ios/Animations/ANNTransitionPlan.mm +10 -0
  87. package/ios/Animations/Elements/ANNElementFinder.h +2 -2
  88. package/ios/Animations/Elements/ANNElementFinder.mm +2 -2
  89. package/ios/Animations/Elements/ANNElementTransition.h +3 -3
  90. package/ios/Animations/Elements/ANNElementTransition.mm +2 -2
  91. package/ios/Animations/Elements/ANNSharedElementFlight.h +3 -3
  92. package/ios/Animations/Elements/ANNSharedElementFlight.mm +181 -43
  93. package/ios/Animations/Interpolators/ANNViewTracks.h +6 -5
  94. package/ios/Animations/Interpolators/ANNViewTracks.mm +51 -21
  95. package/ios/CLAUDE.md +19 -8
  96. package/ios/CORE.md +1 -1
  97. package/ios/Core/ANNAppDelegate.mm +0 -2
  98. package/ios/Core/ANNDeepLinkQueue.h +6 -5
  99. package/ios/Core/ANNDeepLinkQueue.mm +22 -10
  100. package/ios/Core/ANNSceneDelegate.mm +2 -2
  101. package/ios/Core/ANNUtils.h +7 -0
  102. package/ios/Core/ANNUtils.mm +10 -0
  103. package/ios/Core/ApexNavigation.mm +6 -2
  104. package/ios/Layouts/Overlay/ANNOverlayCoordinator.mm +1 -7
  105. package/ios/Layouts/Root/ANNRootCoordinator.mm +45 -15
  106. package/ios/Layouts/Screen/ANNComponentViewController.mm +4 -1
  107. package/ios/Layouts/Stack/ANNStackAnimatingDelegate.h +11 -2
  108. package/ios/Layouts/Stack/ANNStackAnimatingDelegate.mm +14 -7
  109. package/ios/Layouts/Stack/ANNStackController.h +8 -0
  110. package/ios/Layouts/Stack/ANNStackController.mm +153 -11
  111. package/ios/Layouts/Stack/ANNSwipePopInteraction.h +26 -0
  112. package/ios/Layouts/Stack/ANNSwipePopInteraction.mm +185 -0
  113. package/ios/Layouts/Stack/InteractivePopGestureDelegate.mm +28 -4
  114. package/ios/Layouts/Tabs/ANNTabsPreloadWindow.mm +2 -4
  115. package/ios/Options/ANNAnimationOptions.h +2 -1
  116. package/ios/Options/ANNAnimationOptions.mm +1 -1
  117. package/ios/Options/ANNOptions.h +7 -0
  118. package/ios/Options/ANNOptions.mm +45 -0
  119. package/ios/Options/ANNOptionsReader.mm +2 -4
  120. package/ios/Options/ANNStackPopAnimationOptions.h +13 -0
  121. package/ios/Options/ANNStackPopAnimationOptions.mm +11 -0
  122. package/ios/Options/Params/ColorParser.h +4 -3
  123. package/ios/Options/Params/ColorParser.mm +22 -4
  124. package/ios/React/ANNSurfaceHostingView.h +8 -0
  125. package/ios/React/ANNSurfaceHostingView.mm +28 -1
  126. package/ios/React/Slots/ANNSlotHost.mm +1 -3
  127. package/ios/React/Slots/ANNSlotScreenView.mm +8 -2
  128. package/ios/TurboModules/ANNTurboModule.mm +46 -0
  129. package/ios/cpp/react/renderer/components/ApexNavigationSpec/ANNSlotState.cpp +20 -0
  130. package/ios/cpp/react/renderer/components/ApexNavigationSpec/ANNSlotState.h +5 -14
  131. package/lib/module/ARCHITECTURE.md +9 -3
  132. package/lib/module/adapters/NativeANNTurboModule.js.map +1 -1
  133. package/lib/module/api/app/AppRegistry.js +14 -2
  134. package/lib/module/api/app/AppRegistry.js.map +1 -1
  135. package/lib/module/api/app/createNavigation.js +188 -66
  136. package/lib/module/api/app/createNavigation.js.map +1 -1
  137. package/lib/module/api/events/navigationEvents.js +6 -2
  138. package/lib/module/api/events/navigationEvents.js.map +1 -1
  139. package/lib/module/api/linking/linking.js +11 -0
  140. package/lib/module/api/linking/linking.js.map +1 -1
  141. package/lib/module/api/screens/defineScreen.js +45 -3
  142. package/lib/module/api/screens/defineScreen.js.map +1 -1
  143. package/lib/module/api/screens/nativeScreens.js +31 -6
  144. package/lib/module/api/screens/nativeScreens.js.map +1 -1
  145. package/lib/module/api/screens/registerScreens.js +193 -35
  146. package/lib/module/api/screens/registerScreens.js.map +1 -1
  147. package/lib/module/api/theme/theme.js +5 -0
  148. package/lib/module/api/theme/theme.js.map +1 -1
  149. package/lib/module/engine/commands.js +21 -1
  150. package/lib/module/engine/commands.js.map +1 -1
  151. package/lib/module/engine/layout.js +7 -2
  152. package/lib/module/engine/layout.js.map +1 -1
  153. package/lib/module/engine/nativeScreens.js +9 -0
  154. package/lib/module/engine/nativeScreens.js.map +1 -1
  155. package/lib/module/engine/options.js +5 -4
  156. package/lib/module/engine/options.js.map +1 -1
  157. package/lib/module/engine/optionsChange.js +102 -0
  158. package/lib/module/engine/optionsChange.js.map +1 -0
  159. package/lib/module/engine/refresh.js +85 -0
  160. package/lib/module/engine/refresh.js.map +1 -0
  161. package/lib/module/engine/rememberedOptions.js +60 -0
  162. package/lib/module/engine/rememberedOptions.js.map +1 -1
  163. package/lib/module/engine/screens.js +26 -0
  164. package/lib/module/engine/screens.js.map +1 -1
  165. package/lib/typescript/adapters/NativeANNTurboModule.d.ts +7 -0
  166. package/lib/typescript/adapters/NativeANNTurboModule.d.ts.map +1 -1
  167. package/lib/typescript/api/app/AppRegistry.d.ts.map +1 -1
  168. package/lib/typescript/api/app/createNavigation.d.ts +5 -0
  169. package/lib/typescript/api/app/createNavigation.d.ts.map +1 -1
  170. package/lib/typescript/api/events/navigationEvents.d.ts +2 -2
  171. package/lib/typescript/api/events/navigationEvents.d.ts.map +1 -1
  172. package/lib/typescript/api/index.d.ts +1 -1
  173. package/lib/typescript/api/index.d.ts.map +1 -1
  174. package/lib/typescript/api/linking/linking.d.ts +8 -0
  175. package/lib/typescript/api/linking/linking.d.ts.map +1 -1
  176. package/lib/typescript/api/screens/defineScreen.d.ts +23 -4
  177. package/lib/typescript/api/screens/defineScreen.d.ts.map +1 -1
  178. package/lib/typescript/api/screens/nativeScreens.d.ts +8 -1
  179. package/lib/typescript/api/screens/nativeScreens.d.ts.map +1 -1
  180. package/lib/typescript/api/screens/registerScreens.d.ts.map +1 -1
  181. package/lib/typescript/api/theme/theme.d.ts +2 -0
  182. package/lib/typescript/api/theme/theme.d.ts.map +1 -1
  183. package/lib/typescript/engine/commands.d.ts +5 -0
  184. package/lib/typescript/engine/commands.d.ts.map +1 -1
  185. package/lib/typescript/engine/layout.d.ts.map +1 -1
  186. package/lib/typescript/engine/nativeScreens.d.ts +5 -0
  187. package/lib/typescript/engine/nativeScreens.d.ts.map +1 -1
  188. package/lib/typescript/engine/optionsChange.d.ts +28 -0
  189. package/lib/typescript/engine/optionsChange.d.ts.map +1 -0
  190. package/lib/typescript/engine/refresh.d.ts +35 -0
  191. package/lib/typescript/engine/refresh.d.ts.map +1 -0
  192. package/lib/typescript/engine/rememberedOptions.d.ts +13 -0
  193. package/lib/typescript/engine/rememberedOptions.d.ts.map +1 -1
  194. package/lib/typescript/engine/screens.d.ts +7 -0
  195. package/lib/typescript/engine/screens.d.ts.map +1 -1
  196. package/lib/typescript/interfaces/ComponentEvents.d.ts +2 -2
  197. package/lib/typescript/interfaces/options/animations.d.ts +40 -15
  198. package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
  199. package/lib/typescript/interfaces/options/bottomTabs.d.ts +2 -2
  200. package/lib/typescript/interfaces/options/buttons.d.ts +2 -2
  201. package/lib/typescript/interfaces/options/modals.d.ts +6 -7
  202. package/lib/typescript/interfaces/options/modals.d.ts.map +1 -1
  203. package/lib/typescript/interfaces/options/stack.d.ts +8 -3
  204. package/lib/typescript/interfaces/options/stack.d.ts.map +1 -1
  205. package/lib/typescript/interfaces/options/topBar.d.ts +14 -0
  206. package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
  207. package/metro/acceptPlugin.js +57 -0
  208. package/metro/babelTransformer.js +45 -0
  209. package/metro/index.js +36 -0
  210. package/package.json +3 -1
  211. package/src/ARCHITECTURE.md +9 -3
  212. package/src/adapters/NativeANNTurboModule.ts +7 -0
  213. package/src/api/app/AppRegistry.ts +16 -2
  214. package/src/api/app/createNavigation.ts +198 -66
  215. package/src/api/events/navigationEvents.ts +9 -3
  216. package/src/api/index.ts +1 -0
  217. package/src/api/linking/linking.ts +22 -1
  218. package/src/api/screens/defineScreen.ts +49 -5
  219. package/src/api/screens/nativeScreens.ts +40 -4
  220. package/src/api/screens/registerScreens.tsx +185 -42
  221. package/src/api/theme/theme.ts +5 -0
  222. package/src/engine/commands.ts +21 -0
  223. package/src/engine/layout.ts +11 -2
  224. package/src/engine/nativeScreens.ts +10 -1
  225. package/src/engine/options.ts +6 -8
  226. package/src/engine/optionsChange.ts +107 -0
  227. package/src/engine/refresh.ts +97 -0
  228. package/src/engine/rememberedOptions.ts +70 -0
  229. package/src/engine/screens.tsx +24 -0
  230. package/src/interfaces/ComponentEvents.ts +2 -2
  231. package/src/interfaces/options/animations.ts +40 -15
  232. package/src/interfaces/options/bottomTabs.ts +2 -2
  233. package/src/interfaces/options/buttons.ts +2 -2
  234. package/src/interfaces/options/modals.ts +6 -7
  235. package/src/interfaces/options/stack.ts +8 -3
  236. package/src/interfaces/options/topBar.ts +14 -0
  237. package/android/src/main/java/dev/apexnative/navigation/views/element/ElementStub.kt +0 -31
  238. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMatrixAnimator.kt +0 -134
  239. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PlainViewAnimator.kt +0 -78
  240. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PropertyAnimatorCreator.kt +0 -33
  241. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ReactImageAnimator.kt +0 -57
  242. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewChangeAnimator.kt +0 -146
@@ -2,7 +2,8 @@
2
2
  # ApexNavigation): the Objective-C++ core and modules under ios/, with the public headers of ios/Public/ApexNavigation
3
3
  # (<ApexNavigation/ApexNavigation.h>, <ApexNavigation/ANNAppDelegate.h>, <ApexNavigation/ANNSceneDelegate.h>). Other
4
4
  # headers stay private to the pod and are imported by bare name. The C++ of the Fabric components ANNSlot and
5
- # ANNSafeAreaView is header-only, under ios/cpp, next to the code React Native's codegen makes from src/.
5
+ # ANNSafeAreaView is under ios/cpp, next to the code React Native's codegen makes from src/: headers, and one source
6
+ # file with the store of the slots' measured heights.
6
7
  require "json"
7
8
 
8
9
  package = JSON.parse(File.read(File.join(__dir__, "package.json")))
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.0.0-alpha.8](https://github.com/apexnative/navigation/compare/v1.0.0-alpha.7...v1.0.0-alpha.8) (2026-10-29)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * group options reach the screens that show, and the android bar hides as it shows ([#63](https://github.com/apexnative/navigation/issues/63)) ([474ca8a](https://github.com/apexnative/navigation/commit/474ca8aae53e5284d64a5d90dd852fdbdcbabcac))
9
+ * **ios:** overlays and preloaded tabs take the scheme the app set with Appearance.setColorScheme ([#61](https://github.com/apexnative/navigation/issues/61)) ([232d7e0](https://github.com/apexnative/navigation/commit/232d7e038b53cd9656e59c7cfa5cbf0f52f9b587))
10
+
11
+ ## [1.0.0-alpha.7](https://github.com/apexnative/navigation/compare/v1.0.0-alpha.6...v1.0.0-alpha.7) (2026-10-29)
12
+
13
+
14
+ ### Features
15
+
16
+ * own android sheet and bar row, ios transitions in core animation, fast refresh of screens ([#56](https://github.com/apexnative/navigation/issues/56)) ([3113b42](https://github.com/apexnative/navigation/commit/3113b42a466f59490faaca181178a7840dab2826))
17
+
3
18
  ## [1.0.0-alpha.6](https://github.com/apexnative/navigation/compare/v1.0.0-alpha.5...v1.0.0-alpha.6) (2026-10-25)
4
19
 
5
20
 
package/android/CLAUDE.md CHANGED
@@ -22,8 +22,9 @@ top-level folder. Main thread only, unless a line says otherwise.
22
22
  `Variant`, `ThemeColour`); `parsers/` reads them tolerantly from React Native data.
23
23
  - `react/`: the way in and out: `NavigationTurboModule`, `CommandRunner`, `CommandAnswer`, `ErrorTexts`,
24
24
  `OptionsCommands`, `PendingCalls` (a task behind the calls JavaScript made), `ReloadCoordinator`, `ReactGateway`,
25
- `ReactView` (a React root), the stores (`ScreenEnvironment`: a screen's insets, padding, regions and pair state;
26
- `Hinge`, `StartInfo`), `ScreenRegions` (measures a screen's regions), `LaunchLink`, `ScrollEvents`.
25
+ `ReactView` (a React root), `ContentHeights` (content measured without a height limit, through JNI), the stores
26
+ (`ScreenEnvironment`: a screen's insets, padding, regions and pair state; `Hinge`, `StartInfo`), `ScreenRegions`
27
+ (measures a screen's regions), `LaunchLink`, `ScrollEvents`.
27
28
  - `react/events/`: `AppearanceDispatcher`, `ScreenAppearance`, `EventEmitter`, `EventContract` (made by
28
29
  `scripts/contract.mjs`), `NavigationState`, `ComponentType`.
29
30
  - `viewcontrollers/viewcontroller/`: `ViewController` (the base of every node), `Presenter`, `IReactView` (what a
@@ -58,7 +59,8 @@ top-level folder. Main thread only, unless a line says otherwise.
58
59
  `DevelopmentLog`).
59
60
  - `android/src/main/jni/`: `CMakeLists.txt` builds the codegen target with the C++ of `ios/cpp` (the slot's and the
60
61
  safe area view's shadow nodes); `ApexNavigationSpec.h` replaces the generated header so autolinking sees their
61
- descriptors.
62
+ descriptors; `ANNContentHeights.cpp` measures React content without a height limit for `react/ContentHeights.kt`
63
+ (a sheet's `'content'` detent).
62
64
  - `android/src/main/res/`: theme `Theme.ApexNavigation` (and `values-night`), `attrs.xml` (splash attributes),
63
65
  `ids.xml`, `dimens*.xml`, text colors, icons.
64
66
  - `android/src/test/java/…`: unit tests, one package per main package; fakes beside them (`Fakes.kt`,
@@ -80,10 +82,14 @@ top-level folder. Main thread only, unless a line says otherwise.
80
82
  preview of a pop during the back gesture (Android 14+): `StackController.BackGesture`, `BackPreview.kt`,
81
83
  `stackPoppedByBack` of the layouts; of a sheet's dismissal: `viewcontrollers/modal/SheetBack.kt`,
82
84
  `ModalCoordinator.backTargetSheet`, `ModalSheet.BackPreview`.
83
- - Stack FAB: `viewcontrollers/stack/fab/FabPresenter.kt`, `FabPlan.kt`, `FabPlacement.kt`; `views/stack/fab/`.
85
+ - Stack FAB: `viewcontrollers/stack/fab/FabPresenter.kt`, `FabPlan.kt`, `FabPlacement.kt`, `FabMotion.kt` (how it goes
86
+ with a transition); `views/stack/fab/` (`FabCopy`: the picture of a FAB that leaves).
84
87
  - Top bar (title, buttons, menus, large title): `viewcontrollers/stack/topbar/TopBarPresenter.kt`, `TopBarRoom.kt`,
85
88
  `TopBarVisibility.kt`, `BarComponents.kt`, `ButtonPlaces.kt`, `LargeTitleCollapse.kt`, `BarRowTransition.kt`;
86
- `views/stack/topbar/TopBar.kt`, `TitleBar.kt`, `titlebar/ButtonBar.kt`, `titlebar/ButtonMenus.kt`.
89
+ `views/stack/topbar/TopBar.kt`, `TitleBar.kt`, `titlebar/ButtonRow.kt` (the views of a side: back, buttons,
90
+ magnifier, "⋮"), `titlebar/ButtonBar.kt` (the buttons from the options, "⋮"'s popup), `titlebar/BarButtonView.kt`,
91
+ `titlebar/BarMenu.kt` (the bar's menus: a `ListPopupWindow` with the theme's popup style and the options' colors),
92
+ `titlebar/ButtonMenus.kt` (menu options to its items).
87
93
  - Search: `viewcontrollers/stack/search/StackSearchPresenter.kt`, `ScreenSearch.kt`, `SearchPanel.kt`;
88
94
  `views/stack/topbar/search/`.
89
95
  - Tabs: `viewcontrollers/bottomtabs/BottomTabsController.kt`, `BottomTabsPresenter.kt`, `TabSelection.kt`,
@@ -158,9 +164,9 @@ top-level folder. Main thread only, unless a line says otherwise.
158
164
  - Native code never touches React Native's views (their children, their layout): Fabric owns and recycles them. Move
159
165
  content through `SlotContainers` and `SlotView`, and find views by `nativeID` only through React Native's own search
160
166
  or its tag (`ReactR.id.view_tag_native_id`); a transition's elements in one walk per screen (`TransitionViews`).
161
- - Each step of a transition's element (its move into the layer, its animator, a flight's start and frames in
162
- `MoveAnimator`) runs in `elementStep`: an exception skips that element only. A new step of an element goes there
163
- too, or one bad element takes the whole transition down.
167
+ - Each step of a transition's element (its start, its animator, a flight's start and frames in `MoveAnimator`) runs
168
+ in `elementStep`, and the layer draws each copy on its own: an exception skips that element only. A new step of an
169
+ element goes there too, or one bad element takes the whole transition down.
164
170
  - Every change of a stack's children goes through its `StackQueue`; a direct change of `shown` or of the container
165
171
  skips the table that decides interrupts and answers.
166
172
  - Every event of an appearance goes through `AppearanceDispatcher`. A container with its own loop of
@@ -200,8 +206,11 @@ top-level folder. Main thread only, unless a line says otherwise.
200
206
  - The bar measures to its children: a picture laid into it for a transition that is taller than the bar the incoming
201
207
  screen gives holds the bar at that height to the end (the row's picture of a pop from a large title). What doesn't
202
208
  stay in the bar rides with the leaving screen (`TopBarPresenter.pictureUnderRow`).
203
- - A `Toolbar` centers its navigation button and its menu in `min(minimumHeight, height)` (`buttonGravity` is top):
204
- the row's toolbars take the row's height as their minimum (`TitleBar`); 0 pins the buttons to the row's top.
209
+ - The bar's buttons are plain views of the row (`ButtonRow`), not a toolbar's menu: a button keeps its view while its id
210
+ stays, and its `testID` is set when it is bound. They copy a Material 3 toolbar's look through the theme's
211
+ attributes (`actionButtonStyle`, `actionMenuTextAppearance`, `actionOverflowButtonStyle`,
212
+ `toolbarNavigationButtonStyle`) and its `maxButtonHeight` of 56 dp; the count of places is AppCompat's
213
+ (`ButtonPlaces.maxActionButtons`).
205
214
  - A screen's overlay is clipped to the screen, and a screen without a large title is laid out under the bar (its `top`
206
215
  is the bar's height): a picture of the bar in its overlay doesn't show. The bar's picture in a transition of the
207
216
  stack's own is drawn by the stack's view with the screen's transform (`StackLayout.barTravels`).
package/android/CORE.md CHANGED
@@ -207,8 +207,9 @@ Modules are the feature classes that plug into the core through its contract (th
207
207
  custom row (`BottomTabsCustomRowOptions`).
208
208
  - **Views and helpers:** tabs bar (`BottomTabs`, `BottomTabItem`, `BottomTabsCollapseBehavior`, `scrollToTop`), slots
209
209
  (`SlotHost`, `SlotView`, `SlotContainers`), safe area (`SafeAreaView`), native screens (`nativescreen/`), splash
210
- (`splash/`), animations (`FadeThrough`, `SystemTransition`, `TransitionTargets`, three element animators), utils
211
- (`MaterialTheme`, `NavigationLog`, `awaitBarComponents`, `whenDrawn`).
210
+ (`splash/`), animations (`FadeThrough`, `SystemTransition`, `TransitionTargets`, the copies of shared elements:
211
+ `TextMorph`, `ViewMorph`, `ImageMorph`), utils (`MaterialTheme`, `NavigationLog`, `awaitBarComponents`,
212
+ `whenDrawn`).
212
213
  - **Stores and singletons:** a screen's environment (`ScreenEnvironment`: its insets and padding, its regions as
213
214
  `ScreenRegions` measures them, its pair state), the window (`Hinge`, `WindowFolds`), links (`LaunchLink`,
214
215
  `StartInfo`), events (`NavigationState`), commands (`BackCallbackState`), stack (`PreparedFrame`).
@@ -1,2 +1,8 @@
1
1
  # The native screens `npx apex-navigation codegen` lists in the app: the navigation finds the list by its name.
2
2
  -keep class dev.apexnative.navigation.generated.ApexNavigationGeneratedScreens { *; }
3
+
4
+ # The library's C++ measures a React root's content through the surface's handler, which it reads by this getter's
5
+ # name (`android/src/main/jni/ANNContentHeights.cpp`).
6
+ -keepclassmembers class com.facebook.react.runtime.ReactSurfaceImpl {
7
+ com.facebook.react.fabric.SurfaceHandlerBinding getSurfaceHandler();
8
+ }
@@ -105,7 +105,7 @@ class BottomTabOptions {
105
105
  @JvmField var textColor: ThemeColour = NullThemeColour()
106
106
  @JvmField var selectedIconColor: ThemeColour = NullThemeColour()
107
107
  @JvmField var selectedTextColor: ThemeColour = NullThemeColour()
108
- @JvmField var font = FontOptions()
108
+ @ReadFromParent @JvmField var font = FontOptions()
109
109
  @JvmField var fontSize: Fraction = NullFraction()
110
110
  @JvmField var selectedFontSize: Fraction = NullFraction()
111
111
  @JvmField var iconInsets = IconInsetsOptions()
@@ -31,7 +31,7 @@ class ButtonOptions(@JvmField var id: Text = NullText()) {
31
31
  @JvmField var systemItem: Text = NullText()
32
32
  @JvmField var text: Text = NullText()
33
33
  @JvmField var accessibilityLabel: Text = NullText()
34
- @JvmField var font = FontOptions()
34
+ @ReadFromParent @JvmField var font = FontOptions()
35
35
  @JvmField var fontSize: Fraction = NullFraction()
36
36
  @JvmField var enabled: Bool = NullBool()
37
37
  @JvmField var disableIconTint: Bool = NullBool()
@@ -22,7 +22,7 @@ class LargeTitleOptions {
22
22
  @JvmField var collapseOnScroll: Bool = NullBool()
23
23
  @JvmField var fontSize: Fraction = NullFraction()
24
24
  @JvmField var color: ThemeColour = NullThemeColour()
25
- @JvmField var font = FontOptions()
25
+ @ReadFromParent @JvmField var font = FontOptions()
26
26
 
27
27
  /**
28
28
  * When the large title shows: [ALWAYS], [NEVER] or [AUTOMATIC], as on the screen below. `displayMode` over
@@ -9,7 +9,7 @@ import com.facebook.react.bridge.ReadableMap
9
9
  * first).
10
10
  */
11
11
  open class LayoutAnimation(
12
- @JvmField var sharedElements: SharedElements = SharedElements(),
12
+ @OptionKey("sharedElementTransitions") @JvmField var sharedElements: SharedElements = SharedElements(),
13
13
  @JvmField var elementTransitions: ElementTransitions = ElementTransitions(),
14
14
  ) {
15
15
  open fun hasValue(): Boolean = sharedElements.hasValue() || elementTransitions.hasValue()
@@ -0,0 +1,18 @@
1
+ package dev.apexnative.navigation.options
2
+
3
+ /**
4
+ * The JSON key of an option field whose name is not that key (`topBar.backButton` is `TopBarButtons.back`), for the
5
+ * paths of [OptionPaths]; also the flag that tells a value is given, which goes with it. A field without it answers to
6
+ * its own name.
7
+ */
8
+ @Target(AnnotationTarget.FIELD)
9
+ @Retention(AnnotationRetention.RUNTIME)
10
+ annotation class OptionKey(val name: String)
11
+
12
+ /**
13
+ * A group the reader takes from the object of the group that holds it, not from a key of its own (the font of a text,
14
+ * the top bar's buttons): for the paths of [OptionPaths] its fields answer to the keys of that object.
15
+ */
16
+ @Target(AnnotationTarget.FIELD)
17
+ @Retention(AnnotationRetention.RUNTIME)
18
+ annotation class ReadFromParent
@@ -0,0 +1,91 @@
1
+ package dev.apexnative.navigation.options
2
+
3
+ import dev.apexnative.navigation.options.params.Param
4
+ import dev.apexnative.navigation.options.params.ThemeColour
5
+ import java.lang.reflect.Field
6
+ import java.lang.reflect.Modifier
7
+ import java.util.concurrent.ConcurrentHashMap
8
+
9
+ /**
10
+ * The options at a JSON key path, the keys of the options joined with dots (`topBar.subtitle`, `bottomTab.badge`), as
11
+ * the readers parse them: `clearOptions` takes them out of a node's own options. Generic over the whole tree, by
12
+ * reflection on the fields of the groups: a field answers to the key of its name, or of its [OptionKey]; a group the
13
+ * reader takes from the object of its parent ([ReadFromParent]: the font of a text, the top bar's buttons) answers with
14
+ * its fields to the keys of that object. A group is a class with a `mergeWith` of its own class; anything else (a
15
+ * value, a color, a list, a button menu) ends a path.
16
+ */
17
+ internal object OptionPaths {
18
+ /**
19
+ * What a path names: the fields of [group] that answer to its last key, [members] (one, or a value and the flag
20
+ * that tells it is given, as `modal.sheet.largestUndimmedDetent`).
21
+ */
22
+ class Place(val group: Any, val members: List<Field>) {
23
+ /** The value at the path, of the first field. */
24
+ val value: Any? get() = members.first().get(group)
25
+ }
26
+
27
+ // The fields of each class walked, its superclasses' first.
28
+ private val fieldsOfType = ConcurrentHashMap<Class<*>, List<Field>>()
29
+
30
+ /**
31
+ * Takes the value at [path] out of [options], which it changes in place: the field gets the value a freshly made
32
+ * group has for it (unset; a group, an empty one). Whether the path names a field; an empty or unknown path, or
33
+ * one that goes on inside a value (a side of a color), changes nothing.
34
+ */
35
+ fun clear(options: Options, path: String): Boolean {
36
+ val place = find(options, path) ?: return false
37
+ val fresh = newGroup(place.group.javaClass) ?: return false
38
+ place.members.forEach { it.set(place.group, it.get(fresh)) }
39
+ return true
40
+ }
41
+
42
+ /** The fields [path] names in [options]; none for an empty or unknown path or one inside a value. */
43
+ fun find(options: Options, path: String): Place? {
44
+ val keys = path.split('.')
45
+ if (keys.any(String::isEmpty)) return null
46
+ var group: Any = options
47
+ for (key in keys.dropLast(1)) {
48
+ group = fieldOf(group, key)?.value?.takeIf(::isGroup) ?: return null
49
+ }
50
+ return fieldOf(group, keys.last())?.takeIf { newGroup(it.group.javaClass) != null }
51
+ }
52
+
53
+ // The fields of [group] that answer to [key]: its own, else those of a group read from the same object.
54
+ private fun fieldOf(group: Any, key: String): Place? {
55
+ val fields = fieldsOf(group.javaClass)
56
+ fields.filter { !it.isAnnotationPresent(ReadFromParent::class.java) && keyOf(it) == key }
57
+ .takeIf { it.isNotEmpty() }
58
+ ?.let { return Place(group, it) }
59
+ for (field in fields) {
60
+ if (!field.isAnnotationPresent(ReadFromParent::class.java)) continue
61
+ val inner = field.get(group)?.takeIf(::isGroup) ?: continue
62
+ fieldOf(inner, key)?.let { return it }
63
+ }
64
+ return null
65
+ }
66
+
67
+ private fun keyOf(field: Field): String = field.getAnnotation(OptionKey::class.java)?.name ?: field.name
68
+
69
+ private fun fieldsOf(type: Class<*>): List<Field> = fieldsOfType.getOrPut(type) {
70
+ generateSequence(type) { it.superclass }
71
+ .takeWhile { it != Any::class.java }
72
+ .toList()
73
+ .asReversed()
74
+ .flatMap { it.declaredFields.asList() }
75
+ .filter { !Modifier.isStatic(it.modifiers) && !it.isSynthetic }
76
+ .onEach { it.isAccessible = true }
77
+ }
78
+
79
+ private fun isGroup(value: Any): Boolean {
80
+ if (value is Param<*> || value is ThemeColour) return false
81
+ val type = value.javaClass
82
+ return type.declaredMethods.any { it.name == "mergeWith" && it.parameterTypes.contentEquals(arrayOf(type)) }
83
+ }
84
+
85
+ // A group as its class makes it, every field unset; none for a class without a constructor of no arguments.
86
+ private fun newGroup(type: Class<*>): Any? = try {
87
+ type.getDeclaredConstructor().newInstance()
88
+ } catch (_: ReflectiveOperationException) {
89
+ null
90
+ }
91
+ }
@@ -12,25 +12,28 @@ import dev.apexnative.navigation.options.parsers.FractionParser
12
12
  import dev.apexnative.navigation.options.parsers.TextParser
13
13
 
14
14
  /**
15
- * A height a sheet stops at: about half the screen, the full height, or a height in dp. `content` stops at half the
16
- * screen on Android: the Material sheet has no height of its content.
15
+ * A height a sheet stops at: about half the screen, the full height, the height of the screen's content, or a height in
16
+ * dp.
17
17
  */
18
18
  sealed class SheetDetent {
19
19
  object Medium : SheetDetent()
20
20
  object Large : SheetDetent()
21
+ object Content : SheetDetent()
21
22
  data class Height(val dp: Double) : SheetDetent()
22
23
 
23
24
  /** The detent as the app names it, in `sheetDetentChanged`. */
24
25
  fun toValue(): Any = when (this) {
25
26
  Medium -> "medium"
26
27
  Large -> "large"
28
+ Content -> "content"
27
29
  is Height -> dp
28
30
  }
29
31
 
30
32
  companion object {
31
33
  fun parse(value: Any?): SheetDetent? = when (value) {
32
- "medium", "content" -> Medium
34
+ "medium" -> Medium
33
35
  "large" -> Large
36
+ "content" -> Content
34
37
  // A number is a height only when it is finite and positive.
35
38
  is Number -> value.toDouble().takeIf { it.isFinite() && it > 0 }?.let { Height(it) }
36
39
  else -> null
@@ -47,7 +50,7 @@ class SheetOptions {
47
50
  @JvmField var selectedDetent: SheetDetent? = null
48
51
  @JvmField var largestUndimmedDetent: SheetDetent? = null
49
52
  /** The options give `largestUndimmedDetent`, `null` included. */
50
- @JvmField var hasLargestUndimmedDetent = false
53
+ @OptionKey("largestUndimmedDetent") @JvmField var hasLargestUndimmedDetent = false
51
54
  @JvmField var grabber: Bool = NullBool()
52
55
  @JvmField var expandsOnScroll: Bool = NullBool()
53
56
  @JvmField var cornerRadius: Fraction = NullFraction()
@@ -22,7 +22,7 @@ class TitleOptions {
22
22
  @JvmField var text: Text = NullText()
23
23
  @JvmField var fontSize: Fraction = NullFraction()
24
24
  @JvmField var color: ThemeColour = NullThemeColour()
25
- @JvmField var font = FontOptions()
25
+ @ReadFromParent @JvmField var font = FontOptions()
26
26
  @JvmField var component = TitleComponentOptions()
27
27
  /** Shared between copies, as its module wants. */
28
28
  @JvmField var menu: ButtonMenuOptions? = null
@@ -98,7 +98,7 @@ class SubtitleOptions {
98
98
  @JvmField var text: Text = NullText()
99
99
  @JvmField var fontSize: Fraction = NullFraction()
100
100
  @JvmField var color: ThemeColour = NullThemeColour()
101
- @JvmField var font = FontOptions()
101
+ @ReadFromParent @JvmField var font = FontOptions()
102
102
  @JvmField var alignment: Text = NullText()
103
103
 
104
104
  fun hasValue(): Boolean =
@@ -109,7 +109,7 @@ class TopBarBackgroundOptions {
109
109
  */
110
110
  class ScrollEdgeAppearanceTitleOptions {
111
111
  @JvmField var color: ThemeColour = NullThemeColour()
112
- @JvmField var font = FontOptions()
112
+ @ReadFromParent @JvmField var font = FontOptions()
113
113
  @JvmField var fontSize: Fraction = NullFraction()
114
114
 
115
115
  fun hasValue(): Boolean = color.hasValue() || font.hasValue() || fontSize.hasValue()
@@ -34,12 +34,14 @@ class TopBarOptions {
34
34
  @JvmField var rightButtonDisabledColor: ThemeColour = NullThemeColour()
35
35
  @JvmField var centerButtonColor: ThemeColour = NullThemeColour()
36
36
  @JvmField var centerButtonDisabledColor: ThemeColour = NullThemeColour()
37
+ @JvmField var menuBackgroundColor: ThemeColour = NullThemeColour()
38
+ @JvmField var menuTextColor: ThemeColour = NullThemeColour()
37
39
  @JvmField var drawBehind: Bool = NullBool()
38
40
  @JvmField var testID: Text = NullText()
39
41
  @JvmField var title = TitleOptions()
40
42
  @JvmField var subtitle = SubtitleOptions()
41
43
  /** `backButton`, `leftButtons`, `rightButtons`, `centerButtons`. */
42
- @JvmField var buttons = TopBarButtons()
44
+ @ReadFromParent @JvmField var buttons = TopBarButtons()
43
45
  @JvmField var customizationId: Text = NullText()
44
46
  /** Shared between copies, as its module wants. */
45
47
  @JvmField var overflowMenu: ButtonMenuOptions? = null
@@ -63,7 +65,8 @@ class TopBarOptions {
63
65
  hideOnTap.hasValue() || hideOnKeyboard.hasValue() || leftButtonColor.hasValue() ||
64
66
  rightButtonColor.hasValue() || leftButtonBackgroundColor.hasValue() || rightButtonBackgroundColor.hasValue() ||
65
67
  leftButtonDisabledColor.hasValue() || rightButtonDisabledColor.hasValue() || centerButtonColor.hasValue() ||
66
- centerButtonDisabledColor.hasValue() || drawBehind.hasValue() || testID.hasValue() || title.hasValue() ||
68
+ centerButtonDisabledColor.hasValue() || menuBackgroundColor.hasValue() || menuTextColor.hasValue() ||
69
+ drawBehind.hasValue() || testID.hasValue() || title.hasValue() ||
67
70
  subtitle.hasValue() || buttons.hasValue() || customizationId.hasValue() || overflowMenu != null ||
68
71
  verticalBarKeeps.hasValue() || background.hasValue() || scrollEdgeAppearance.hasValue() ||
69
72
  barStyle.hasValue() || style.hasValue() || noBorder.hasValue() || searchBar.hasAnyValue() ||
@@ -85,6 +88,8 @@ class TopBarOptions {
85
88
  rightButtonDisabledColor = rightButtonDisabledColor.mergeWith(other.rightButtonDisabledColor)
86
89
  centerButtonColor = centerButtonColor.mergeWith(other.centerButtonColor)
87
90
  centerButtonDisabledColor = centerButtonDisabledColor.mergeWith(other.centerButtonDisabledColor)
91
+ menuBackgroundColor = menuBackgroundColor.mergeWith(other.menuBackgroundColor)
92
+ menuTextColor = menuTextColor.mergeWith(other.menuTextColor)
88
93
  if (other.drawBehind.hasValue()) drawBehind = other.drawBehind
89
94
  if (other.testID.hasValue()) testID = other.testID
90
95
  title.mergeWith(other.title)
@@ -127,6 +132,8 @@ class TopBarOptions {
127
132
  rightButtonDisabledColor = rightButtonDisabledColor.withDefault(defaults.rightButtonDisabledColor)
128
133
  centerButtonColor = centerButtonColor.withDefault(defaults.centerButtonColor)
129
134
  centerButtonDisabledColor = centerButtonDisabledColor.withDefault(defaults.centerButtonDisabledColor)
135
+ menuBackgroundColor = menuBackgroundColor.withDefault(defaults.menuBackgroundColor)
136
+ menuTextColor = menuTextColor.withDefault(defaults.menuTextColor)
130
137
  if (!drawBehind.hasValue()) drawBehind = defaults.drawBehind
131
138
  if (!testID.hasValue()) testID = defaults.testID
132
139
  title.mergeWithDefault(defaults.title)
@@ -178,6 +185,8 @@ class TopBarOptions {
178
185
  options.rightButtonDisabledColor = ThemeColour.parse(context, map, "rightButtonDisabledColor")
179
186
  options.centerButtonColor = ThemeColour.parse(context, map, "centerButtonColor")
180
187
  options.centerButtonDisabledColor = ThemeColour.parse(context, map, "centerButtonDisabledColor")
188
+ options.menuBackgroundColor = ThemeColour.parse(context, map, "menuBackgroundColor")
189
+ options.menuTextColor = ThemeColour.parse(context, map, "menuTextColor")
181
190
  options.drawBehind = BoolParser.parse(map, "drawBehind")
182
191
  options.testID = TextParser.parse(map, "testID")
183
192
  options.title = map.group("title") { TitleOptions.parse(context, it) }
@@ -212,13 +221,13 @@ class TopBarOptions {
212
221
  * don't give it; `[]` takes the buttons of the layers below away.
213
222
  */
214
223
  class TopBarButtons {
215
- @JvmField var back = BackButtonOptions()
224
+ @OptionKey("backButton") @JvmField var back = BackButtonOptions()
216
225
  /** `topBar.leftButtons`; null when the options don't give them. */
217
- @JvmField var left: List<ButtonOptions>? = null
226
+ @OptionKey("leftButtons") @JvmField var left: List<ButtonOptions>? = null
218
227
  /** `topBar.rightButtons`; null when the options don't give them. */
219
- @JvmField var right: List<ButtonOptions>? = null
228
+ @OptionKey("rightButtons") @JvmField var right: List<ButtonOptions>? = null
220
229
  /** `topBar.centerButtons`; null when the options don't give them. */
221
- @JvmField var center: List<ButtonOptions>? = null
230
+ @OptionKey("centerButtons") @JvmField var center: List<ButtonOptions>? = null
222
231
 
223
232
  fun hasValue(): Boolean = back.hasValue() || left != null || right != null || center != null
224
233
 
@@ -263,7 +272,7 @@ class BackButtonOptions {
263
272
  @JvmField var showTitle: Bool = NullBool()
264
273
  @JvmField var color: ThemeColour = NullThemeColour()
265
274
  @JvmField var fontSize: Fraction = NullFraction()
266
- @JvmField var font = FontOptions()
275
+ @ReadFromParent @JvmField var font = FontOptions()
267
276
  @JvmField var iconBackground = IconBackgroundOptions()
268
277
  @JvmField var testID: Text = NullText()
269
278
  @JvmField var enableMenu: Bool = NullBool()
@@ -0,0 +1,95 @@
1
+ package dev.apexnative.navigation.react
2
+
3
+ import android.content.Context
4
+ import android.util.Log
5
+ import com.facebook.react.interfaces.fabric.ReactSurface
6
+ import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
7
+ import com.facebook.react.modules.i18nmanager.I18nUtil
8
+ import com.facebook.react.runtime.ReactSurfaceImpl
9
+ import com.facebook.soloader.SoLoader
10
+ import dev.apexnative.navigation.utils.NavigationLog
11
+
12
+ /**
13
+ * The height of React content laid out at a width without a height limit, as iOS measures a sheet's `'content'`
14
+ * detent: Fabric lays out a copy of the tree, so the shown layout stays as it is. A React root's surface measures
15
+ * through `SurfaceHandler::measure`; a slot's content in the slot's own layout, once asked for ([requestSlot]), which
16
+ * keeps the height for [slotHeight] (`ANNSlotContentHeights`, the C++ iOS shares). The native half is the library's
17
+ * own C++ (`android/src/main/jni`), in the codegen library of its components; when that library doesn't load, nothing
18
+ * is measured. Main thread.
19
+ */
20
+ internal object ContentHeights {
21
+ private const val LIBRARY = "react_codegen_ApexNavigationSpec"
22
+
23
+ private val loaded: Boolean by lazy {
24
+ try {
25
+ SoLoader.loadLibrary(LIBRARY)
26
+ true
27
+ } catch (e: Throwable) {
28
+ Log.w(NavigationLog.TAG, "[ApexNavigation] $LIBRARY doesn't load: no content is measured", e)
29
+ false
30
+ }
31
+ }
32
+
33
+ /**
34
+ * The height of [surface]'s content at [width] pixels, in pixels, with the layout context the surface lays out
35
+ * with (`ReactSurfaceImpl.updateLayoutSpecs`); null before the surface runs or renders, or when it can't measure.
36
+ */
37
+ fun surfaceHeight(context: Context, surface: ReactSurface, width: Int): Int? {
38
+ if (width <= 0 || surface !is ReactSurfaceImpl || !loaded) return null
39
+ val density = context.resources.displayMetrics.density
40
+ val scalesFont = ReactNativeFeatureFlags.enableFontScaleChangesUpdatingLayout()
41
+ val fontScale = if (scalesFont) context.resources.configuration.fontScale else 1f
42
+ val height = try {
43
+ nativeSurfaceHeight(
44
+ surface,
45
+ width / density,
46
+ density,
47
+ fontScale,
48
+ I18nUtil.instance.isRTL(context),
49
+ I18nUtil.instance.doLeftAndRightSwapInRTL(context),
50
+ )
51
+ } catch (e: Throwable) {
52
+ Log.w(NavigationLog.TAG, "[ApexNavigation] the content of a surface can't be measured", e)
53
+ return null
54
+ }
55
+ return if (height > 0f) Math.round(height * density) else null
56
+ }
57
+
58
+ /** The content of the slot [slotId] is measured from its next layout on. */
59
+ fun requestSlot(slotId: String) {
60
+ if (loaded) nativeRequestSlot(slotId)
61
+ }
62
+
63
+ /** The content of the slot [slotId] is measured. */
64
+ fun isSlotRequested(slotId: String): Boolean = loaded && nativeIsSlotRequested(slotId)
65
+
66
+ /** The height of the content of the slot [slotId] at its last layout, in dp; null before it. */
67
+ fun slotHeight(slotId: String): Float? = if (loaded) nativeSlotHeight(slotId).takeIf { it >= 0f } else null
68
+
69
+ /** The screen of the slot [slotId] went: nothing more is measured for it. */
70
+ fun forgetSlot(slotId: String) {
71
+ if (loaded) nativeForgetSlot(slotId)
72
+ }
73
+
74
+ @JvmStatic
75
+ private external fun nativeRequestSlot(slotId: String)
76
+
77
+ @JvmStatic
78
+ private external fun nativeIsSlotRequested(slotId: String): Boolean
79
+
80
+ @JvmStatic
81
+ private external fun nativeSlotHeight(slotId: String): Float
82
+
83
+ @JvmStatic
84
+ private external fun nativeForgetSlot(slotId: String)
85
+
86
+ @JvmStatic
87
+ private external fun nativeSurfaceHeight(
88
+ surface: ReactSurfaceImpl,
89
+ width: Float,
90
+ pointScale: Float,
91
+ fontScale: Float,
92
+ rtl: Boolean,
93
+ swapLeftAndRight: Boolean,
94
+ ): Float
95
+ }
@@ -0,0 +1,66 @@
1
+ package dev.apexnative.navigation.react
2
+
3
+ import com.facebook.react.bridge.UIManager
4
+ import com.facebook.react.bridge.UIManagerListener
5
+ import com.facebook.react.common.annotations.UnstableReactNativeAPI
6
+ import com.facebook.react.uimanager.UIManagerHelper
7
+ import com.facebook.react.uimanager.common.UIManagerType
8
+ import dev.apexnative.navigation.NavigationApplication
9
+
10
+ /**
11
+ * The handlers of a screen's content that hear each mount of Fabric (`IReactView.setOnContentMounted`), by key, while
12
+ * [hears] holds (the content is in a window and not destroyed). Fabric tells every mount of the app, on the main
13
+ * thread, not those of one surface: the handlers measure and compare, so a mount elsewhere costs only that. It listens
14
+ * only while a handler waits. Main thread only.
15
+ */
16
+ internal class ContentMounts(private val hears: () -> Boolean) {
17
+ private val handlers = LinkedHashMap<String, Runnable>()
18
+ // The UI manager that tells the mounts; none while no handler waits.
19
+ private var uiManager: UIManager? = null
20
+
21
+ // React Native marks the listener unstable; it is the one place that tells a mount.
22
+ @OptIn(UnstableReactNativeAPI::class)
23
+ private val listener = object : UIManagerListener {
24
+ override fun willDispatchViewUpdates(uiManager: UIManager) = Unit
25
+
26
+ override fun willMountItems(uiManager: UIManager) = Unit
27
+
28
+ override fun didMountItems(uiManager: UIManager) {
29
+ if (hears()) run()
30
+ }
31
+
32
+ override fun didDispatchMountItems(uiManager: UIManager) = Unit
33
+
34
+ override fun didScheduleMountItems(uiManager: UIManager) = Unit
35
+ }
36
+
37
+ /** [handler] runs after each mount under [key]; null removes the handler of [key]. */
38
+ fun set(key: String, handler: Runnable?) {
39
+ if (handler == null) handlers.remove(key) else handlers[key] = handler
40
+ if (handlers.isEmpty()) stop() else start()
41
+ }
42
+
43
+ /** Runs the handlers now: a mount the content tells itself. */
44
+ fun run() = handlers.values.toList().forEach(Runnable::run)
45
+
46
+ /** The content goes: no handler runs again. */
47
+ fun clear() {
48
+ handlers.clear()
49
+ stop()
50
+ }
51
+
52
+ @OptIn(UnstableReactNativeAPI::class)
53
+ private fun start() {
54
+ if (uiManager != null) return
55
+ val reactContext = NavigationApplication.instanceOrNull?.reactGatewayOrNull?.currentReactContext ?: return
56
+ uiManager = UIManagerHelper.getUIManager(reactContext, UIManagerType.FABRIC)?.also {
57
+ it.addUIManagerEventListener(listener)
58
+ }
59
+ }
60
+
61
+ @OptIn(UnstableReactNativeAPI::class)
62
+ private fun stop() {
63
+ uiManager?.removeUIManagerEventListener(listener)
64
+ uiManager = null
65
+ }
66
+ }
@@ -55,6 +55,10 @@ class NavigationTurboModule(reactContext: ReactApplicationContext) : NativeANNTu
55
55
  override fun mergeOptions(componentId: String, options: ReadableMap) =
56
56
  optionsCommands.mergeOptions(componentId, options)
57
57
 
58
+ // Development (Fast Refresh): the options an edit took out of a screen that shows; as mergeOptions without one.
59
+ override fun clearOptions(componentId: String, paths: ReadableArray) =
60
+ optionsCommands.clearOptions(componentId, paths)
61
+
58
62
  override fun push(commandId: String, componentId: String, layout: ReadableMap, promise: Promise) {
59
63
  val what = "push the screen '${LayoutReader.idOf(layout)}'"
60
64
  runner.run(