@apexnative/navigation 1.0.0-alpha.3 → 1.0.0-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/android/CLAUDE.md +26 -9
- package/android/CORE.md +32 -17
- package/android/RECIPES.md +9 -3
- package/android/src/main/java/dev/apexnative/navigation/NavigationActivity.kt +25 -9
- 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/AnimationsOptions.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/HardwareBackButtonOptions.kt +9 -1
- package/android/src/main/java/dev/apexnative/navigation/options/OptionsCache.kt +8 -4
- package/android/src/main/java/dev/apexnative/navigation/options/StackAnimationOptions.kt +40 -2
- package/android/src/main/java/dev/apexnative/navigation/options/UserChoices.kt +159 -0
- package/android/src/main/java/dev/apexnative/navigation/react/ArrangementStates.kt +6 -6
- package/android/src/main/java/dev/apexnative/navigation/react/CommandAnswer.kt +29 -36
- package/android/src/main/java/dev/apexnative/navigation/react/CommandContract.kt +59 -0
- package/android/src/main/java/dev/apexnative/navigation/react/Hinge.kt +33 -22
- 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/ReactView.kt +39 -20
- package/android/src/main/java/dev/apexnative/navigation/react/ReloadCoordinator.kt +7 -4
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenInsets.kt +6 -6
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenRegions.kt +10 -5
- package/android/src/main/java/dev/apexnative/navigation/react/events/AppearanceDispatcher.kt +47 -46
- package/android/src/main/java/dev/apexnative/navigation/react/events/EventEmitter.kt +34 -7
- package/android/src/main/java/dev/apexnative/navigation/react/events/NavigationState.kt +154 -0
- package/android/src/main/java/dev/apexnative/navigation/safearea/SafeAreaView.kt +6 -4
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotContainers.kt +10 -7
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotScreenView.kt +12 -12
- 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/arrangement/ArrangementController.kt +3 -2
- 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 +25 -17
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsPresenter.kt +25 -24
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ComponentViewController.kt +6 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ContentReadiness.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/RenderWait.kt +27 -7
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ScreenReadiness.kt +103 -56
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalCoordinator.kt +5 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/BackCallbackCheck.kt +50 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/BackCallbackState.kt +45 -14
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt +25 -8
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/RootCoordinator.kt +8 -5
- 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 +61 -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 +25 -12
- 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/ScreenSearch.kt +3 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/StackSearchPresenter.kt +5 -8
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/BarComponents.kt +5 -51
- 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 +14 -11
- 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/bottomtabs/CustomBottomTabItemView.kt +0 -5
- 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/doctor/checks/splash.js +4 -22
- package/cli/migrate/native.js +18 -0
- package/cli/setup/source.js +2 -1
- package/cli/setup/steps/mainApplication.js +134 -19
- package/cli/splash/index.js +3 -1
- package/docs-md/architecture/transitions.md +6 -7
- package/docs-md/containers/modals.md +2 -2
- package/docs-md/guides/cli.md +1 -2
- package/docs-md/help/glossary.md +1 -1
- package/docs-md/help/troubleshooting.md +22 -5
- 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 +24 -2
- package/docs-md/start/installation.md +4 -2
- package/ios/CLAUDE.md +17 -5
- package/ios/CORE.md +18 -13
- package/ios/Contract/ANNCommandAnswer.h +9 -2
- package/ios/Contract/ANNCommandAnswer.mm +28 -30
- package/ios/Contract/ANNCommandContract.h +15 -0
- package/ios/Contract/ANNCommandContract.mm +78 -0
- package/ios/Core/ANNAppearanceDispatcher.h +5 -4
- package/ios/Core/ANNAppearanceDispatcher.mm +12 -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 -5
- package/ios/Core/ANNNavigationState.mm +8 -7
- 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/Root/ANNSplashViewController.h +5 -3
- package/ios/Layouts/Root/ANNSplashViewController.mm +147 -0
- 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/ANNPreviewPush.h +5 -5
- package/ios/Layouts/Stack/ANNPreviewPush.mm +85 -22
- 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/commands/commands.js +54 -6
- package/lib/module/api/commands/commands.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 +52 -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/commands/commands.d.ts.map +1 -1
- package/lib/typescript/api/commands/presentation.d.ts +4 -3
- package/lib/typescript/api/commands/presentation.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 +7 -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.d.ts +1 -1
- package/lib/typescript/interfaces/Options.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/animations.d.ts +23 -4
- 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/stack.d.ts +9 -0
- package/lib/typescript/interfaces/options/stack.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/commands/commands.ts +48 -7
- package/src/api/commands/presentation.ts +4 -3
- 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 +65 -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/Options.ts +1 -0
- package/src/interfaces/contract/commands.json +101 -0
- package/src/interfaces/options/animations.ts +24 -4
- package/src/interfaces/options/buttons.ts +13 -5
- package/src/interfaces/options/stack.ts +9 -0
- package/src/interfaces/options/topBar.ts +22 -9
- package/android/src/main/java/dev/apexnative/navigation/react/events/NavigationStateReporter.kt +0 -62
- package/android/src/main/java/dev/apexnative/navigation/react/events/StateHold.kt +0 -62
package/cli/doctor/checks/app.js
CHANGED
|
@@ -11,8 +11,10 @@ const {
|
|
|
11
11
|
reactNativeVersion,
|
|
12
12
|
usesExpo,
|
|
13
13
|
} = require('../../app');
|
|
14
|
+
const { mainActivity } = require('../../links/native');
|
|
14
15
|
const { findProject } = require('../../project');
|
|
15
16
|
const { codeOf } = require('../../setup/source');
|
|
17
|
+
const { loadsReactNativeItself } = require('../../setup/steps/mainApplication');
|
|
16
18
|
const { planCodegen } = require('../../codegen');
|
|
17
19
|
const { readConfig } = require('../../config');
|
|
18
20
|
const { planProject } = require('../../setup');
|
|
@@ -27,6 +29,16 @@ const { splashChecks } = require('./splash');
|
|
|
27
29
|
const SETUP = { label: 'npx apex-navigation setup', run: /** @type {const} */ ('setup') };
|
|
28
30
|
const GENERATED = ['ApexNavigation.generated.swift', 'ApexNavigationGenerated.kt'];
|
|
29
31
|
const CODEGEN = { label: 'npx apex-navigation codegen', run: /** @type {const} */ ('codegen') };
|
|
32
|
+
// The changes the main activity must handle itself; the template's value has them all.
|
|
33
|
+
const CONFIG_CHANGES = [
|
|
34
|
+
'orientation',
|
|
35
|
+
'screenSize',
|
|
36
|
+
'smallestScreenSize',
|
|
37
|
+
'screenLayout',
|
|
38
|
+
'uiMode',
|
|
39
|
+
];
|
|
40
|
+
const TEMPLATE_CONFIG_CHANGES =
|
|
41
|
+
'keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode';
|
|
30
42
|
|
|
31
43
|
/**
|
|
32
44
|
* React Native, the navigation, and the files setup sets up.
|
|
@@ -122,28 +134,64 @@ function appChecks({ root, run }) {
|
|
|
122
134
|
});
|
|
123
135
|
}
|
|
124
136
|
|
|
137
|
+
const configChanges = configChangesCheck(project.manifest);
|
|
138
|
+
if (configChanges) checks.push(configChanges);
|
|
139
|
+
|
|
125
140
|
checks.push(...splashChecks(root, project), ...iconChecks(root, project));
|
|
126
141
|
|
|
127
|
-
//
|
|
142
|
+
// NavigationApplication leaves the load of React Native to the template's call: without it the app closes on start,
|
|
143
|
+
// and so it does with a second load of the app's own next to it.
|
|
128
144
|
const application = project.mainApplication && fs.readFileSync(project.mainApplication, 'utf8');
|
|
129
|
-
if (
|
|
130
|
-
application
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
145
|
+
if (application && /:\s*NavigationApplication\(/.test(application)) {
|
|
146
|
+
const loads = /\bloadReactNative\s*\(/.test(codeOf(application));
|
|
147
|
+
if (!loads) {
|
|
148
|
+
checks.push({
|
|
149
|
+
group: 'app',
|
|
150
|
+
title: 'React Native not loaded',
|
|
151
|
+
status: 'error',
|
|
152
|
+
detail: "MainApplication.kt doesn't call loadReactNative(this) in onCreate",
|
|
153
|
+
fix: SETUP,
|
|
154
|
+
});
|
|
155
|
+
} else if (loadsReactNativeItself(application)) {
|
|
156
|
+
checks.push({
|
|
157
|
+
group: 'app',
|
|
158
|
+
title: 'React Native loads twice',
|
|
159
|
+
status: 'error',
|
|
160
|
+
detail:
|
|
161
|
+
'MainApplication.kt loads React Native itself (SoLoader.init or the New Architecture entry point) next to loadReactNative(this)',
|
|
162
|
+
fix: SETUP,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
141
165
|
}
|
|
142
166
|
const generated = generatedCheck(root, run);
|
|
143
167
|
if (generated) checks.push(generated);
|
|
144
168
|
return checks;
|
|
145
169
|
}
|
|
146
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Whether the main activity handles the changes of its window itself, as the React Native template has it. Without
|
|
173
|
+
* one of them Android recreates the activity on a rotation, a resize, a fold or a light/dark switch: every screen goes
|
|
174
|
+
* with it and the navigation starts again from its root. Nothing without a manifest or a main activity.
|
|
175
|
+
* @param {string | undefined} manifest
|
|
176
|
+
* @returns {Check | undefined}
|
|
177
|
+
*/
|
|
178
|
+
function configChangesCheck(manifest) {
|
|
179
|
+
if (!manifest || !fs.existsSync(manifest)) return undefined;
|
|
180
|
+
const activity = mainActivity(fs.readFileSync(manifest, 'utf8'));
|
|
181
|
+
if (!activity) return undefined;
|
|
182
|
+
const value = /android:configChanges\s*=\s*(?:"([^"]*)"|'([^']*)')/.exec(activity.text);
|
|
183
|
+
const declared = (value?.[1] ?? value?.[2] ?? '').split('|').map((change) => change.trim());
|
|
184
|
+
const missing = CONFIG_CHANGES.filter((change) => !declared.includes(change));
|
|
185
|
+
if (missing.length === 0) return undefined;
|
|
186
|
+
return {
|
|
187
|
+
group: 'app',
|
|
188
|
+
title: 'Config changes',
|
|
189
|
+
status: 'warn',
|
|
190
|
+
detail: `the main activity doesn't handle ${missing.join(', ')}: Android recreates it, and every screen with it`,
|
|
191
|
+
fix: { label: `Set android:configChanges="${TEMPLATE_CONFIG_CHANGES}" on the main activity` },
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
147
195
|
/**
|
|
148
196
|
* One line for a few files: set up, or which need setup or a change by hand.
|
|
149
197
|
* @param {string} title
|
|
@@ -17,34 +17,16 @@ const CIRCLE = 192;
|
|
|
17
17
|
const LOGO_HEIGHT = /<dimen name="apex_navigation_splash_logo_height">([\d.]+)dp<\/dimen>/;
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
* The native splash: made from its settings in package.json on both platforms, its logo whole on Android 12
|
|
21
|
-
* settings
|
|
20
|
+
* The native splash: made from its settings in package.json on both platforms, its logo whole on Android 12+. Without
|
|
21
|
+
* settings there is nothing to check: the navigation keeps a copy of a storyboard and of `UILaunchScreen` alike.
|
|
22
22
|
* @param {string} root
|
|
23
23
|
* @param {Project} project
|
|
24
24
|
* @returns {Check[]}
|
|
25
25
|
*/
|
|
26
26
|
function splashChecks(root, project) {
|
|
27
|
-
const plist = project.infoPlist && fs.readFileSync(project.infoPlist, 'utf8');
|
|
28
27
|
const saved = readConfig(root).splash;
|
|
29
|
-
if (!saved)
|
|
30
|
-
|
|
31
|
-
if (
|
|
32
|
-
plist?.includes('<key>UILaunchScreen</key>') &&
|
|
33
|
-
!plist.includes('<key>UILaunchStoryboardName</key>')
|
|
34
|
-
) {
|
|
35
|
-
return [
|
|
36
|
-
{
|
|
37
|
-
group: 'app',
|
|
38
|
-
title: 'Launch screen',
|
|
39
|
-
status: 'warn',
|
|
40
|
-
detail:
|
|
41
|
-
'UILaunchScreen in Info.plist: an empty window shows between it and the first screen',
|
|
42
|
-
fix: SPLASH,
|
|
43
|
-
},
|
|
44
|
-
];
|
|
45
|
-
}
|
|
46
|
-
return [];
|
|
47
|
-
}
|
|
28
|
+
if (!saved) return [];
|
|
29
|
+
const plist = project.infoPlist && fs.readFileSync(project.infoPlist, 'utf8');
|
|
48
30
|
const resolved = resolveSettings(root, {}, saved);
|
|
49
31
|
if ('errors' in resolved) {
|
|
50
32
|
return [
|
package/cli/migrate/native.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
const fs = require('node:fs');
|
|
5
5
|
const path = require('node:path');
|
|
6
|
+
const { withReactNativeLoaded } = require('../setup/steps/mainApplication');
|
|
6
7
|
|
|
7
8
|
/** @typedef {import('../changes').FileChange} FileChange */
|
|
8
9
|
/** @typedef {import('./report').ReportItem} ReportItem */
|
|
@@ -158,6 +159,23 @@ function planNative(project) {
|
|
|
158
159
|
const before = fs.readFileSync(file, 'utf8');
|
|
159
160
|
let next = before;
|
|
160
161
|
for (const [pattern, replacement] of rewrite.rules) next = next.replace(pattern, replacement);
|
|
162
|
+
// react-native-navigation loaded React Native itself; the navigation's application leaves it to the template's
|
|
163
|
+
// loadReactNative(this), which such an app doesn't have.
|
|
164
|
+
if (file === project.mainApplication && /:\s*NavigationApplication\(/.test(next)) {
|
|
165
|
+
const loaded = withReactNativeLoaded(next);
|
|
166
|
+
if (loaded !== undefined) next = loaded;
|
|
167
|
+
else {
|
|
168
|
+
report.push({
|
|
169
|
+
file: path.relative(root, file),
|
|
170
|
+
line: 1,
|
|
171
|
+
step: 'native',
|
|
172
|
+
before: 'onCreate',
|
|
173
|
+
after: 'super.onCreate() then loadReactNative(this), and no load of its own',
|
|
174
|
+
what: 'React Native loads through loadReactNative(this) in onCreate: add it by hand',
|
|
175
|
+
docs: `${DOCS}#native`,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
161
179
|
if (next !== before) {
|
|
162
180
|
changes.push({
|
|
163
181
|
name: path.basename(file),
|
package/cli/setup/source.js
CHANGED
|
@@ -35,7 +35,8 @@ function removeUnusedImports(text, names) {
|
|
|
35
35
|
for (const name of names) {
|
|
36
36
|
const line = new RegExp(`^import\\s+${escape(name)}[ \\t]*\\r?\\n`, 'm');
|
|
37
37
|
const simple = name.slice(name.lastIndexOf('.') + 1);
|
|
38
|
-
|
|
38
|
+
// A name in a comment is no use of the import.
|
|
39
|
+
const code = codeOf(result).replace(/^import\s.*$/gm, '');
|
|
39
40
|
if (line.test(result) && !new RegExp(`\\b${escape(simple)}\\b`).test(code))
|
|
40
41
|
result = result.replace(line, '');
|
|
41
42
|
}
|
|
@@ -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 };
|
package/cli/splash/index.js
CHANGED
|
@@ -88,7 +88,9 @@ async function splash({
|
|
|
88
88
|
}
|
|
89
89
|
const plist = project.infoPlist && fs.readFileSync(project.infoPlist, 'utf8');
|
|
90
90
|
if (plist?.includes('<key>UILaunchScreen</key>')) {
|
|
91
|
-
command.warn(
|
|
91
|
+
command.warn(
|
|
92
|
+
'Info.plist has UILaunchScreen too: iOS shows it instead of the storyboard; remove it, so that iOS shows the storyboard.'
|
|
93
|
+
);
|
|
92
94
|
}
|
|
93
95
|
return command.apply(
|
|
94
96
|
[
|
|
@@ -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
|
|
|
@@ -52,8 +52,8 @@ Compose shows with a close button at the leading edge of its top bar. The settin
|
|
|
52
52
|
|
|
53
53
|
| Setting | What it does |
|
|
54
54
|
| --- | --- |
|
|
55
|
-
| `closeButton` | A close button on the first screen: the system one on iOS, an icon on Android. Needs the stack. |
|
|
56
|
-
| `onDismiss` | Called once the modal is gone, whatever closed it: a swipe, a command, a new root. |
|
|
55
|
+
| `closeButton` | A close button on the first screen: the system one on iOS, an icon on Android. Needs the stack. A new first screen from `setStack` or `replace` in the modal's stack gets it too. |
|
|
56
|
+
| `onDismiss` | Called once the modal is gone, whatever closed it: a swipe, a command, a new root. `setStack` and `replace` in the modal's stack replace its first screen and call it too. |
|
|
57
57
|
| `stack` | `false`: the screen alone, without a stack and its top bar. |
|
|
58
58
|
| `style`, `sheet`, `popover`, `contentSize` | How it looks: see [How a modal looks](#styles). |
|
|
59
59
|
| `transition`, `options` | The animation, and the screen's options for this show. |
|
package/docs-md/guides/cli.md
CHANGED
|
@@ -311,7 +311,7 @@ Each file and why: [What the command writes](./splash-screen#files).
|
|
|
311
311
|
- **The settings** go into `package.json`, under `apexNavigation.splash`. An option given replaces its saved value; to drop the dark logo or color, remove `darkLogo` or `darkBackground` there and run the command again.
|
|
312
312
|
- **A logo whose corners leave the circle** of Android 12's splash icon, 192 dp, gets a warning: Android cuts them. Make `--logo-width` smaller, or give the logo room around it.
|
|
313
313
|
- **A file it can't change** is listed as `manual`, with the change to make: an app without `Images.xcassets` next to `Info.plist`, a manifest whose `MainActivity` and `<application>` have no theme to go over.
|
|
314
|
-
- **`Info.plist` with `UILaunchScreen`** as well gets a warning:
|
|
314
|
+
- **`Info.plist` with `UILaunchScreen`** as well gets a warning: iOS shows that dictionary instead of the storyboard, so remove it.
|
|
315
315
|
- **Its errors** in the JSON: `splash-settings` for a setting it can't take, `splash-image` for a logo it can't read or that doesn't fit (too tall at its width, a dark logo of other proportions), and `sharp-missing`, as for `icons`.
|
|
316
316
|
|
|
317
317
|
`doctor` checks the splash in the App group:
|
|
@@ -320,7 +320,6 @@ Each file and why: [What the command writes](./splash-screen#files).
|
|
|
320
320
|
| --- | --- |
|
|
321
321
|
| Splash | The files of iOS or Android aren't made from the settings in `package.json`: changed by hand, made on one platform only, or by an older version of the command. Or the settings themselves are wrong, such as a logo that isn't there. |
|
|
322
322
|
| Splash logo | The logo's corners leave Android 12's circle. |
|
|
323
|
-
| Launch screen | Without settings: `Info.plist` has `UILaunchScreen` and no storyboard. iOS shows it at launch, but the navigation can only keep a storyboard on screen, so an empty window shows between it and the first screen. |
|
|
324
323
|
|
|
325
324
|
Each warning names its fix, such as `npx apex-navigation splash`; `--fix` leaves these to you. What the user sees, and a splash of your own after the native one: [Splash screen](./splash-screen).
|
|
326
325
|
|
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
|
|
|
@@ -215,9 +232,9 @@ A signed-in user sees Login for a moment, then the app.
|
|
|
215
232
|
|
|
216
233
|
(iOS) Between the launch screen and the first screen, the window is empty for a moment.
|
|
217
234
|
|
|
218
|
-
**Why:** the
|
|
235
|
+
**Why:** the app has no launch screen the navigation can copy: `Info.plist` names no storyboard and has no `UILaunchScreen` dictionary (or `UILaunchScreens` default), and no legacy launch images.
|
|
219
236
|
|
|
220
|
-
**Fix:** run `npx apex-navigation splash`, or name a storyboard in `UILaunchStoryboardName
|
|
237
|
+
**Fix:** run `npx apex-navigation splash`, or name a storyboard in `UILaunchStoryboardName`. More: [Make the native splash](../guides/splash-screen#command).
|
|
221
238
|
|
|
222
239
|
### Android brings the app back without its pushed screens
|
|
223
240
|
|
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
|
|
|
@@ -106,6 +106,17 @@ export const MapScreen = defineScreen({
|
|
|
106
106
|
|
|
107
107
|
The preview shows when back pops the screen with the default transition. It doesn't when back does something else first, or when the app takes it: a menu or a search that closes, a screen that holds back (`hardwareBackButton.popStackOnPress: false`, a [`BackHandler` listener](#back-handler), `usePreventLeave`), or a pop with an animation of its own (`animations.pop`). On Android 13 and older the screen pops when the swipe ends.
|
|
108
108
|
|
|
109
|
+
`animations.pop.preview: false` turns the preview off for a screen: the gesture shows nothing, and the screen pops when the user lets go, as on Android 13. It is read from the screen that goes, so the app's `options` turn it off everywhere:
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
defineScreen({
|
|
113
|
+
component: Checkout,
|
|
114
|
+
options: { animations: { pop: { preview: false } } },
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
iOS ignores it: the swipe back there is UIKit's.
|
|
119
|
+
|
|
109
120
|
### Back to the home screen
|
|
110
121
|
|
|
111
122
|
(Android) On the first screen of the app, with nothing left to go back to, the navigation leaves the back to the system. The system then plays its predictive back animation: as the user swipes, the app shrinks and the home screen shows behind it, and the user can still change their mind halfway. The system decides this as the swipe starts, so the navigation takes the back only while it has something to do with it: a screen to pop, a modal, a menu or a search to close, a tab to go to (`backBehavior`), or a [`BackHandler` listener](#back-handler) of the app.
|
|
@@ -122,6 +133,17 @@ Without it, back on the first screen still leaves the app, at once. See [What se
|
|
|
122
133
|
|
|
123
134
|
With the animation on, the system leaves the app itself: an `invokeDefaultOnBackPressed` of the app's `MainActivity` (such as `moveTaskToBack(true)`) isn't called on the first screen. Without it, and before Android 13, the back goes through the activity as before.
|
|
124
135
|
|
|
136
|
+
`hardwareBackButton.systemAnimation: false` keeps the back in the app on the first screen too, so the system plays no animation there: the gesture shows nothing, and on release the press goes to the [`BackHandler` listeners](#back-handler), then to the activity's own back, `invokeDefaultOnBackPressed` included. It is read from the screen back acts on; for the whole app, set it in the app's `options`:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
createNavigation({
|
|
140
|
+
// …
|
|
141
|
+
options: { hardwareBackButton: { systemAnimation: false } },
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
iOS ignores it.
|
|
146
|
+
|
|
125
147
|
### Handling back yourself: BackHandler
|
|
126
148
|
|
|
127
149
|
(Android) React Native's `BackHandler` works on every screen, as in any React Native app. A listener runs before the navigation's own back: returning `true` takes the press, `false` passes it on to the next listener, then to the navigation.
|