@apexnative/navigation 1.0.0-alpha.3 → 1.0.0-alpha.5

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 (239) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/android/CLAUDE.md +26 -9
  3. package/android/CORE.md +32 -17
  4. package/android/RECIPES.md +9 -3
  5. package/android/src/main/java/dev/apexnative/navigation/NavigationActivity.kt +25 -9
  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/AnimationsOptions.kt +2 -2
  11. package/android/src/main/java/dev/apexnative/navigation/options/ButtonMenuOptions.kt +2 -2
  12. package/android/src/main/java/dev/apexnative/navigation/options/ButtonOptions.kt +2 -2
  13. package/android/src/main/java/dev/apexnative/navigation/options/HardwareBackButtonOptions.kt +9 -1
  14. package/android/src/main/java/dev/apexnative/navigation/options/OptionsCache.kt +8 -4
  15. package/android/src/main/java/dev/apexnative/navigation/options/StackAnimationOptions.kt +40 -2
  16. package/android/src/main/java/dev/apexnative/navigation/options/UserChoices.kt +159 -0
  17. package/android/src/main/java/dev/apexnative/navigation/react/ArrangementStates.kt +6 -6
  18. package/android/src/main/java/dev/apexnative/navigation/react/CommandAnswer.kt +29 -36
  19. package/android/src/main/java/dev/apexnative/navigation/react/CommandContract.kt +59 -0
  20. package/android/src/main/java/dev/apexnative/navigation/react/Hinge.kt +33 -22
  21. package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +5 -4
  22. package/android/src/main/java/dev/apexnative/navigation/react/PendingCalls.kt +48 -0
  23. package/android/src/main/java/dev/apexnative/navigation/react/ReactView.kt +39 -20
  24. package/android/src/main/java/dev/apexnative/navigation/react/ReloadCoordinator.kt +7 -4
  25. package/android/src/main/java/dev/apexnative/navigation/react/ScreenInsets.kt +6 -6
  26. package/android/src/main/java/dev/apexnative/navigation/react/ScreenRegions.kt +10 -5
  27. package/android/src/main/java/dev/apexnative/navigation/react/events/AppearanceDispatcher.kt +47 -46
  28. package/android/src/main/java/dev/apexnative/navigation/react/events/EventEmitter.kt +34 -7
  29. package/android/src/main/java/dev/apexnative/navigation/react/events/NavigationState.kt +154 -0
  30. package/android/src/main/java/dev/apexnative/navigation/safearea/SafeAreaView.kt +6 -4
  31. package/android/src/main/java/dev/apexnative/navigation/slots/SlotContainers.kt +10 -7
  32. package/android/src/main/java/dev/apexnative/navigation/slots/SlotScreenView.kt +12 -12
  33. package/android/src/main/java/dev/apexnative/navigation/slots/SlotView.kt +4 -3
  34. package/android/src/main/java/dev/apexnative/navigation/slots/SlotViews.kt +40 -0
  35. package/android/src/main/java/dev/apexnative/navigation/utils/ImageKey.kt +66 -0
  36. package/android/src/main/java/dev/apexnative/navigation/utils/ImageLoader.kt +7 -89
  37. package/android/src/main/java/dev/apexnative/navigation/utils/ImageRequests.kt +76 -0
  38. package/android/src/main/java/dev/apexnative/navigation/utils/OptionImages.kt +262 -0
  39. package/android/src/main/java/dev/apexnative/navigation/utils/ViewController.kt +3 -1
  40. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/arrangement/ArrangementController.kt +3 -2
  41. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/AppliedTabCalls.kt +49 -0
  42. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsController.kt +25 -17
  43. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsPresenter.kt +25 -24
  44. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ComponentViewController.kt +6 -2
  45. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ContentReadiness.kt +2 -2
  46. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/RenderWait.kt +27 -7
  47. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ScreenReadiness.kt +103 -56
  48. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalCoordinator.kt +5 -5
  49. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/BackCallbackCheck.kt +50 -0
  50. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/BackCallbackState.kt +45 -14
  51. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt +25 -8
  52. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/RootCoordinator.kt +8 -5
  53. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/overlay/OverlayCoordinator.kt +8 -8
  54. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/Capabilities.kt +11 -4
  55. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuController.kt +1 -1
  56. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitPanes.kt +23 -0
  57. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitViewController.kt +61 -15
  58. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackCommands.kt +28 -18
  59. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +25 -12
  60. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackDisplay.kt +2 -1
  61. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackQueue.kt +85 -29
  62. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +2 -2
  63. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabPresenter.kt +7 -5
  64. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/ScreenSearch.kt +3 -5
  65. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/StackSearchPresenter.kt +5 -8
  66. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/BarComponents.kt +5 -51
  67. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/MenuChecks.kt +13 -56
  68. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +14 -11
  69. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/viewcontroller/ViewController.kt +56 -21
  70. package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/BottomTabs.kt +4 -2
  71. package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/CustomBottomTabItemView.kt +0 -5
  72. package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/Fab.kt +14 -10
  73. package/cli/doctor/checks/app.js +61 -13
  74. package/cli/doctor/checks/splash.js +4 -22
  75. package/cli/migrate/native.js +18 -0
  76. package/cli/setup/source.js +2 -1
  77. package/cli/setup/steps/mainApplication.js +134 -19
  78. package/cli/splash/index.js +3 -1
  79. package/docs-md/architecture/transitions.md +6 -7
  80. package/docs-md/containers/modals.md +2 -2
  81. package/docs-md/guides/cli.md +1 -2
  82. package/docs-md/help/glossary.md +1 -1
  83. package/docs-md/help/troubleshooting.md +22 -5
  84. package/docs-md/index.json +4 -0
  85. package/docs-md/migration/from-react-native-navigation.md +8 -2
  86. package/docs-md/screen-ui/back-and-gestures.md +24 -2
  87. package/docs-md/start/installation.md +4 -2
  88. package/ios/CLAUDE.md +17 -5
  89. package/ios/CORE.md +18 -13
  90. package/ios/Contract/ANNCommandAnswer.h +9 -2
  91. package/ios/Contract/ANNCommandAnswer.mm +28 -30
  92. package/ios/Contract/ANNCommandContract.h +15 -0
  93. package/ios/Contract/ANNCommandContract.mm +78 -0
  94. package/ios/Core/ANNAppearanceDispatcher.h +5 -4
  95. package/ios/Core/ANNAppearanceDispatcher.mm +12 -1
  96. package/ios/Core/ANNErrorHandler.h +1 -1
  97. package/ios/Core/ANNErrorHandler.mm +3 -1
  98. package/ios/Core/ANNEventEmitter.h +1 -1
  99. package/ios/Core/ANNEventEmitter.mm +2 -2
  100. package/ios/Core/ANNJavaScriptCalls.h +34 -0
  101. package/ios/Core/ANNJavaScriptCalls.mm +61 -0
  102. package/ios/Core/ANNNavigationState.h +7 -5
  103. package/ios/Core/ANNNavigationState.mm +8 -7
  104. package/ios/Core/ANNNodeHelper.h +32 -12
  105. package/ios/Core/ANNNodeHelper.mm +100 -3
  106. package/ios/Core/ANNRenderWait.h +15 -1
  107. package/ios/Core/ANNRenderWait.mm +119 -17
  108. package/ios/Core/UIViewController+LayoutProtocol.h +9 -2
  109. package/ios/Core/UIViewController+LayoutProtocol.mm +8 -0
  110. package/ios/Layouts/Modal/ANNModalCoordinator.h +1 -1
  111. package/ios/Layouts/Modal/ANNModalCoordinator.mm +55 -14
  112. package/ios/Layouts/Root/ANNSplashViewController.h +5 -3
  113. package/ios/Layouts/Root/ANNSplashViewController.mm +147 -0
  114. package/ios/Layouts/Screen/ANNComponentViewController.h +8 -0
  115. package/ios/Layouts/Screen/ANNComponentViewController.mm +32 -5
  116. package/ios/Layouts/Screen/ANNScreenPlace.h +41 -0
  117. package/ios/Layouts/Screen/ANNScreenPlace.mm +175 -0
  118. package/ios/Layouts/SplitView/ANNSplitViewCommands.h +3 -2
  119. package/ios/Layouts/SplitView/ANNSplitViewCommands.mm +105 -39
  120. package/ios/Layouts/SplitView/ANNSplitViewController.mm +4 -2
  121. package/ios/Layouts/Stack/ANNPreviewPush.h +5 -5
  122. package/ios/Layouts/Stack/ANNPreviewPush.mm +85 -22
  123. package/ios/Layouts/Stack/ANNStackCommands.mm +1 -0
  124. package/ios/Layouts/Stack/ANNStackController.mm +26 -3
  125. package/ios/Layouts/Stack/ANNStackQueue.h +3 -1
  126. package/ios/Layouts/Stack/ANNStackQueue.mm +56 -43
  127. package/ios/Layouts/Stack/UINavigationController+ANNCommands.h +4 -2
  128. package/ios/Layouts/Stack/UINavigationController+ANNCommands.mm +2 -3
  129. package/ios/Layouts/Tabs/ANNBottomTabItemBuilder.h +4 -0
  130. package/ios/Layouts/Tabs/ANNBottomTabItemBuilder.mm +11 -0
  131. package/ios/Layouts/Tabs/ANNBottomTabsController.mm +13 -5
  132. package/ios/Layouts/Tabs/ANNBottomTabsPresenter.h +2 -2
  133. package/ios/Layouts/Tabs/ANNBottomTabsPresenter.mm +109 -7
  134. package/ios/Options/ANNOptionsCache.h +7 -6
  135. package/ios/Options/ANNSearchBarOptions.h +4 -3
  136. package/ios/Options/ANNUserChoice.h +57 -0
  137. package/ios/Options/ANNUserChoice.mm +44 -0
  138. package/ios/Options/Params/ANNImageSource.h +61 -0
  139. package/ios/Options/Params/ANNImageSource.mm +101 -0
  140. package/ios/Options/Params/ANNOptionImages.h +37 -0
  141. package/ios/Options/Params/ANNOptionImages.mm +284 -0
  142. package/ios/Options/Params/Image.h +11 -2
  143. package/ios/Options/Params/Image.mm +15 -2
  144. package/ios/Options/Params/ImageParser.h +5 -5
  145. package/ios/Options/Params/ImageParser.mm +28 -46
  146. package/ios/Public/ApexNavigation/ApexNavigation.h +1 -1
  147. package/ios/RECIPES.md +11 -5
  148. package/ios/React/Slots/ANNSlotComponentView.mm +24 -17
  149. package/ios/React/Slots/ANNSlotContainers.h +14 -6
  150. package/ios/React/Slots/ANNSlotContainers.mm +75 -2
  151. package/ios/TopBar/ANNBarButtons.h +9 -5
  152. package/ios/TopBar/ANNBarButtons.mm +211 -63
  153. package/ios/TopBar/ANNIconButtonImage.h +1 -0
  154. package/ios/TopBar/ANNIconButtonImage.mm +34 -1
  155. package/ios/TopBar/ANNLargeTitlePresenter.h +13 -0
  156. package/ios/TopBar/ANNLargeTitlePresenter.mm +26 -3
  157. package/ios/TopBar/ANNSearchPresenter.mm +170 -19
  158. package/ios/TopBar/ANNTopBarPresenter.h +5 -0
  159. package/ios/TopBar/ANNTopBarPresenter.mm +60 -11
  160. package/ios/TurboModules/ANNTurboModule.mm +39 -21
  161. package/lib/module/ARCHITECTURE.md +18 -18
  162. package/lib/module/adapters/NativeANNTurboModule.js.map +1 -1
  163. package/lib/module/api/commands/commands.js +54 -6
  164. package/lib/module/api/commands/commands.js.map +1 -1
  165. package/lib/module/api/navigation.js.map +1 -1
  166. package/lib/module/engine/commands.js +9 -7
  167. package/lib/module/engine/commands.js.map +1 -1
  168. package/lib/module/engine/layout.js +13 -9
  169. package/lib/module/engine/layout.js.map +1 -1
  170. package/lib/module/engine/options.js +44 -11
  171. package/lib/module/engine/options.js.map +1 -1
  172. package/lib/module/engine/places.js +52 -7
  173. package/lib/module/engine/places.js.map +1 -1
  174. package/lib/module/engine/props.js +30 -1
  175. package/lib/module/engine/props.js.map +1 -1
  176. package/lib/module/engine/rememberedOptions.js +1 -1
  177. package/lib/module/engine/rememberedOptions.js.map +1 -1
  178. package/lib/module/engine/screens.js +22 -12
  179. package/lib/module/engine/screens.js.map +1 -1
  180. package/lib/module/interfaces/contract/commands.json +101 -0
  181. package/lib/typescript/adapters/NativeANNTurboModule.d.ts +5 -2
  182. package/lib/typescript/adapters/NativeANNTurboModule.d.ts.map +1 -1
  183. package/lib/typescript/api/commands/commands.d.ts.map +1 -1
  184. package/lib/typescript/api/commands/presentation.d.ts +4 -3
  185. package/lib/typescript/api/commands/presentation.d.ts.map +1 -1
  186. package/lib/typescript/api/events/navigationEvents.d.ts +1 -1
  187. package/lib/typescript/api/events/navigationEvents.d.ts.map +1 -1
  188. package/lib/typescript/api/hooks/useNavigation.d.ts +1 -1
  189. package/lib/typescript/api/hooks/useNavigation.d.ts.map +1 -1
  190. package/lib/typescript/api/navigation.d.ts +4 -1
  191. package/lib/typescript/api/navigation.d.ts.map +1 -1
  192. package/lib/typescript/engine/commands.d.ts.map +1 -1
  193. package/lib/typescript/engine/options.d.ts +5 -2
  194. package/lib/typescript/engine/options.d.ts.map +1 -1
  195. package/lib/typescript/engine/places.d.ts +7 -2
  196. package/lib/typescript/engine/places.d.ts.map +1 -1
  197. package/lib/typescript/engine/props.d.ts +8 -1
  198. package/lib/typescript/engine/props.d.ts.map +1 -1
  199. package/lib/typescript/engine/rememberedOptions.d.ts.map +1 -1
  200. package/lib/typescript/engine/screens.d.ts.map +1 -1
  201. package/lib/typescript/interfaces/ComponentEvents.d.ts +2 -2
  202. package/lib/typescript/interfaces/ComponentEvents.d.ts.map +1 -1
  203. package/lib/typescript/interfaces/Events.d.ts +19 -10
  204. package/lib/typescript/interfaces/Events.d.ts.map +1 -1
  205. package/lib/typescript/interfaces/Options.d.ts +1 -1
  206. package/lib/typescript/interfaces/Options.d.ts.map +1 -1
  207. package/lib/typescript/interfaces/options/animations.d.ts +23 -4
  208. package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
  209. package/lib/typescript/interfaces/options/buttons.d.ts +13 -5
  210. package/lib/typescript/interfaces/options/buttons.d.ts.map +1 -1
  211. package/lib/typescript/interfaces/options/stack.d.ts +9 -0
  212. package/lib/typescript/interfaces/options/stack.d.ts.map +1 -1
  213. package/lib/typescript/interfaces/options/topBar.d.ts +22 -9
  214. package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
  215. package/package.json +1 -1
  216. package/src/ARCHITECTURE.md +18 -18
  217. package/src/adapters/NativeANNTurboModule.ts +5 -2
  218. package/src/api/commands/commands.ts +48 -7
  219. package/src/api/commands/presentation.ts +4 -3
  220. package/src/api/events/navigationEvents.ts +1 -1
  221. package/src/api/hooks/useNavigation.ts +1 -1
  222. package/src/api/navigation.ts +4 -1
  223. package/src/engine/commands.ts +19 -7
  224. package/src/engine/layout.ts +16 -9
  225. package/src/engine/options.ts +39 -11
  226. package/src/engine/places.ts +65 -7
  227. package/src/engine/props.ts +30 -1
  228. package/src/engine/rememberedOptions.ts +4 -1
  229. package/src/engine/screens.tsx +23 -13
  230. package/src/interfaces/ComponentEvents.ts +2 -2
  231. package/src/interfaces/Events.ts +19 -10
  232. package/src/interfaces/Options.ts +1 -0
  233. package/src/interfaces/contract/commands.json +101 -0
  234. package/src/interfaces/options/animations.ts +24 -4
  235. package/src/interfaces/options/buttons.ts +13 -5
  236. package/src/interfaces/options/stack.ts +9 -0
  237. package/src/interfaces/options/topBar.ts +22 -9
  238. package/android/src/main/java/dev/apexnative/navigation/react/events/NavigationStateReporter.kt +0 -62
  239. package/android/src/main/java/dev/apexnative/navigation/react/events/StateHold.kt +0 -62
