@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.
- package/CHANGELOG.md +9 -0
- package/android/CLAUDE.md +17 -7
- package/android/CORE.md +8 -3
- package/android/RECIPES.md +9 -3
- package/android/src/main/java/dev/apexnative/navigation/NavigationApplication.kt +34 -13
- package/android/src/main/java/dev/apexnative/navigation/layout/LayoutChecks.kt +5 -1
- package/android/src/main/java/dev/apexnative/navigation/layout/LayoutNode.kt +6 -2
- package/android/src/main/java/dev/apexnative/navigation/nativescreen/NativeScreenContext.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/options/ButtonMenuOptions.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/options/ButtonOptions.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/options/OptionsCache.kt +8 -4
- package/android/src/main/java/dev/apexnative/navigation/options/UserChoices.kt +159 -0
- package/android/src/main/java/dev/apexnative/navigation/react/CommandAnswer.kt +2 -24
- package/android/src/main/java/dev/apexnative/navigation/react/CommandContract.kt +59 -0
- package/android/src/main/java/dev/apexnative/navigation/react/Hinge.kt +27 -19
- package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +5 -4
- package/android/src/main/java/dev/apexnative/navigation/react/PendingCalls.kt +48 -0
- package/android/src/main/java/dev/apexnative/navigation/react/events/AppearanceDispatcher.kt +17 -7
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotContainers.kt +10 -7
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotView.kt +4 -3
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotViews.kt +40 -0
- package/android/src/main/java/dev/apexnative/navigation/utils/ImageKey.kt +66 -0
- package/android/src/main/java/dev/apexnative/navigation/utils/ImageLoader.kt +7 -89
- package/android/src/main/java/dev/apexnative/navigation/utils/ImageRequests.kt +76 -0
- package/android/src/main/java/dev/apexnative/navigation/utils/OptionImages.kt +262 -0
- package/android/src/main/java/dev/apexnative/navigation/utils/ViewController.kt +3 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/AppliedTabCalls.kt +49 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsController.kt +16 -6
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsPresenter.kt +25 -24
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ContentReadiness.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/RenderWait.kt +27 -7
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalCoordinator.kt +5 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt +4 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/RootCoordinator.kt +6 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/overlay/OverlayCoordinator.kt +8 -8
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/Capabilities.kt +11 -4
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuController.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitPanes.kt +23 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitViewController.kt +58 -15
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackCommands.kt +28 -18
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +16 -7
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackDisplay.kt +2 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackQueue.kt +85 -29
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabPresenter.kt +7 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/StackSearchPresenter.kt +3 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/MenuChecks.kt +13 -56
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +10 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/viewcontroller/ViewController.kt +56 -21
- package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/BottomTabs.kt +4 -2
- package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/Fab.kt +14 -10
- package/cli/doctor/checks/app.js +61 -13
- package/cli/migrate/native.js +18 -0
- package/cli/setup/source.js +2 -1
- package/cli/setup/steps/mainApplication.js +134 -19
- package/docs-md/architecture/transitions.md +6 -7
- package/docs-md/help/glossary.md +1 -1
- package/docs-md/help/troubleshooting.md +20 -3
- package/docs-md/index.json +4 -0
- package/docs-md/migration/from-react-native-navigation.md +8 -2
- package/docs-md/screen-ui/back-and-gestures.md +2 -2
- package/docs-md/start/installation.md +3 -1
- package/ios/CLAUDE.md +17 -5
- package/ios/CORE.md +13 -8
- package/ios/Contract/ANNCommandAnswer.mm +5 -20
- package/ios/Contract/ANNCommandContract.h +15 -0
- package/ios/Contract/ANNCommandContract.mm +78 -0
- package/ios/Core/ANNAppearanceDispatcher.mm +1 -1
- package/ios/Core/ANNErrorHandler.h +1 -1
- package/ios/Core/ANNErrorHandler.mm +3 -1
- package/ios/Core/ANNEventEmitter.h +1 -1
- package/ios/Core/ANNEventEmitter.mm +2 -2
- package/ios/Core/ANNJavaScriptCalls.h +34 -0
- package/ios/Core/ANNJavaScriptCalls.mm +61 -0
- package/ios/Core/ANNNavigationState.h +7 -3
- package/ios/Core/ANNNavigationState.mm +10 -5
- package/ios/Core/ANNNodeHelper.h +32 -12
- package/ios/Core/ANNNodeHelper.mm +100 -3
- package/ios/Core/ANNRenderWait.h +15 -1
- package/ios/Core/ANNRenderWait.mm +119 -17
- package/ios/Core/UIViewController+LayoutProtocol.h +9 -2
- package/ios/Core/UIViewController+LayoutProtocol.mm +8 -0
- package/ios/Layouts/Modal/ANNModalCoordinator.h +1 -1
- package/ios/Layouts/Modal/ANNModalCoordinator.mm +55 -14
- package/ios/Layouts/Screen/ANNComponentViewController.h +8 -0
- package/ios/Layouts/Screen/ANNComponentViewController.mm +32 -5
- package/ios/Layouts/Screen/ANNScreenPlace.h +41 -0
- package/ios/Layouts/Screen/ANNScreenPlace.mm +175 -0
- package/ios/Layouts/SplitView/ANNSplitViewCommands.h +3 -2
- package/ios/Layouts/SplitView/ANNSplitViewCommands.mm +105 -39
- package/ios/Layouts/SplitView/ANNSplitViewController.mm +4 -2
- package/ios/Layouts/Stack/ANNStackCommands.mm +1 -0
- package/ios/Layouts/Stack/ANNStackController.mm +26 -3
- package/ios/Layouts/Stack/ANNStackQueue.h +3 -1
- package/ios/Layouts/Stack/ANNStackQueue.mm +56 -43
- package/ios/Layouts/Stack/UINavigationController+ANNCommands.h +4 -2
- package/ios/Layouts/Stack/UINavigationController+ANNCommands.mm +2 -3
- package/ios/Layouts/Tabs/ANNBottomTabItemBuilder.h +4 -0
- package/ios/Layouts/Tabs/ANNBottomTabItemBuilder.mm +11 -0
- package/ios/Layouts/Tabs/ANNBottomTabsController.mm +13 -5
- package/ios/Layouts/Tabs/ANNBottomTabsPresenter.h +2 -2
- package/ios/Layouts/Tabs/ANNBottomTabsPresenter.mm +109 -7
- package/ios/Options/ANNOptionsCache.h +7 -6
- package/ios/Options/ANNSearchBarOptions.h +4 -3
- package/ios/Options/ANNUserChoice.h +57 -0
- package/ios/Options/ANNUserChoice.mm +44 -0
- package/ios/Options/Params/ANNImageSource.h +61 -0
- package/ios/Options/Params/ANNImageSource.mm +101 -0
- package/ios/Options/Params/ANNOptionImages.h +37 -0
- package/ios/Options/Params/ANNOptionImages.mm +284 -0
- package/ios/Options/Params/Image.h +11 -2
- package/ios/Options/Params/Image.mm +15 -2
- package/ios/Options/Params/ImageParser.h +5 -5
- package/ios/Options/Params/ImageParser.mm +28 -46
- package/ios/Public/ApexNavigation/ApexNavigation.h +1 -1
- package/ios/RECIPES.md +11 -5
- package/ios/React/Slots/ANNSlotComponentView.mm +24 -17
- package/ios/React/Slots/ANNSlotContainers.h +14 -6
- package/ios/React/Slots/ANNSlotContainers.mm +75 -2
- package/ios/TopBar/ANNBarButtons.h +9 -5
- package/ios/TopBar/ANNBarButtons.mm +211 -63
- package/ios/TopBar/ANNIconButtonImage.h +1 -0
- package/ios/TopBar/ANNIconButtonImage.mm +34 -1
- package/ios/TopBar/ANNLargeTitlePresenter.h +13 -0
- package/ios/TopBar/ANNLargeTitlePresenter.mm +26 -3
- package/ios/TopBar/ANNSearchPresenter.mm +170 -19
- package/ios/TopBar/ANNTopBarPresenter.h +5 -0
- package/ios/TopBar/ANNTopBarPresenter.mm +60 -11
- package/ios/TurboModules/ANNTurboModule.mm +39 -21
- package/lib/module/ARCHITECTURE.md +18 -18
- package/lib/module/adapters/NativeANNTurboModule.js.map +1 -1
- package/lib/module/api/navigation.js.map +1 -1
- package/lib/module/engine/commands.js +9 -7
- package/lib/module/engine/commands.js.map +1 -1
- package/lib/module/engine/layout.js +13 -9
- package/lib/module/engine/layout.js.map +1 -1
- package/lib/module/engine/options.js +44 -11
- package/lib/module/engine/options.js.map +1 -1
- package/lib/module/engine/places.js +44 -7
- package/lib/module/engine/places.js.map +1 -1
- package/lib/module/engine/props.js +30 -1
- package/lib/module/engine/props.js.map +1 -1
- package/lib/module/engine/rememberedOptions.js +1 -1
- package/lib/module/engine/rememberedOptions.js.map +1 -1
- package/lib/module/engine/screens.js +22 -12
- package/lib/module/engine/screens.js.map +1 -1
- package/lib/module/interfaces/contract/commands.json +101 -0
- package/lib/typescript/adapters/NativeANNTurboModule.d.ts +5 -2
- package/lib/typescript/adapters/NativeANNTurboModule.d.ts.map +1 -1
- package/lib/typescript/api/events/navigationEvents.d.ts +1 -1
- package/lib/typescript/api/events/navigationEvents.d.ts.map +1 -1
- package/lib/typescript/api/hooks/useNavigation.d.ts +1 -1
- package/lib/typescript/api/hooks/useNavigation.d.ts.map +1 -1
- package/lib/typescript/api/navigation.d.ts +4 -1
- package/lib/typescript/api/navigation.d.ts.map +1 -1
- package/lib/typescript/engine/commands.d.ts.map +1 -1
- package/lib/typescript/engine/options.d.ts +5 -2
- package/lib/typescript/engine/options.d.ts.map +1 -1
- package/lib/typescript/engine/places.d.ts +5 -2
- package/lib/typescript/engine/places.d.ts.map +1 -1
- package/lib/typescript/engine/props.d.ts +8 -1
- package/lib/typescript/engine/props.d.ts.map +1 -1
- package/lib/typescript/engine/rememberedOptions.d.ts.map +1 -1
- package/lib/typescript/engine/screens.d.ts.map +1 -1
- package/lib/typescript/interfaces/ComponentEvents.d.ts +2 -2
- package/lib/typescript/interfaces/ComponentEvents.d.ts.map +1 -1
- package/lib/typescript/interfaces/Events.d.ts +19 -10
- package/lib/typescript/interfaces/Events.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/animations.d.ts +5 -2
- package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/buttons.d.ts +13 -5
- package/lib/typescript/interfaces/options/buttons.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/topBar.d.ts +22 -9
- package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/ARCHITECTURE.md +18 -18
- package/src/adapters/NativeANNTurboModule.ts +5 -2
- package/src/api/events/navigationEvents.ts +1 -1
- package/src/api/hooks/useNavigation.ts +1 -1
- package/src/api/navigation.ts +4 -1
- package/src/engine/commands.ts +19 -7
- package/src/engine/layout.ts +16 -9
- package/src/engine/options.ts +39 -11
- package/src/engine/places.ts +56 -7
- package/src/engine/props.ts +30 -1
- package/src/engine/rememberedOptions.ts +4 -1
- package/src/engine/screens.tsx +23 -13
- package/src/interfaces/ComponentEvents.ts +2 -2
- package/src/interfaces/Events.ts +19 -10
- package/src/interfaces/contract/commands.json +101 -0
- package/src/interfaces/options/animations.ts +5 -2
- package/src/interfaces/options/buttons.ts +13 -5
- 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
|
|
9
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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\(
|
|
36
|
-
|
|
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: '
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
next
|
|
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
|
-
|
|
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
|
-
|
|
|
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
|
|
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`
|
|
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
|
|
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
|
|
package/docs-md/help/glossary.md
CHANGED
|
@@ -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
|
|
95
|
+
(Android) The app closes at once, and `doctor` reports `MainApplication.kt loads React Native itself … next to loadReactNative(this)`.
|
|
79
96
|
|
|
80
|
-
**Why:**
|
|
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:**
|
|
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
|
|
package/docs-md/index.json
CHANGED
|
@@ -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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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` (
|
|
72
|
-
|
|
73
|
-
goes back to that entry; else
|
|
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
|
|
80
|
-
|
|
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
|
-
(`
|
|
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
|
|
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 (
|
|
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 =
|
|
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
|
|
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 &&
|
|
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
|
+
}
|