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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/android/CLAUDE.md +17 -7
  3. package/android/CORE.md +8 -3
  4. package/android/RECIPES.md +9 -3
  5. package/android/src/main/java/dev/apexnative/navigation/NavigationApplication.kt +34 -13
  6. package/android/src/main/java/dev/apexnative/navigation/layout/LayoutChecks.kt +5 -1
  7. package/android/src/main/java/dev/apexnative/navigation/layout/LayoutNode.kt +6 -2
  8. package/android/src/main/java/dev/apexnative/navigation/nativescreen/NativeScreenContext.kt +2 -2
  9. package/android/src/main/java/dev/apexnative/navigation/options/ButtonMenuOptions.kt +2 -2
  10. package/android/src/main/java/dev/apexnative/navigation/options/ButtonOptions.kt +2 -2
  11. package/android/src/main/java/dev/apexnative/navigation/options/OptionsCache.kt +8 -4
  12. package/android/src/main/java/dev/apexnative/navigation/options/UserChoices.kt +159 -0
  13. package/android/src/main/java/dev/apexnative/navigation/react/CommandAnswer.kt +2 -24
  14. package/android/src/main/java/dev/apexnative/navigation/react/CommandContract.kt +59 -0
  15. package/android/src/main/java/dev/apexnative/navigation/react/Hinge.kt +27 -19
  16. package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +5 -4
  17. package/android/src/main/java/dev/apexnative/navigation/react/PendingCalls.kt +48 -0
  18. package/android/src/main/java/dev/apexnative/navigation/react/events/AppearanceDispatcher.kt +17 -7
  19. package/android/src/main/java/dev/apexnative/navigation/slots/SlotContainers.kt +10 -7
  20. package/android/src/main/java/dev/apexnative/navigation/slots/SlotView.kt +4 -3
  21. package/android/src/main/java/dev/apexnative/navigation/slots/SlotViews.kt +40 -0
  22. package/android/src/main/java/dev/apexnative/navigation/utils/ImageKey.kt +66 -0
  23. package/android/src/main/java/dev/apexnative/navigation/utils/ImageLoader.kt +7 -89
  24. package/android/src/main/java/dev/apexnative/navigation/utils/ImageRequests.kt +76 -0
  25. package/android/src/main/java/dev/apexnative/navigation/utils/OptionImages.kt +262 -0
  26. package/android/src/main/java/dev/apexnative/navigation/utils/ViewController.kt +3 -1
  27. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/AppliedTabCalls.kt +49 -0
  28. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsController.kt +16 -6
  29. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsPresenter.kt +25 -24
  30. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ContentReadiness.kt +1 -1
  31. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/RenderWait.kt +27 -7
  32. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalCoordinator.kt +5 -5
  33. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt +4 -3
  34. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/RootCoordinator.kt +6 -3
  35. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/overlay/OverlayCoordinator.kt +8 -8
  36. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/Capabilities.kt +11 -4
  37. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuController.kt +1 -1
  38. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitPanes.kt +23 -0
  39. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitViewController.kt +58 -15
  40. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackCommands.kt +28 -18
  41. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +16 -7
  42. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackDisplay.kt +2 -1
  43. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackQueue.kt +85 -29
  44. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +2 -2
  45. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabPresenter.kt +7 -5
  46. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/StackSearchPresenter.kt +3 -3
  47. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/MenuChecks.kt +13 -56
  48. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +10 -2
  49. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/viewcontroller/ViewController.kt +56 -21
  50. package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/BottomTabs.kt +4 -2
  51. package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/Fab.kt +14 -10
  52. package/cli/doctor/checks/app.js +61 -13
  53. package/cli/migrate/native.js +18 -0
  54. package/cli/setup/source.js +2 -1
  55. package/cli/setup/steps/mainApplication.js +134 -19
  56. package/docs-md/architecture/transitions.md +6 -7
  57. package/docs-md/help/glossary.md +1 -1
  58. package/docs-md/help/troubleshooting.md +20 -3
  59. package/docs-md/index.json +4 -0
  60. package/docs-md/migration/from-react-native-navigation.md +8 -2
  61. package/docs-md/screen-ui/back-and-gestures.md +2 -2
  62. package/docs-md/start/installation.md +3 -1
  63. package/ios/CLAUDE.md +17 -5
  64. package/ios/CORE.md +13 -8
  65. package/ios/Contract/ANNCommandAnswer.mm +5 -20
  66. package/ios/Contract/ANNCommandContract.h +15 -0
  67. package/ios/Contract/ANNCommandContract.mm +78 -0
  68. package/ios/Core/ANNAppearanceDispatcher.mm +1 -1
  69. package/ios/Core/ANNErrorHandler.h +1 -1
  70. package/ios/Core/ANNErrorHandler.mm +3 -1
  71. package/ios/Core/ANNEventEmitter.h +1 -1
  72. package/ios/Core/ANNEventEmitter.mm +2 -2
  73. package/ios/Core/ANNJavaScriptCalls.h +34 -0
  74. package/ios/Core/ANNJavaScriptCalls.mm +61 -0
  75. package/ios/Core/ANNNavigationState.h +7 -3
  76. package/ios/Core/ANNNavigationState.mm +10 -5
  77. package/ios/Core/ANNNodeHelper.h +32 -12
  78. package/ios/Core/ANNNodeHelper.mm +100 -3
  79. package/ios/Core/ANNRenderWait.h +15 -1
  80. package/ios/Core/ANNRenderWait.mm +119 -17
  81. package/ios/Core/UIViewController+LayoutProtocol.h +9 -2
  82. package/ios/Core/UIViewController+LayoutProtocol.mm +8 -0
  83. package/ios/Layouts/Modal/ANNModalCoordinator.h +1 -1
  84. package/ios/Layouts/Modal/ANNModalCoordinator.mm +55 -14
  85. package/ios/Layouts/Screen/ANNComponentViewController.h +8 -0
  86. package/ios/Layouts/Screen/ANNComponentViewController.mm +32 -5
  87. package/ios/Layouts/Screen/ANNScreenPlace.h +41 -0
  88. package/ios/Layouts/Screen/ANNScreenPlace.mm +175 -0
  89. package/ios/Layouts/SplitView/ANNSplitViewCommands.h +3 -2
  90. package/ios/Layouts/SplitView/ANNSplitViewCommands.mm +105 -39
  91. package/ios/Layouts/SplitView/ANNSplitViewController.mm +4 -2
  92. package/ios/Layouts/Stack/ANNStackCommands.mm +1 -0
  93. package/ios/Layouts/Stack/ANNStackController.mm +26 -3
  94. package/ios/Layouts/Stack/ANNStackQueue.h +3 -1
  95. package/ios/Layouts/Stack/ANNStackQueue.mm +56 -43
  96. package/ios/Layouts/Stack/UINavigationController+ANNCommands.h +4 -2
  97. package/ios/Layouts/Stack/UINavigationController+ANNCommands.mm +2 -3
  98. package/ios/Layouts/Tabs/ANNBottomTabItemBuilder.h +4 -0
  99. package/ios/Layouts/Tabs/ANNBottomTabItemBuilder.mm +11 -0
  100. package/ios/Layouts/Tabs/ANNBottomTabsController.mm +13 -5
  101. package/ios/Layouts/Tabs/ANNBottomTabsPresenter.h +2 -2
  102. package/ios/Layouts/Tabs/ANNBottomTabsPresenter.mm +109 -7
  103. package/ios/Options/ANNOptionsCache.h +7 -6
  104. package/ios/Options/ANNSearchBarOptions.h +4 -3
  105. package/ios/Options/ANNUserChoice.h +57 -0
  106. package/ios/Options/ANNUserChoice.mm +44 -0
  107. package/ios/Options/Params/ANNImageSource.h +61 -0
  108. package/ios/Options/Params/ANNImageSource.mm +101 -0
  109. package/ios/Options/Params/ANNOptionImages.h +37 -0
  110. package/ios/Options/Params/ANNOptionImages.mm +284 -0
  111. package/ios/Options/Params/Image.h +11 -2
  112. package/ios/Options/Params/Image.mm +15 -2
  113. package/ios/Options/Params/ImageParser.h +5 -5
  114. package/ios/Options/Params/ImageParser.mm +28 -46
  115. package/ios/Public/ApexNavigation/ApexNavigation.h +1 -1
  116. package/ios/RECIPES.md +11 -5
  117. package/ios/React/Slots/ANNSlotComponentView.mm +24 -17
  118. package/ios/React/Slots/ANNSlotContainers.h +14 -6
  119. package/ios/React/Slots/ANNSlotContainers.mm +75 -2
  120. package/ios/TopBar/ANNBarButtons.h +9 -5
  121. package/ios/TopBar/ANNBarButtons.mm +211 -63
  122. package/ios/TopBar/ANNIconButtonImage.h +1 -0
  123. package/ios/TopBar/ANNIconButtonImage.mm +34 -1
  124. package/ios/TopBar/ANNLargeTitlePresenter.h +13 -0
  125. package/ios/TopBar/ANNLargeTitlePresenter.mm +26 -3
  126. package/ios/TopBar/ANNSearchPresenter.mm +170 -19
  127. package/ios/TopBar/ANNTopBarPresenter.h +5 -0
  128. package/ios/TopBar/ANNTopBarPresenter.mm +60 -11
  129. package/ios/TurboModules/ANNTurboModule.mm +39 -21
  130. package/lib/module/ARCHITECTURE.md +18 -18
  131. package/lib/module/adapters/NativeANNTurboModule.js.map +1 -1
  132. package/lib/module/api/navigation.js.map +1 -1
  133. package/lib/module/engine/commands.js +9 -7
  134. package/lib/module/engine/commands.js.map +1 -1
  135. package/lib/module/engine/layout.js +13 -9
  136. package/lib/module/engine/layout.js.map +1 -1
  137. package/lib/module/engine/options.js +44 -11
  138. package/lib/module/engine/options.js.map +1 -1
  139. package/lib/module/engine/places.js +44 -7
  140. package/lib/module/engine/places.js.map +1 -1
  141. package/lib/module/engine/props.js +30 -1
  142. package/lib/module/engine/props.js.map +1 -1
  143. package/lib/module/engine/rememberedOptions.js +1 -1
  144. package/lib/module/engine/rememberedOptions.js.map +1 -1
  145. package/lib/module/engine/screens.js +22 -12
  146. package/lib/module/engine/screens.js.map +1 -1
  147. package/lib/module/interfaces/contract/commands.json +101 -0
  148. package/lib/typescript/adapters/NativeANNTurboModule.d.ts +5 -2
  149. package/lib/typescript/adapters/NativeANNTurboModule.d.ts.map +1 -1
  150. package/lib/typescript/api/events/navigationEvents.d.ts +1 -1
  151. package/lib/typescript/api/events/navigationEvents.d.ts.map +1 -1
  152. package/lib/typescript/api/hooks/useNavigation.d.ts +1 -1
  153. package/lib/typescript/api/hooks/useNavigation.d.ts.map +1 -1
  154. package/lib/typescript/api/navigation.d.ts +4 -1
  155. package/lib/typescript/api/navigation.d.ts.map +1 -1
  156. package/lib/typescript/engine/commands.d.ts.map +1 -1
  157. package/lib/typescript/engine/options.d.ts +5 -2
  158. package/lib/typescript/engine/options.d.ts.map +1 -1
  159. package/lib/typescript/engine/places.d.ts +5 -2
  160. package/lib/typescript/engine/places.d.ts.map +1 -1
  161. package/lib/typescript/engine/props.d.ts +8 -1
  162. package/lib/typescript/engine/props.d.ts.map +1 -1
  163. package/lib/typescript/engine/rememberedOptions.d.ts.map +1 -1
  164. package/lib/typescript/engine/screens.d.ts.map +1 -1
  165. package/lib/typescript/interfaces/ComponentEvents.d.ts +2 -2
  166. package/lib/typescript/interfaces/ComponentEvents.d.ts.map +1 -1
  167. package/lib/typescript/interfaces/Events.d.ts +19 -10
  168. package/lib/typescript/interfaces/Events.d.ts.map +1 -1
  169. package/lib/typescript/interfaces/options/animations.d.ts +5 -2
  170. package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
  171. package/lib/typescript/interfaces/options/buttons.d.ts +13 -5
  172. package/lib/typescript/interfaces/options/buttons.d.ts.map +1 -1
  173. package/lib/typescript/interfaces/options/topBar.d.ts +22 -9
  174. package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
  175. package/package.json +1 -1
  176. package/src/ARCHITECTURE.md +18 -18
  177. package/src/adapters/NativeANNTurboModule.ts +5 -2
  178. package/src/api/events/navigationEvents.ts +1 -1
  179. package/src/api/hooks/useNavigation.ts +1 -1
  180. package/src/api/navigation.ts +4 -1
  181. package/src/engine/commands.ts +19 -7
  182. package/src/engine/layout.ts +16 -9
  183. package/src/engine/options.ts +39 -11
  184. package/src/engine/places.ts +56 -7
  185. package/src/engine/props.ts +30 -1
  186. package/src/engine/rememberedOptions.ts +4 -1
  187. package/src/engine/screens.tsx +23 -13
  188. package/src/interfaces/ComponentEvents.ts +2 -2
  189. package/src/interfaces/Events.ts +19 -10
  190. package/src/interfaces/contract/commands.json +101 -0
  191. package/src/interfaces/options/animations.ts +5 -2
  192. package/src/interfaces/options/buttons.ts +13 -5
  193. package/src/interfaces/options/topBar.ts +22 -9