@@ -126,11 +126,13 @@ In an app as the React Native template makes it, the window shows **one React vi
126
126
 
127
127
  | File | What setup does | Why |
128
128
  | --- | --- | --- |
129
- | `MainApplication.kt` | Extends `NavigationApplication`. | It loads React Native itself and keeps one React host for all screens. Native screens made by `codegen` are registered there, before JS opens them. |
129
+ | `MainApplication.kt` | Extends `NavigationApplication`; keeps the template's `loadReactNative(this)` in `onCreate`, or adds it. | It keeps one React host for all screens, made after `loadReactNative(this)` loaded React Native. Native screens made by `codegen` are registered there, before JS opens them. |
130
130
  | `MainActivity.kt` | Extends `NavigationActivity`. | The activity holds the containers of the navigation instead of one React view. It gets the back press, edge-to-edge and configuration changes first. |
131
131
  | `AndroidManifest.xml` | Adds `android:enableOnBackInvokedCallback="true"` to `<application>`. | On the first screen the system plays its own animation back to the home screen as the user swipes back (predictive back). Android 16 turns it on for apps that target API 36; an app that targets less opts in here. A value the app set itself stays. See [Back to the home screen](../screen-ui/back-and-gestures#system-animation). |
132
132
  | `styles.xml` | `AppTheme` inherits `Theme.ApexNavigation`. | The bars, tabs and search are Material 3 views and take their colors from the app's theme. This one is Material 3 in neutral grays, light and dark; change any color in the same file. A theme of the app's own stays as it is. See [Android's colors](../architecture/theme-and-context#android). |
133
133
 
134
+ The main activity keeps the template's `android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"`. Without `orientation`, `screenSize`, `smallestScreenSize`, `screenLayout` or `uiMode` Android recreates the activity on a rotation, a resize, a fold or a light/dark switch, and every screen goes with it; `doctor` warns about it under Config changes.
135
+
134
136
  #### iOS files
135
137
 
136
138
  | File | What setup does | Why |
@@ -213,7 +215,7 @@ export {};
213
215
 
214
216
  | Group | What it checks |
215
217
  | --- | --- |
216
- | App | React Native and the New Architecture, the package, the files `setup` sets up, the entry point, React Native loaded twice, the status bar of the template, the generated code of native screens. Typed commands: `navigation-env.d.ts` there, pointing to the navigation, with its `context` line, and read by `tsconfig.json`. The splash screen made from its settings, with its logo inside the circle of Android's splash icon, and an iOS launch screen made with `UILaunchScreen` and no storyboard, which the navigation can't keep on screen. The app icon made from its settings, and an iOS icon set with no image, which the App Store refuses. |
218
+ | App | React Native and the New Architecture, the package, the files `setup` sets up, the entry point, React Native loaded twice, the status bar of the template, the generated code of native screens. Typed commands: `navigation-env.d.ts` there, pointing to the navigation, with its `context` line, and read by `tsconfig.json`. The splash screen made from its settings, with its logo inside the circle of Android's splash icon. The app icon made from its settings, and an iOS icon set with no image, which the App Store refuses. |
217
219
  | Navigation | From the app's code. Screens: no name given twice in `screens` and the groups, and no file of the screens' folder with a `defineScreen` or `nativeScreen` that no file imports, a screen made and never added. Links: each prefix of `linking` declared on both platforms, the declarations no prefix has, a main activity that isn't `singleTask`. Screen names, in a JavaScript app only: a name that `push`, `replace`, `showModal` or `showInColumn` gives and no screen has, with its file and line. |
218
220
  | (Android) | JDK 17 in use, the Android SDK, `minSdk`, how edge-to-edge works in the app. |
219
221
  | (iOS) | Xcode 26 or newer, CocoaPods, the pods of the navigation and whether they are out of date, the iOS version of the Podfile. |
package/ios/CLAUDE.md CHANGED
@@ -27,7 +27,7 @@ Swift is not used here: apps reach the core through the public headers (`Public/
27
27
  - `Layouts/SplitView/`: The split view module, plus the core's `ANNSplitViewPresenter` and `ANNSplitViewCommands`.
28
28
  - `Layouts/Arrangement/`: The pair (iOS 27.1): `ANNArrangementViewController`, `ANNArrangementState`.
29
29
  - `TopBar/`: The bar of a screen: title, buttons, back button, large title, appearance, search, toolbar, edge effects.
30
- - `Options/`, `Options/Params`: One `ANN<Group>Options` class per group, the field table, the value kinds and parsers (`Bool`, `Color`, `Text`, `Image`, …).
30
+ - `Options/`, `Options/Params`: One `ANN<Group>Options` class per group, the field table, the value kinds and parsers (`Bool`, `Color`, `Text`, `Image`, …), the images' cache and loader (`ANNOptionImages`).
31
31
  - `Animations/`: The custom transition engine: plan, animator, wait, frame driver, rules; `Elements/` and `Interpolators/`.
32
32
  - `React/`, `React/Slots/`: React views of screens and components, the surface host, the safe area view, the slot host and slot screens.
33
33
  - `Public/ApexNavigation/`: The headers apps import: `ApexNavigation.h`, `ANNAppDelegate.h`, `ANNSceneDelegate.h`.
@@ -42,7 +42,7 @@ Top-level files: `Package.swift` (SwiftPM; `headerFolders` lists every folder wh
42
42
  - Launch, the app-facing API — `Public/ApexNavigation/`; `Core/`: `ApexNavigation.mm`, `ANNAppDelegate.mm`, `ANNSceneDelegate.mm`, `ApexNavigation+Internal.h`
43
43
  - Root, splash, reload — `Layouts/Root/`: `ANNRootCoordinator`, `ANNSplashViewController`; `Core/`: `ANNReloadCoordinator`
44
44
  - Screen: React root, slot, native screen — `Layouts/Screen/`: `ANNComponentViewController`; `React/`: `ANNReactView`, `ANNReactViewCreator`, `Slots/`; `Layouts/External/`
45
- - Screen: insets, background, keyboard focus — `Layouts/Screen/`: `ANNScreenInsets`, `ANNScreenSafeArea`, `ANNScreenBars`, `ANNComponentPresenter` (background, status bar blur), `ANNComponentViewController` (key commands)
45
+ - Screen: insets, background, keyboard focus — `Layouts/Screen/`: `ANNScreenInsets`, `ANNScreenSafeArea`, `ANNScreenBars`, `ANNScreenPlace` (the safe area of a parked screen), `ANNComponentPresenter` (background, status bar blur), `ANNComponentViewController` (key commands)
46
46
  - Safe area view — `React/`: `ANNSafeAreaViewComponentView`; `Layouts/Screen/`: `ANNScreenReadiness`; `cpp/`
47
47
  - Stack: queue, commands, back, gestures — `Layouts/Stack/`: `ANNStackQueue`, `ANNStackCommands`, `ANNStackController`, `InteractivePopGestureDelegate`, `ANNStackAnimatingDelegate`
48
48
  - Top bar: title, buttons, menus, large title — `TopBar/`: `ANNTopBarPresenter`, `ANNBarButtons`, `ANNBarAppearance`, `ANNLargeTitlePresenter`, `ANNBackButtonPresenter`, `ANNTitleView`, `ANNTitleGlassView`
@@ -60,24 +60,31 @@ Top-level files: `Package.swift` (SwiftPM; `headerFolders` lists every folder wh
60
60
  - System bars, orientation, direction — `Core/`: `ANNSystemUI`, `ANNSystemUIRules`, `ANNAppDirection`, `ANNNodePresenter` (the answers to UIKit)
61
61
  - Events and state — `Core/`: `ANNEventEmitter`, `ANNEventContract` (names and keys, made by `scripts/contract.mjs`), `ANNAppearanceDispatcher`, `ANNScreenAppearance`, `ANNNavigationState`; `TurboModules/`: `ANNTurboEventEmitter`
62
62
  - Options: parsing, merging, defaults — `Options/`: `ANNOptions`, `ANNOptionField`, `ANNOptionsReader`, `ANNDefaultOptions`, `ANNOptionsCache`; `Core/`: `ANNOptionsRepaint`
63
- - Error texts — `Contract/`: `ANNErrorTexts` (made from `src/interfaces/contract/errors.json` by `scripts/contract.mjs`); `Core/`: `ANNErrorHandler`
63
+ - Error texts and answers — `Contract/`: `ANNErrorTexts` and `ANNCommandContract` (made from `src/interfaces/contract/errors.json` and `commands.json` by `scripts/contract.mjs`); `Core/`: `ANNErrorHandler`
64
64
  - Links — `Core/`: `ANNDeepLinkQueue`; the `getLaunchLink` method of `ANNTurboModule`
65
65
  - Key commands — `Core/`: `ANNKeyCommands`; `Layouts/Screen/`: `ANNComponentViewController`
66
66
  - Hinge, regions — `Core/`: `ANNHinge`; `Layouts/Screen/`: `ANNScreenRegions`; `Layouts/Arrangement/`: `ANNArrangementState`
67
67
  - Native screens — `Layouts/External/`: `ANNExternalViewController`, `ANNNativeScreenCalls`; `Layouts/Screen/ANNNativeScreenContext.mm`
68
- - Slots — `React/Slots/`: `ANNSlotHost`, `ANNSlotContainers`, `ANNSlotScreenView`, `ANNSlotComponentView`; `cpp/`
68
+ - Slots — `React/Slots/`: `ANNSlotHost`, `ANNSlotContainers` (containers and slot views by slot id: a change goes
69
+ straight to the views of its id), `ANNSlotScreenView`, `ANNSlotComponentView`; `cpp/`
69
70
 
70
71
  ## Threads
71
72
 
72
73
  - **Module queue:** the layout check (`ANNLayoutReader`) and the sync getters. The check reads only stores safe from
73
74
  any thread (`ApexNavigation hasNativeScreenNamed:`, `ANNArrangementState canShowArrangements`).
74
75
  - **Main queue:** everything else. A command body starts with `ANNAssertMainQueue()`. The options of a layout are parsed
75
- here (`ANNLayoutTreeOptions`, images load while they are read). `ANNEventEmitter` moves an event from another queue
76
+ here (`ANNLayoutTreeOptions`; an image keeps only its source). `ANNEventEmitter` moves an event from another queue
76
77
  to the main queue.
78
+ - **The images' queue** (`ANNOptionImages`, `Options/Params`): every option image decodes there, through one cache.
79
+ An element that shows an image asks for it when it applies and waits for it (the first frame needs it); only a tab
80
+ item's new icon on the bar that shows comes later (`ANNBottomTabsPresenter`), the last build of the item winning.
77
81
  - **Sync getters** answer JS during a screen's first render: `getScreenInsets`, `getScreenRegions`,
78
82
  `getArrangementState`, `getHinge`, `getStartInfo`. They read `ANNScreenInsets`, `ANNScreenRegions`,
79
83
  `ANNArrangementState`, `ANNHinge lastValue`, written on the main queue and guarded by `@synchronized`.
80
84
  `ANNReloadCoordinator.generation` and `ANNEventEmitter.isCurrent` are readable from any thread.
85
+ - **After the calls of JavaScript:** `ANNJavaScriptCalls afterCallsMadeSoFar:` runs a step on the main queue after
86
+ every call JavaScript made by then, by the calls' own way (the JavaScript thread, the module's queue, the main
87
+ queue). A push's wait ends through it, so the `mergeOptions` of its screen's first commit come first.
81
88
 
82
89
  ## Checks
83
90
 
@@ -92,6 +99,11 @@ Top-level files: `Package.swift` (SwiftPM; `headerFolders` lists every folder wh
92
99
 
93
100
  - Native code never touches React Native's views (their subviews, their layout, their frames): Fabric owns and recycles
94
101
  them. Read them; what a transition writes (`alpha`, `transform`, a hidden flag) goes back on every path.
102
+ - A screen parked by a wait for its content (`ANNParking`) has no parent, and UIKit gives its view no safe area: it lays
103
+ out with the safe area of its place (`ANNScreenPlace`, measured on a navigation controller of the core's), or its
104
+ list would move as the push starts and UIKit would show the large title only after the transition. On iOS 26 its
105
+ large title also keeps a place of the core's for the push (`arrivesByPushOn:`). Check Showcase 1.4 (with a font
106
+ size), every section panel and 12.6 on push, pop and swipe after any change to waits, parking or the large title.
95
107
  - Every change of a stack's children goes through its `ANNStackQueue`. The core's own calls to UIKit's navigation
96
108
  come only from there; UIKit's own pops are followed (`uikitShows:`), not fought.
97
109
  - Every UIKit present and dismiss in the main window is a step in `ANNPresentationGate`. UIKit drops a transition
package/ios/CORE.md CHANGED
@@ -68,25 +68,29 @@ dispatcher.
68
68
  `+[ANNErrorTexts nowhereToShow]` when there is no window, then runs the body in `@try`. The body finds Home in
69
69
  `ANNNodeRegistry` and calls `+[ANNStackCommands push:componentId:what:answer:]`.
70
70
  4. **`ANNStackCommands`.** Finds the stack by walking `layoutParent`; checks `ANNLayoutValidator` (an id already shown,
71
- what a stack takes); reads the options of the whole tree with `ANNLayoutTreeOptions` (images load here); checks
72
- `ANNLayoutFactory canMakeTrees:`; a node with `preview.nativeID` goes to `ANNPreviewPush`; a `key` of an entry below
73
- goes back to that entry; else `+[ANNLayoutFactory nodeFromLayout:options:]` makes Profile (an
71
+ what a stack takes); reads the options of the whole tree with `ANNLayoutTreeOptions` (an image keeps its source,
72
+ loaded through `ANNOptionImages` when an element shows it); checks `ANNLayoutFactory canMakeTrees:`; a node with
73
+ `preview.nativeID` goes to `ANNPreviewPush`; a `key` of an entry below goes back to that entry; else
74
+ `+[ANNLayoutFactory nodeFromLayout:options:]` makes Profile (an
74
75
  `ANNComponentViewController` with an `ANNComponentPresenter`, content from `ANNReactViewCreator`) and registers it.
75
76
  No view is rendered yet.
76
77
  5. **`-[ANNStackQueue push:answer:…]`.** The node joins `entries` now (every check reads `entries`), the operation is
77
78
  queued, `runNext` starts it when the queue is at rest.
78
79
  6. **`startPush:` and `startPushTransition:`.** By `animations.push.waitForRender` the queue waits through
79
- `ANNRenderWait`, for the content and the bar components or for the bar components alone (a screen that waits for its
80
- content is parked unseen in the stack's view by `ANNParking`, `stackPark:`), then renders the node, drives the appearance of Home and Profile (`+[ANNAppearanceDispatcher driveAppearanceOf:]`), and calls
80
+ `ANNRenderWait`: unset, for the content at most 0.5 s, with `true` as long as it takes, then for the bar components
81
+ (their limits from the wait's start when unset, from the content's end with `true`; the screen is parked unseen in
82
+ the stack's view by `ANNParking`, `stackPark:`); the content's end, content there already too, waits for the calls
83
+ JavaScript made by then (`ANNJavaScriptCalls`: the JavaScript thread, the module's queue, the main queue), so the
84
+ options of the screen's first commit are in the bar; with `false` nothing. Then it renders the node, drives the appearance of Home and Profile (`+[ANNAppearanceDispatcher driveAppearanceOf:]`), and calls
81
85
  `+[ANNAppearanceDispatcher beginTransitionShowing:]`. Profile's `ComponentWillAppear` is sent now (held until its
82
86
  content mounted, 0.5 s at most). `stackChange:` pushes in UIKit; with an own animation `ANNTransitionPlan` and
83
87
  `ANNStackAnimatingDelegate` give UIKit the `ANNTransitionAnimator`.
84
88
  7. **The end.** `stackWhenTransitionEnds:` fires from the transition coordinator and calls `endPush:`, which calls
85
89
  `+[ANNAppearanceDispatcher endTransitionHiding:showing:events:answer:value:]`. In this order: `ComponentDidDisappear`
86
- of Home, `ComponentDidAppear` of Profile, `ANN.NavigationStateChanged` (`+[ANNNavigationState updateNow]`), the
87
- transition's `events`, then `-[ANNCommandAnswer resolve:]`: the state again if it changed
88
- (`reportBeforeAnswer`), the promise with Profile's id, then `ANN.CommandCompleted`. `finish:` frees the queue for
89
- the next operation.
90
+ of Home, `ComponentDidAppear` of Profile, `ANN.NavigationStateChanged` (`-[ANNCommandAnswer reportStateNow]`, the
91
+ answer's one state step), the transition's `events`, then `-[ANNCommandAnswer resolve:]`: the promise with
92
+ Profile's id, then `ANN.CommandCompleted`. A command answered without a transition's end (`setRoot` with layout
93
+ modals) sends its state from `resolve:`. `finish:` frees the queue for the next operation.
90
94
 
91
95
  Events go out through `ANNEventEmitter` on the main queue and reach JS through `ANNTurboEventEmitter`.
92
96
 
@@ -113,16 +117,17 @@ Each of these owns one rule. Call it; do not copy it.
113
117
 
114
118
  - `ANNCommandRunner`: The one path of every async command: generation, check, main queue, exception guard. Called by: `ANNTurboModule`.
115
119
  - `ANNLayoutReader`: The layout check, depth first, the same order and texts as Android. Module queue. Called by: The runner's check blocks.
116
- - `ANNCommandAnswer`: The answer: state, then promise, then `ANN.CommandCompleted`; once; silent for a JavaScript that went. Called by: Every coordinator and queue.
120
+ - `ANNCommandAnswer`: The answer: state (one step per answer, `reportStateNow`, asked by the end of its transition or by `resolve:`), then promise, then `ANN.CommandCompleted`; once; silent for a JavaScript that went. Called by: Every coordinator and queue.
117
121
  - `ANNNodeRegistry`: Every node by id, weakly. Filled by the factory, cleared at a reload. Called by: Commands, modules, `findViewController:`.
118
122
  - `ANNLayoutFactory`: The core code that names module classes (`ANNSplitViewController`, `ANNSideMenuViewController`, `ANNArrangementViewController`); `ANNSideMenuPresenter` is the one other file that does. One maker per layout type; registers each node. Called by: Every command that shows a layout.
119
- - `ANNOptionsCache`: The one invalidation point of resolved options. Called by: The setter of `ANNNodeHelper.options` and `ANNDefaultOptions` only.
120
- - `ANNNodeHelper resolveOptionsWithDefault`: What a node shows with: defaults, parents' own options, the node's `resolveOptions` (own, then the current child down). Cached, read-only. Called by: Presenters, coordinators.
123
+ - `ANNOptionsCache`: The one invalidation point of resolved options. Called by: The setters of `ANNNodeHelper.options` and of its user layer (`setUserChoice:forKey:`), and `ANNDefaultOptions`, only.
124
+ - `ANNNodeHelper resolveOptionsWithDefault`: What a node shows with: defaults, parents' own options (each with its user layer), the node's `resolveOptions` (own, its user layer, then the current child down). Cached, read-only. Called by: Presenters, coordinators.
121
125
  - `ANNAppearanceDispatcher`: The only sender of `ComponentWillAppear`, `ComponentDidAppear`, `ComponentDidDisappear`; the end-of-transition order; the events of a node that goes. Called by: Screens, queues, coordinators.
122
- - `ANNNavigationState`: The visible screen and the nearest tabs it is in (the last tabs met walking down); coalesced updates, holds, the state before an answer. Called by: The dispatcher, `ANNCommandAnswer`, `ANNRootCoordinator`.
126
+ - `ANNNavigationState`: The visible screen and the nearest tabs it is in (the last tabs met walking down); coalesced updates, holds, the state now. Called by: The dispatcher, `ANNCommandAnswer`, `ANNRootCoordinator`, `ANNModalCoordinator`, `ANNBottomTabsController`.
123
127
  - `ANNScreenReadiness`: The signals of a screen's safe area view (has one, laid out, content rendered), sticky, by screen id. Called by: `ANNSafeAreaViewComponentView`, `ANNReactView`.
124
128
  - `ANNRenderWait`: The one wait for the render of what a command shows: content, bar components, limits, the unknown-content rule. Called by: Root, stack queue, modals, overlays, `showInColumn`.
125
129
  - `ANNParking`: Where a view waits for its content unseen: the window, a stack's view, a preview's host view. Called by: Root, stack, modals, overlays, `ANNPreviewPush`.
130
+ - `ANNScreenPlace`: The safe area a parked screen lays out with, measured where it will show on top of a stack. Called by: `ANNStackController` (`stackPark:`), `ANNSplitViewCommands` (`showInColumn`), through `placeWhileParked` of the screen.
126
131
  - `ANNPendingLayers`: Layers that wait to show (a pending root, a modal, an overlay); a node inside counts as off screen. Called by: The layer's owner; nodes ask.
127
132
  - `ANNShownScreens`: "What a node shows changed": tells the pending layer, the modal hold and the system UI, in that order. Called by: Every container that changes what it shows.
128
133
  - `ANNModalCoordinator`, `ANNModal`: One record per modal, its states and waiting commands. Called by: Commands, root, state, repaint, reload.
@@ -27,8 +27,15 @@ NS_ASSUME_NONNULL_BEGIN
27
27
  /// Whether the JavaScript the command came from still runs: its work goes on only then.
28
28
  @property(nonatomic, readonly) BOOL isCurrent;
29
29
 
30
- /// Resolves the promise: first the state goes, then the promise, then `ANN.CommandCompleted` for a command that shows
31
- /// or dismisses.
30
+ /// The navigation state now, sent when it changed, once per answer: the end of the command's transition asks for it
31
+ /// after the `DidAppear` it sends and before the transition's own events (`ANNAppearanceDispatcher`); otherwise
32
+ /// `resolve:` asks for it before the promise. A later call sends nothing, and nothing goes for a JavaScript that went.
33
+ /// A failure of the step is logged and costs only the state.
34
+ - (void)reportStateNow;
35
+
36
+ /// Resolves the promise: first the state, when the command changes what shows (`ANNCommandContract`) and its
37
+ /// transition didn't send it already (`reportStateNow`), then the promise, then `ANN.CommandCompleted` for a command
38
+ /// that shows or dismisses.
32
39
  - (void)resolve:(nullable id)value;
33
40
 
34
41
  /// Rejects the promise with the text alone: no code of the navigation.
@@ -1,29 +1,16 @@
1
1
  #import "ANNCommandAnswer.h"
2
2
  #import "ANNAssert.h"
3
+ #import "ANNCommandContract.h"
3
4
  #import "ANNErrorHandler.h"
4
5
  #import "ANNErrorTexts.h"
5
6
  #import "ANNNavigationState.h"
6
7
  #import "ANNReloadCoordinator.h"
7
8
  #import "ApexNavigation+Internal.h"
8
9
 
9
- // The commands that send `ANN.CommandCompleted` once they resolved: every one that shows or dismisses, not
10
- // `setColumnShown`, `preparePush` or `getLaunchLink`.
11
- static NSSet<NSString *> *ANNCompletedCommandNames(void) {
12
- static NSSet<NSString *> *names;
13
- static dispatch_once_t once;
14
- dispatch_once(&once, ^{
15
- names = [NSSet setWithArray:@[
16
- @"setRoot", @"push", @"pop", @"popTo", @"popToRoot", @"setStackRoot", @"showInColumn",
17
- @"showModal", @"dismissModal", @"dismissAllModals", @"showOverlay", @"dismissOverlay",
18
- @"dismissAllOverlays"
19
- ]];
20
- });
21
- return names;
22
- }
23
-
24
10
  @implementation ANNCommandAnswer {
25
11
  RCTPromiseResolveBlock _resolve;
26
12
  RCTPromiseRejectBlock _reject;
13
+ BOOL _stateReported;
27
14
  }
28
15
 
29
16
  - (instancetype)initWithCommandName:(NSString *)commandName
@@ -65,6 +52,27 @@ static NSSet<NSString *> *ANNCompletedCommandNames(void) {
65
52
  return YES;
66
53
  }
67
54
 
55
+ - (void)reportStateNow {
56
+ ANNAssertMainQueue();
57
+ if (_stateReported) {
58
+ return;
59
+ }
60
+ _stateReported = YES;
61
+ // A step made for the JavaScript that went sends nothing, as its answer does.
62
+ if (!self.isCurrent) {
63
+ return;
64
+ }
65
+ // The command did its work: a failure of the state step costs only the state, never the events and the answer.
66
+ @try {
67
+ [ANNNavigationState updateNowFor:_commandName];
68
+ } @catch (NSException *exception) {
69
+ NSString *reason = exception.reason.length > 0 ? exception.reason : exception.name;
70
+ [ANNErrorHandler logFailureOf:_commandName
71
+ text:[ANNErrorTexts couldNot:@"report the navigation state"
72
+ reason:reason]];
73
+ }
74
+ }
75
+
68
76
  - (void)resolve:(id)value {
69
77
  if (![self takeAnswer]) {
70
78
  return;
@@ -72,23 +80,13 @@ static NSSet<NSString *> *ANNCompletedCommandNames(void) {
72
80
  RCTPromiseResolveBlock resolve = _resolve;
73
81
  _resolve = nil;
74
82
  _reject = nil;
75
- // A command that may change what shows (the 13 and `setColumnShown`): the state goes before the promise, so that
76
- // `getState()` after `await` has the result.
77
- BOOL changesWhatShows = [ANNCompletedCommandNames() containsObject:_commandName] ||
78
- [_commandName isEqualToString:@"setColumnShown"];
79
- if (changesWhatShows) {
80
- // The command did its work: a failure of the state step costs only the state, never the answer.
81
- @try {
82
- [ANNNavigationState reportBeforeAnswer];
83
- } @catch (NSException *exception) {
84
- NSString *reason = exception.reason.length > 0 ? exception.reason : exception.name;
85
- [ANNErrorHandler logFailureOf:_commandName
86
- text:[ANNErrorTexts couldNot:@"report the navigation state"
87
- reason:reason]];
88
- }
83
+ // A command that may change what shows (`ANNCommandContract`): the state goes before the promise, so that
84
+ // `getState()` after `await` has the result. Sent at the end of its transition already, it goes no more.
85
+ if (ANNCommandReportsState(_commandName)) {
86
+ [self reportStateNow];
89
87
  }
90
88
  [ANNErrorHandler resolve:resolve of:_commandName value:value];
91
- if (_commandId && [ANNCompletedCommandNames() containsObject:_commandName]) {
89
+ if (_commandId && ANNCommandSendsCompleted(_commandName)) {
92
90
  double completionTime = floor(NSDate.date.timeIntervalSince1970 * 1000);
93
91
  [ApexNavigation.eventEmitter sendCommandCompleted:_commandName
94
92
  commandId:_commandId
@@ -0,0 +1,15 @@
1
+ // Made by scripts/contract.mjs from src/interfaces/contract/commands.json: change that file and run the script again.
2
+
3
+ #import <Foundation/Foundation.h>
4
+
5
+ NS_ASSUME_NONNULL_BEGIN
6
+
7
+ /// Whether the navigation state goes before the answer of the command `name`: every command that may change what
8
+ /// shows. Any thread.
9
+ FOUNDATION_EXPORT BOOL ANNCommandReportsState(NSString *name);
10
+
11
+ /// Whether `ANN.CommandCompleted` follows the answer of the command `name`: every command that shows or dismisses.
12
+ /// Any thread.
13
+ FOUNDATION_EXPORT BOOL ANNCommandSendsCompleted(NSString *name);
14
+
15
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,78 @@
1
+ // Made by scripts/contract.mjs from src/interfaces/contract/commands.json: change that file and run the script again.
2
+
3
+ #import "ANNCommandContract.h"
4
+
5
+ // What each command resolves with:
6
+ // - `setRoot`: the id of the root, once the root and the layout's modals and overlays show.
7
+ // - `push`: the id of the screen that shows; of a pair, the pair's id.
8
+ // - `preparePush`: the frame `{ width, height, padding }` of the prepared screen, or null; always
9
+ // null on iOS.
10
+ // - `pop`: the id of the screen that went.
11
+ // - `popTo`: the id of the screen it went back to.
12
+ // - `popToRoot`: the id of the first screen of the stack.
13
+ // - `setStackRoot`: the id of the new top screen.
14
+ // - `showModal`: the id of the modal's root.
15
+ // - `dismissModal`: the id of the modal's root.
16
+ // - `dismissAllModals`: an empty string.
17
+ // - `showOverlay`: the id of the overlay's root.
18
+ // - `dismissOverlay`: the id of the overlay's root.
19
+ // - `dismissAllOverlays`: an empty string.
20
+ // - `showInColumn`: the id of the screen that shows.
21
+ // - `setColumnShown`: nothing once the columns stopped moving (`undefined` on iOS, `null` on
22
+ // Android).
23
+ // - `getLaunchLink`: the link that launched the app, or null.
24
+
25
+ static NSSet<NSString *> *ANNStateCommandNames(void) {
26
+ static NSSet<NSString *> *names;
27
+ static dispatch_once_t once;
28
+ dispatch_once(&once, ^{
29
+ names = [NSSet setWithArray:@[
30
+ @"setRoot",
31
+ @"push",
32
+ @"pop",
33
+ @"popTo",
34
+ @"popToRoot",
35
+ @"setStackRoot",
36
+ @"showModal",
37
+ @"dismissModal",
38
+ @"dismissAllModals",
39
+ @"showOverlay",
40
+ @"dismissOverlay",
41
+ @"dismissAllOverlays",
42
+ @"showInColumn",
43
+ @"setColumnShown",
44
+ ]];
45
+ });
46
+ return names;
47
+ }
48
+
49
+ static NSSet<NSString *> *ANNCompletedCommandNames(void) {
50
+ static NSSet<NSString *> *names;
51
+ static dispatch_once_t once;
52
+ dispatch_once(&once, ^{
53
+ names = [NSSet setWithArray:@[
54
+ @"setRoot",
55
+ @"push",
56
+ @"pop",
57
+ @"popTo",
58
+ @"popToRoot",
59
+ @"setStackRoot",
60
+ @"showModal",
61
+ @"dismissModal",
62
+ @"dismissAllModals",
63
+ @"showOverlay",
64
+ @"dismissOverlay",
65
+ @"dismissAllOverlays",
66
+ @"showInColumn",
67
+ ]];
68
+ });
69
+ return names;
70
+ }
71
+
72
+ BOOL ANNCommandReportsState(NSString *name) {
73
+ return [ANNStateCommandNames() containsObject:name];
74
+ }
75
+
76
+ BOOL ANNCommandSendsCompleted(NSString *name) {
77
+ return [ANNCompletedCommandNames() containsObject:name];
78
+ }
@@ -58,11 +58,12 @@ NS_ASSUME_NONNULL_BEGIN
58
58
 
59
59
  /// A transition ended, in this order: first a screen a transition began inside a node of `shown` that left the window
60
60
  /// and isn't one of that node's shown screens gets its `DidDisappear`; then `DidDisappear` of every screen of `hidden`,
61
- /// `DidAppear` of what `shown` shows, the navigation state now when it changed, `events` (the transition's own events,
61
+ /// `DidAppear` of what `shown` shows, the navigation state now when it changed (the answer's one state step,
62
+ /// `-[ANNCommandAnswer reportStateNow]`; nothing for a JavaScript that went), `events` (the transition's own events,
62
63
  /// `ANN.ScreenPopped` and `ANN.ModalDismissed`, and what must follow them, like stopping the content that went), then
63
- /// `answer` resolves with `value` (its own state step finds nothing new, then the promise, then
64
- /// `ANN.CommandCompleted`). A user's action passes no answer. The callbacks of UIKit that come later find the phases
65
- /// set and send nothing.
64
+ /// `answer` resolves with `value` (the promise, then `ANN.CommandCompleted`; no second state). A user's action passes
65
+ /// no answer, and its state goes all the same. The callbacks of UIKit that come later find the phases set and send
66
+ /// nothing.
66
67
  + (void)endTransitionHiding:(NSArray<UIViewController *> *)hidden
67
68
  showing:(NSArray<UIViewController *> *)shown
68
69
  events:(nullable dispatch_block_t)events
@@ -305,7 +305,18 @@ static BOOL ANNIsInsideNode(UIViewController *screen, UIViewController *node) {
305
305
  for (UIViewController *node in shown) {
306
306
  [self nodeDidAppear:node];
307
307
  }
308
- [ANNNavigationState updateNow];
308
+ // The state before the transition's events, once per answer: the answer's own step before its promise finds it
309
+ // sent. A user's transition has no answer; one whose answer went before its end (an interrupt) sends the state of
310
+ // its end itself, as Android does.
311
+ if (!answer) {
312
+ [ANNNavigationState updateNow];
313
+ } else if (answer.answered) {
314
+ if (answer.isCurrent) {
315
+ [ANNNavigationState updateNowFor:answer.commandName];
316
+ }
317
+ } else {
318
+ [answer reportStateNow];
319
+ }
309
320
  if (events) {
310
321
  events();
311
322
  }
@@ -14,7 +14,7 @@ NS_ASSUME_NONNULL_BEGIN
14
14
  /// Resolves a command's promise with `value`. Without a resolve block one line goes to the log instead.
15
15
  + (void)resolve:(nullable RCTPromiseResolveBlock)resolve of:(NSString *)method value:(nullable id)value;
16
16
 
17
- /// The log line of a command without a promise: `[ApexNavigation] <method>: <text>`.
17
+ /// The log line of a command without a promise: `[ApexNavigation] <method>: <text>`, a warning (`RCTLogWarn`).
18
18
  + (void)logFailureOf:(NSString *)method text:(NSString *)text;
19
19
 
20
20
  @end
@@ -23,8 +23,10 @@
23
23
  resolve(value);
24
24
  }
25
25
 
26
+ // A warning, not an error: a failed command without a promise (a `mergeOptions` the screen can't take) is the app's
27
+ // to notice, not a red box over it; Android logs it the same way, without a dialog.
26
28
  + (void)logFailureOf:(NSString *)method text:(NSString *)text {
27
- RCTLogError(@"[ApexNavigation] %@: %@", method, text);
29
+ RCTLogWarn(@"[ApexNavigation] %@: %@", method, text);
28
30
  }
29
31
 
30
32
  @end
@@ -28,7 +28,7 @@ NS_ASSUME_NONNULL_BEGIN
28
28
 
29
29
  #pragma mark - The contract
30
30
 
31
- /// `ANN.AppLaunched`, without a payload. While JavaScript has no listener of it, it is kept and goes once JavaScript
31
+ /// `ANN.AppLaunched`, with an empty payload. While JavaScript has no listener of it, it is kept and goes once JavaScript
32
32
  /// adds the first one. Main queue.
33
33
  - (void)sendAppLaunched;
34
34
 
@@ -60,7 +60,7 @@ static NSUInteger ANNAppLaunchedBundle;
60
60
  return;
61
61
  }
62
62
  ANNAppLaunchedPending = NO;
63
- [eventEmitter sendIfListened:ANNEventContract.AppLaunched.event body:nil];
63
+ [eventEmitter sendIfListened:ANNEventContract.AppLaunched.event body:@{}];
64
64
  }
65
65
 
66
66
  + (void)eventEmitterListensToAppLaunched:(ANNTurboEventEmitter *)eventEmitter {
@@ -69,7 +69,7 @@ static NSUInteger ANNAppLaunchedBundle;
69
69
  return;
70
70
  }
71
71
  ANNAppLaunchedPending = NO;
72
- [eventEmitter sendIfListened:ANNEventContract.AppLaunched.event body:nil];
72
+ [eventEmitter sendIfListened:ANNEventContract.AppLaunched.event body:@{}];
73
73
  }
74
74
 
75
75
  // Only an `AppLaunched` of the module's own JavaScript or an older one: the module is made while its bundle runs,
@@ -0,0 +1,34 @@
1
+ #import <Foundation/Foundation.h>
2
+
3
+ @class RCTCallInvoker;
4
+
5
+ NS_ASSUME_NONNULL_BEGIN
6
+
7
+ /// The way of the calls JavaScript makes to `ANNTurboModule`, for a step that must come after them. A call without an
8
+ /// answer (`mergeOptions`) leaves the JavaScript thread as it is made, goes to the module's queue (React Native's
9
+ /// `ModuleNativeMethodCallInvoker`, a `dispatch_async` to it) and from there to the main queue (`ANNCommandRunner`):
10
+ /// a step that takes the same three hops after them comes after them on the main queue, as each of the three runs its
11
+ /// work in order. The module tells its call invoker and its queue when React Native made it, and that it went when
12
+ /// React Native invalidates it (a reload). Any thread, except `afterCallsMadeSoFar:`.
13
+ @interface ANNJavaScriptCalls : NSObject
14
+
15
+ - (instancetype)init NS_UNAVAILABLE;
16
+ + (instancetype)new NS_UNAVAILABLE;
17
+
18
+ /// The module of the JavaScript that runs now, its call invoker (the JavaScript thread) and its queue.
19
+ + (void)setCallInvoker:(nullable RCTCallInvoker *)callInvoker
20
+ methodQueue:(nullable dispatch_queue_t)methodQueue
21
+ ofModule:(id)module;
22
+
23
+ /// The module went: with no newer module told since, no JavaScript runs to wait for.
24
+ + (void)moduleWent:(id)module;
25
+
26
+ /// `block` runs on the main queue once every call JavaScript made to the module before the next task of its thread is
27
+ /// applied on the main queue: a task on the JavaScript thread, from there one on the module's queue, from there one on
28
+ /// the main queue. With no JavaScript to ask (none yet, a reload), at once. Main queue. A JavaScript thread that never
29
+ /// runs the task again (its runtime went) never runs `block`: a caller that can't wait for long has a limit of its own.
30
+ + (void)afterCallsMadeSoFar:(dispatch_block_t)block;
31
+
32
+ @end
33
+
34
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,61 @@
1
+ #import "ANNJavaScriptCalls.h"
2
+ #import "ANNAssert.h"
3
+ #import <React/RCTCallInvoker.h>
4
+ #import <React/RCTUtils.h>
5
+ #import <ReactCommon/CallInvoker.h>
6
+
7
+ // The module told last, weakly: a module that went takes its invoker and its queue with it.
8
+ static __weak id ANNCallsModule;
9
+ static RCTCallInvoker *ANNCallsInvoker;
10
+ static dispatch_queue_t ANNCallsQueue;
11
+
12
+ @implementation ANNJavaScriptCalls
13
+
14
+ + (void)setCallInvoker:(RCTCallInvoker *)callInvoker
15
+ methodQueue:(dispatch_queue_t)methodQueue
16
+ ofModule:(id)module {
17
+ @synchronized(self) {
18
+ ANNCallsModule = module;
19
+ ANNCallsInvoker = callInvoker;
20
+ ANNCallsQueue = methodQueue;
21
+ }
22
+ }
23
+
24
+ + (void)moduleWent:(id)module {
25
+ @synchronized(self) {
26
+ if (ANNCallsModule != module) {
27
+ return;
28
+ }
29
+ ANNCallsModule = nil;
30
+ ANNCallsInvoker = nil;
31
+ ANNCallsQueue = nil;
32
+ }
33
+ }
34
+
35
+ + (void)afterCallsMadeSoFar:(dispatch_block_t)block {
36
+ ANNAssertMainQueue();
37
+ RCTCallInvoker *callInvoker = nil;
38
+ dispatch_queue_t queue = nil;
39
+ @synchronized(self) {
40
+ if (ANNCallsModule) {
41
+ callInvoker = ANNCallsInvoker;
42
+ queue = ANNCallsQueue;
43
+ }
44
+ }
45
+ std::shared_ptr<facebook::react::CallInvoker> invoker =
46
+ callInvoker ? callInvoker.callInvoker : nullptr;
47
+ if (!invoker || !queue) {
48
+ block();
49
+ return;
50
+ }
51
+ dispatch_block_t done = [block copy];
52
+ // The JavaScript thread runs the task after the one it runs now, which made the calls of this moment (a commit of
53
+ // React and its layout effects); the module's queue then has those calls before this hop, and the main queue too.
54
+ invoker->invokeAsync([queue, done](facebook::jsi::Runtime &) {
55
+ dispatch_async(queue, ^{
56
+ RCTExecuteOnMainQueue(done);
57
+ });
58
+ });
59
+ }
60
+
61
+ @end
@@ -23,12 +23,14 @@ NS_ASSUME_NONNULL_BEGIN
23
23
  + (void)holdUpdates;
24
24
  + (void)releaseUpdates;
25
25
 
26
- /// The state now, sent when it changed: at the end of a transition, at once after a tab chosen from code. A walk that
27
- /// throws logs one line and sends nothing, so its caller always goes on.
28
- + (void)updateNow;
26
+ /// The state now, sent when it changed. A command's answer asks for it once (`-[ANNCommandAnswer reportStateNow]`, at
27
+ /// the end of its transition or before its promise); the end of a user's transition and a tab chosen from code ask
28
+ /// `updateNow`. A walk that throws logs one line, `[ApexNavigation] <commandName>: could not report the navigation
29
+ /// state: <reason>` (as on Android; `transition` without a command), and sends nothing, so its caller always goes on.
30
+ + (void)updateNowFor:(nullable NSString *)commandName;
29
31
 
30
- /// A command is about to answer: when what shows changed, the new state goes now, before the promise.
31
- + (void)reportBeforeAnswer;
32
+ /// `updateNowFor:` without a command.
33
+ + (void)updateNow;
32
34
 
33
35
  /// Forgets the last state sent: the next one goes even if it is the same (a new bundle, `AppLaunched`).
34
36
  + (void)reset;
@@ -2,12 +2,12 @@
2
2
  #import "ANNAssert.h"
3
3
  #import "ANNBottomTabsController.h"
4
4
  #import "ANNComponentViewController.h"
5
+ #import "ANNErrorHandler.h"
5
6
  #import "ANNErrorTexts.h"
6
7
  #import "ANNModalCoordinator.h"
7
8
  #import "ANNSlotContainers.h"
8
9
  #import "ApexNavigation+Internal.h"
9
10
  #import "UIViewController+LayoutProtocol.h"
10
- #import <React/RCTLog.h>
11
11
 
12
12
  // Main queue.
13
13
  static BOOL ANNUpdateScheduled;
@@ -58,6 +58,10 @@ static BOOL ANNSameValue(id a, id b) {
58
58
  }
59
59
 
60
60
  + (void)updateNow {
61
+ [self updateNowFor:nil];
62
+ }
63
+
64
+ + (void)updateNowFor:(NSString *)commandName {
61
65
  ANNAssertMainQueue();
62
66
  NSString *screenId = nil;
63
67
  NSString *name = nil;
@@ -74,8 +78,9 @@ static BOOL ANNSameValue(id a, id b) {
74
78
  }
75
79
  } @catch (NSException *exception) {
76
80
  NSString *reason = exception.reason.length > 0 ? exception.reason : exception.name;
77
- RCTLogWarn(@"[ApexNavigation] %@",
78
- [ANNErrorTexts couldNot:@"report the navigation state" reason:reason]);
81
+ [ANNErrorHandler logFailureOf:commandName ?: @"transition"
82
+ text:[ANNErrorTexts couldNot:@"report the navigation state"
83
+ reason:reason]];
79
84
  return;
80
85
  }
81
86
  if (ANNHasLastState && ANNSameValue(screenId, ANNLastScreenId) &&
@@ -89,10 +94,6 @@ static BOOL ANNSameValue(id a, id b) {
89
94
  [ApexNavigation.eventEmitter sendNavigationStateChanged:screenId name:name tab:tab];
90
95
  }
91
96
 
92
- + (void)reportBeforeAnswer {
93
- [self updateNow];
94
- }
95
-
96
97
  + (void)reset {
97
98
  ANNAssertMainQueue();
98
99
  ANNHasLastState = NO;