@@ -5,24 +5,51 @@ const { addImport, codeOf, removeUnusedImports } = require('../source');
5
5
 
6
6
  /** @typedef {import('./types').StepResult} StepResult */
7
7
 
8
- const HOW_TO = `Extend NavigationApplication. It loads React Native itself, so loadReactNative(this) goes:
9
- calling it again crashes the app on start.
8
+ const HOW_TO = `Extend NavigationApplication and keep the template's loadReactNative(this) in onCreate: it loads
9
+ React Native as the app's Gradle files say, and the navigation makes its React host after it.
10
10
 
11
+ import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
11
12
  import dev.apexnative.navigation.NavigationApplication
12
13
 
13
14
  class MainApplication : NavigationApplication() {
14
15
  override val reactHost: ReactHost by lazy { ... } // as it is
16
+
17
+ override fun onCreate() {
18
+ super.onCreate()
19
+ loadReactNative(this)
20
+ }
15
21
  }`;
16
22
 
23
+ const LOAD_IMPORT = 'import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative';
24
+
17
25
  // Imports of the template that the change leaves unused.
18
- const TEMPLATE_IMPORTS = [
19
- 'android.app.Application',
20
- 'com.facebook.react.ReactApplication',
21
- 'com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative',
26
+ const TEMPLATE_IMPORTS = ['android.app.Application', 'com.facebook.react.ReactApplication'];
27
+
28
+ // A load of React Native of the app's own, next to loadReactNative(this) a second one that crashes the app at start:
29
+ // the New Architecture's entry point (react-native-navigation's apps call it in onCreate) and SoLoader.
30
+ // A bare `load()` is the entry point's only where the file imports it. A form these don't take out (`load` with
31
+ // arguments, an `if` without braces) is left to the app's author.
32
+ const OWN_LOAD = [
33
+ /^[ \t]*if\s*\(\s*BuildConfig\.IS_NEW_ARCHITECTURE_ENABLED\s*\)\s*\{(?:\s*\/\/[^\n]*)*\s*DefaultNewArchitectureEntryPoint\.load\(\)\s*\}[ \t]*\r?\n/gm,
34
+ /^[ \t]*DefaultNewArchitectureEntryPoint\.load\(\)[ \t]*\r?\n/gm,
35
+ /^[ \t]*SoLoader\.init\([^\n]*\)[ \t]*\r?\n/gm,
36
+ ];
37
+ const BARE_LOAD = [
38
+ /^[ \t]*if\s*\(\s*BuildConfig\.IS_NEW_ARCHITECTURE_ENABLED\s*\)\s*\{(?:\s*\/\/[^\n]*)*\s*load\(\)\s*\}[ \t]*\r?\n/gm,
39
+ /^[ \t]*load\(\)[ \t]*\r?\n/gm,
40
+ ];
41
+ const BARE_LOAD_IMPORT =
42
+ /^import\s+com\.facebook\.react\.defaults\.DefaultNewArchitectureEntryPoint\.load\b/m;
43
+ const OWN_LOAD_IMPORTS = [
44
+ 'com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load',
45
+ 'com.facebook.react.defaults.DefaultNewArchitectureEntryPoint',
46
+ 'com.facebook.soloader.SoLoader',
47
+ 'com.facebook.react.soloader.OpenSourceMergedSoMapping',
22
48
  ];
23
49
 
24
50
  /**
25
- * MainApplication.kt: the application becomes a NavigationApplication.
51
+ * MainApplication.kt: the application becomes a NavigationApplication, and loads React Native in its onCreate as the
52
+ * template does.
26
53
  * @param {string} text
27
54
  * @returns {StepResult}
28
55
  */
@@ -32,21 +59,20 @@ function planMainApplication(text) {
32
59
  note: 'changed by hand, see below',
33
60
  howTo: HOW_TO,
34
61
  });
35
- if (/:\s*NavigationApplication\(\)/.test(text)) {
36
- return /\bloadReactNative\s*\(/.test(codeOf(text))
62
+ if (/:\s*NavigationApplication\(/.test(text)) {
63
+ if (loadsReactNative(text) && !loadsReactNativeItself(text)) {
64
+ return { status: 'done', note: 'already set up' };
65
+ }
66
+ const next = withReactNativeLoaded(text);
67
+ return next === undefined
37
68
  ? manual
38
- : { status: 'done', note: 'already set up' };
69
+ : { status: 'change', next, note: 'loadReactNative(this) in onCreate' };
39
70
  }
40
71
  const header = /class\s+(\w+)\s*:\s*Application\(\)\s*,\s*ReactApplication\b/;
41
72
  if (!header.test(text)) return manual;
42
- let next = text.replace(header, 'class $1 : NavigationApplication()');
43
- // The template's onCreate only loads React Native, so it goes; another one keeps the rest of its code.
44
- next = next.replace(
45
- /\n\n?[ \t]*override\s+fun\s+onCreate\(\)\s*\{\s*super\.onCreate\(\)\s*loadReactNative\(this\)\s*\}/,
46
- ''
47
- );
48
- next = next.replace(/^[ \t]*loadReactNative\(this\)[ \t]*\r?\n/m, '');
49
- if (/\bloadReactNative\s*\(/.test(codeOf(next))) return manual;
73
+ // The template's onCreate stays: its loadReactNative(this) loads React Native for the navigation too.
74
+ let next = withReactNativeLoaded(text.replace(header, 'class $1 : NavigationApplication()'));
75
+ if (next === undefined) return manual;
50
76
  // Added first: with the template's imports gone the file would have none to sort it among.
51
77
  next = addImport(next, 'import dev.apexnative.navigation.NavigationApplication', {
52
78
  sorted: true,
@@ -55,4 +81,93 @@ function planMainApplication(text) {
55
81
  return { status: 'change', next, note: 'NavigationApplication' };
56
82
  }
57
83
 
58
- module.exports = { planMainApplication };
84
+ /**
85
+ * Whether the code of the file, not a comment, calls loadReactNative.
86
+ * @param {string} text
87
+ */
88
+ function loadsReactNative(text) {
89
+ return /\bloadReactNative\s*\(/.test(codeOf(text));
90
+ }
91
+
92
+ /**
93
+ * Whether the code of the file loads React Native on its own as well: the New Architecture's entry point or SoLoader.
94
+ * @param {string} text
95
+ */
96
+ function loadsReactNativeItself(text) {
97
+ const code = codeOf(text);
98
+ return (
99
+ /\bSoLoader\.init\s*\(/.test(code) ||
100
+ /\bDefaultNewArchitectureEntryPoint\.load\s*\(/.test(code) ||
101
+ (BARE_LOAD_IMPORT.test(text) && /(?<![\w.])load\s*\(/.test(code))
102
+ );
103
+ }
104
+
105
+ /**
106
+ * The application with `loadReactNative(this)` in its onCreate, after `super.onCreate()`: as it is when it has the
107
+ * call, with an onCreate of the template's when it has none. Undefined for an onCreate that doesn't call
108
+ * `super.onCreate()`, whose order only its author knows.
109
+ * @param {string} text
110
+ * @returns {string | undefined}
111
+ */
112
+ function withReactNativeLoaded(text) {
113
+ let without = text;
114
+ const patterns = BARE_LOAD_IMPORT.test(text) ? [...OWN_LOAD, ...BARE_LOAD] : OWN_LOAD;
115
+ for (const pattern of patterns) without = without.replace(pattern, '');
116
+ without = removeUnusedImports(without, OWN_LOAD_IMPORTS);
117
+ if (loadsReactNativeItself(without)) return undefined;
118
+ if (loadsReactNative(without)) return without;
119
+ return withLoadCall(without);
120
+ }
121
+
122
+ /**
123
+ * @param {string} text an application without a load of React Native
124
+ * @returns {string | undefined}
125
+ */
126
+ function withLoadCall(text) {
127
+ let next;
128
+ const superCall = /^([ \t]*)super\.onCreate\(\)[ \t]*\r?\n/m;
129
+ if (/\boverride\s+fun\s+onCreate\s*\(/.test(text)) {
130
+ if (!superCall.test(text)) return undefined;
131
+ next = text.replace(superCall, (line, indent) => `${line}${indent}loadReactNative(this)\n`);
132
+ } else {
133
+ const close = classEnd(text);
134
+ if (close === undefined) return undefined;
135
+ const indent = /^([ \t]+)override\b/m.exec(text)?.[1] ?? ' ';
136
+ const body = text.slice(0, close).replace(/\s*$/, '');
137
+ const onCreate = [
138
+ '',
139
+ `${indent}override fun onCreate() {`,
140
+ `${indent}${indent}super.onCreate()`,
141
+ `${indent}${indent}loadReactNative(this)`,
142
+ `${indent}}`,
143
+ ].join('\n');
144
+ next = `${body}\n${onCreate}\n${text.slice(close)}`;
145
+ }
146
+ return addImport(next, LOAD_IMPORT, { sorted: true });
147
+ }
148
+
149
+ /**
150
+ * The index of the closing brace of the file's application class: its braces counted from the class's own, past any
151
+ * declaration or comment after it. Undefined when the braces don't close.
152
+ * @param {string} text
153
+ * @returns {number | undefined}
154
+ */
155
+ function classEnd(text) {
156
+ // The comments and the string and character literals blanked out, so that an index in it is one in the text and a
157
+ // brace in a literal doesn't count.
158
+ const blank = (/** @type {string} */ part) => part.replace(/[^\n]/g, ' ');
159
+ const code = text
160
+ .replace(/\/\*[\s\S]*?\*\//g, blank)
161
+ .replace(/\/\/.*$/gm, blank)
162
+ .replace(/"(?:[^"\\\n]|\\.)*"|'(?:[^'\\\n]|\\.)*'/g, blank);
163
+ const header = /\bclass\s+\w+\s*:[^{]*\{/.exec(code);
164
+ if (!header) return undefined;
165
+ let depth = 0;
166
+ for (let index = header.index + header[0].length - 1; index < code.length; index++) {
167
+ if (code[index] === '{') depth++;
168
+ else if (code[index] === '}' && --depth === 0) return index;
169
+ }
170
+ return undefined;
171
+ }
172
+
173
+ module.exports = { planMainApplication, withReactNativeLoaded, loadsReactNativeItself };
@@ -146,7 +146,7 @@ navigation.push('Details', {
146
146
 
147
147
  Details rises 300 points as it fades in. Each property animates from a value to a value over a time, with a delay and an interpolation if it needs them; the [API](#api) lists every one.
148
148
 
149
- The same goes for `pop`, `showModal`, `dismissModal`, `setStackRoot` and `setRoot`. The swipe back is the exception: (iOS) it always follows the finger with the system's slide, so a custom `pop` plays for the back button and `pop()` only.
149
+ The same goes for `pop`, `showModal`, `dismissModal`, `setStackRoot` and `setRoot`. The swipe back is the exception: (iOS) it always follows the finger with the system's slide, so a custom `pop` plays for the back button and `pop()` only. (Android) A pop with an animation of its own shows no preview under the swipe: the screen pops when the swipe ends.
150
150
 
151
151
  ### The top bar
152
152
 
@@ -168,11 +168,10 @@ A screen can come in before its React content has drawn, or wait for it. Coming
168
168
 
169
169
  | | By default |
170
170
  | --- | --- |
171
- | (iOS) push | Doesn't wait for the content: the screen slides in with its bar, and the content draws as it comes. It waits only for components in the bar. |
172
- | (Android) push | Doesn't wait. With `appProvider` a React screen is rendered before the push starts, so it comes in drawn from the first frame. A screen with a React root of its own comes in at once, and its content draws as it comes; so does a native screen. The components of the bar draw as the screen comes in. |
171
+ | Push | Waits for the screen's first mount, half a second at most, so it comes in drawn, with what it set as it rendered (a title from `useScreenOptions`) already in its bar. A native screen comes in at once. (iOS) The components of the bar wait too, each to its own limit from the start of the wait. (Android) The components of the bar draw as the screen comes in; with `appProvider` the screen is rendered before the push starts ([below](#wait-one-tree)). |
173
172
  | (Android) modal | Waits for the content and the components of the bar, half a second at most, so the screen comes in drawn. |
174
173
 
175
- `waitForRender` of an animation changes it. `true` waits as long as it takes; `false` comes in at once (a push on Android with `appProvider` [still renders the screen first](#wait-one-tree)):
174
+ `waitForRender` of an animation changes it. `true` waits as long as it takes; `false` comes in at once, the content drawing as it comes (a push on Android with `appProvider` [still renders the screen first](#wait-one-tree)):
176
175
 
177
176
  ```ts
178
177
  navigation.push('Details', {
@@ -180,15 +179,15 @@ navigation.push('Details', {
180
179
  });
181
180
  ```
182
181
 
183
- A screen that draws nothing at first doesn't hold its transition back: it comes in empty, with its bar.
182
+ A screen that draws nothing at first holds its transition back half a second at most, then comes in with its bar.
184
183
 
185
184
  ### A push on Android, in one tree
186
185
 
187
- (Android) With `appProvider` a push doesn't wait for the screen, and the screen still comes in drawn: nothing of it mounts during the transition, which would drop frames. It goes in three steps:
186
+ (Android) With `appProvider` the screen comes in drawn without a wait of its own: nothing of it mounts during the transition, which would drop frames. It goes in three steps:
188
187
 
189
188
  1. The native side lays the screen out in its stack, out of sight, and gives React its size and its safe area.
190
189
  2. React renders the screen at that size with its content, at once.
191
- 3. The push starts in the frame in which that content appears; if it doesn't appear, after half a second.
190
+ 3. The push starts right after that content appears, once what the screen set as it rendered (its `useScreenOptions`) is applied; if it doesn't appear, after half a second.
192
191
 
193
192
  So the push starts later by the time React takes to render the screen, as a push in a native app starts once its screen is built. `waitForRender: false` goes the same way here; `true` waits for the content as long as it takes, and for the components of the bar, half a second at most.
194
193
 
@@ -370,7 +370,7 @@ The system bar at the top of the screen, with the time and the battery. Each scr
370
370
 
371
371
  ### Swipe back
372
372
 
373
- (iOS) The swipe from the left edge, and on iOS 26 from the content, that goes back following the finger. `popGesture` narrows it or turns it off. More: [The swipe back](../screen-ui/back-and-gestures#swipe).
373
+ (iOS) The swipe from the left edge, and on iOS 26 from the content, that goes back following the finger. `popGesture` narrows it or turns it off. (Android) 14+ The swipe of the system back shows the pop under the finger, and the user can stop halfway. More: [The swipe back](../screen-ui/back-and-gestures#swipe).
374
374
 
375
375
  ### System back
376
376
 
@@ -73,13 +73,30 @@ Options set in `static options` on a component no longer apply.
73
73
 
74
74
  **Fix:** move them into `defineScreen({ component, options })`; the object carries over as it is. More: [Options next to the screen](../migration/from-react-native-navigation#options).
75
75
 
76
+ ### The Android app crashes at start: React Native isn't loaded
77
+
78
+ (Android) The app closes at once with `NavigationApplication needs React Native loaded first`, and `doctor` reports `MainApplication.kt doesn't call loadReactNative(this) in onCreate`.
79
+
80
+ **Why:** `NavigationApplication` leaves the load of React Native to the template's `loadReactNative(this)`, which loads it as the app's Gradle files say (the New Architecture, edge-to-edge). Without the call nothing loads it.
81
+
82
+ **Fix:** keep the template's `onCreate`, or run `npx apex-navigation setup`, which adds it:
83
+
84
+ ```kotlin
85
+ override fun onCreate() {
86
+ super.onCreate()
87
+ loadReactNative(this)
88
+ }
89
+ ```
90
+
91
+ More: [Android files](../start/installation#android-files).
92
+
76
93
  ### The Android app crashes at start: React Native loads twice
77
94
 
78
- (Android) The app closes at once, and `doctor` reports `MainApplication.kt calls loadReactNative(this), NavigationApplication loads it too`.
95
+ (Android) The app closes at once, and `doctor` reports `MainApplication.kt loads React Native itself … next to loadReactNative(this)`.
79
96
 
80
- **Why:** `NavigationApplication` loads React Native itself, and a second load crashes the app.
97
+ **Why:** the application loads React Native on its own as well, with `SoLoader.init` or the New Architecture's entry point (`load()`, as apps from react-native-navigation have it), and a second load crashes the app.
81
98
 
82
- **Fix:** remove the `loadReactNative(this)` call from `MainApplication.kt`. More: [Android files](../start/installation#android-files).
99
+ **Fix:** keep only `loadReactNative(this)` in `onCreate`; `npx apex-navigation setup` removes the other load. More: [Android files](../start/installation#android-files).
83
100
 
84
101
  ### doctor warns about the JDK
85
102
 
@@ -3952,6 +3952,10 @@
3952
3952
  "heading": "A screen ignores its static options",
3953
3953
  "anchor": "static-options"
3954
3954
  },
3955
+ {
3956
+ "heading": "The Android app crashes at start: React Native isn't loaded",
3957
+ "anchor": "not-loaded"
3958
+ },
3955
3959
  {
3956
3960
  "heading": "The Android app crashes at start: React Native loads twice",
3957
3961
  "anchor": "loads-twice"
@@ -140,7 +140,7 @@ Each piece of `react-native-navigation`, and what takes its place. The [steps](#
140
140
  | `AppRegistry` left out of `index.js` | `AppRegistry.registerComponent(name, Navigation)` of the package. |
141
141
  | `RNNAppDelegate`, `import ReactNativeNavigation` | `ANNAppDelegate`, `import ApexNavigation`. |
142
142
  | `com.reactnativenavigation.*` | `dev.apexnative.navigation.*`. |
143
- | `NavigationReactNativeHost` in `MainApplication` | The template's `reactHost`; `NavigationApplication` loads React Native itself. |
143
+ | `NavigationReactNativeHost` in `MainApplication` | The template's `reactHost`, and its `loadReactNative(this)` in `onCreate`. |
144
144
  | `RNNKotlinVersion` in `android/build.gradle` | Nothing: the library reads `kotlinVersion`. |
145
145
 
146
146
  ## The move, step by step
@@ -212,11 +212,12 @@ import dev.apexnative.navigation.NavigationActivity
212
212
  class MainActivity : NavigationActivity()
213
213
  ```
214
214
 
215
- The application drops `NavigationReactNativeHost` and keeps the `reactHost` of the React Native template. `NavigationApplication` loads React Native in its `onCreate`, so the app doesn't call `loadReactNative` again.
215
+ The application drops `NavigationReactNativeHost` and keeps the `reactHost` of the React Native template. react-native-navigation loaded React Native itself; the navigation leaves it to the template's `loadReactNative(this)` in `onCreate`, which `migrate` adds.
216
216
 
217
217
  ```kotlin
218
218
  import com.facebook.react.PackageList
219
219
  import com.facebook.react.ReactHost
220
+ import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
220
221
  import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
221
222
  import dev.apexnative.navigation.NavigationApplication
222
223
 
@@ -227,6 +228,11 @@ class MainApplication : NavigationApplication() {
227
228
  packageList = PackageList(this).packages,
228
229
  )
229
230
  }
231
+
232
+ override fun onCreate() {
233
+ super.onCreate()
234
+ loadReactNative(this)
235
+ }
230
236
  }
231
237
  ```
232
238
 
@@ -17,7 +17,7 @@ On iOS the back button of Details reads "Inbox". This page answers:
17
17
 
18
18
  1. [The ways back](#ways) on each platform.
19
19
  2. [The back button](#back-button): [its title](#title) and [its look](#look).
20
- 3. [The swipe back](#swipe) of iOS, and [the system back](#system-back) of Android.
20
+ 3. [The swipe back](#swipe) of both platforms, and [the system back](#system-back) of Android.
21
21
  4. [How to keep the user on a screen](#prevent) with unsaved changes.
22
22
 
23
23
  ## The ways back
@@ -85,7 +85,7 @@ export const MapScreen = defineScreen({
85
85
  | `'edge'` | From the edge only: a swipe on the content stays with the content. |
86
86
  | `false` | Off. The back button still works. |
87
87
 
88
- (Android) The system back swipes from either edge; the app doesn't change it.
88
+ (Android) The swipe is the system's back, from either edge, and `popGesture` doesn't apply to it. On Android 14+ it shows the pop before the user lets go: both screens shrink, the screen under comes in behind a dim, and the screen on top follows the finger. The user can stop halfway and stay, as on iOS. On Android 13 and older the screen pops when the swipe ends. When the preview shows and when it doesn't: [the preview of a pop](#pop-preview).
89
89
 
90
90
  ## The system back of Android
91
91
 
@@ -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 |
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,16 +68,20 @@ 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`.
@@ -85,7 +89,7 @@ dispatcher.
85
89
  `+[ANNAppearanceDispatcher endTransitionHiding:showing:events:answer:value:]`. In this order: `ComponentDidDisappear`
86
90
  of Home, `ComponentDidAppear` of Profile, `ANN.NavigationStateChanged` (`+[ANNNavigationState updateNow]`), the
87
91
  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
92
+ (`reportBeforeAnswerOf:`), the promise with Profile's id, then `ANN.CommandCompleted`. `finish:` frees the queue for
89
93
  the next operation.
90
94
 
91
95
  Events go out through `ANNEventEmitter` on the main queue and reach JS through `ANNTurboEventEmitter`.
@@ -116,13 +120,14 @@ Each of these owns one rule. Call it; do not copy it.
116
120
  - `ANNCommandAnswer`: The answer: state, 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
126
  - `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`.
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.
@@ -1,26 +1,12 @@
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;
@@ -72,14 +58,13 @@ static NSSet<NSString *> *ANNCompletedCommandNames(void) {
72
58
  RCTPromiseResolveBlock resolve = _resolve;
73
59
  _resolve = nil;
74
60
  _reject = nil;
75
- // A command that may change what shows (the 13 and `setColumnShown`): the state goes before the promise, so that
61
+ // A command that may change what shows (`ANNCommandContract`): the state goes before the promise, so that
76
62
  // `getState()` after `await` has the result.
77
- BOOL changesWhatShows = [ANNCompletedCommandNames() containsObject:_commandName] ||
78
- [_commandName isEqualToString:@"setColumnShown"];
63
+ BOOL changesWhatShows = ANNCommandReportsState(_commandName);
79
64
  if (changesWhatShows) {
80
65
  // The command did its work: a failure of the state step costs only the state, never the answer.
81
66
  @try {
82
- [ANNNavigationState reportBeforeAnswer];
67
+ [ANNNavigationState reportBeforeAnswerOf:_commandName];
83
68
  } @catch (NSException *exception) {
84
69
  NSString *reason = exception.reason.length > 0 ? exception.reason : exception.name;
85
70
  [ANNErrorHandler logFailureOf:_commandName
@@ -88,7 +73,7 @@ static NSSet<NSString *> *ANNCompletedCommandNames(void) {
88
73
  }
89
74
  }
90
75
  [ANNErrorHandler resolve:resolve of:_commandName value:value];
91
- if (_commandId && [ANNCompletedCommandNames() containsObject:_commandName]) {
76
+ if (_commandId && ANNCommandSendsCompleted(_commandName)) {
92
77
  double completionTime = floor(NSDate.date.timeIntervalSince1970 * 1000);
93
78
  [ApexNavigation.eventEmitter sendCommandCompleted:_commandName
94
79
  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
+ }