@apexnative/navigation 1.0.0-alpha.2 → 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 +31 -0
- package/android/CLAUDE.md +38 -9
- package/android/CORE.md +11 -4
- package/android/RECIPES.md +9 -3
- package/android/src/main/java/dev/apexnative/navigation/NavigationActivity.kt +29 -1
- 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/StatusBarOptions.kt +7 -3
- 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/arrangement/SecondaryScreenPresenter.kt +13 -5
- 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 +92 -19
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsPresenter.kt +25 -24
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ComponentPresenter.kt +28 -7
- 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/component/ScreenFrame.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalAnimations.kt +22 -24
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalCoordinator.kt +79 -13
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalRecord.kt +21 -6
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalSheet.kt +29 -12
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +22 -18
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/BackCallbackState.kt +4 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt +45 -6
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/RootCoordinator.kt +35 -9
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/overlay/OverlayCoordinator.kt +10 -8
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/Capabilities.kt +11 -4
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/ContainerBackground.kt +3 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/ParentController.kt +3 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuController.kt +5 -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 +72 -16
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/BackPreview.kt +287 -0
- 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 +164 -13
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackDisplay.kt +26 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackQueue.kt +136 -40
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackTransitions.kt +26 -8
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +107 -4
- 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 +131 -9
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/viewcontroller/ViewController.kt +75 -21
- package/android/src/main/java/dev/apexnative/navigation/views/animations/Curves.kt +29 -11
- package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/BottomTabs.kt +4 -2
- package/android/src/main/java/dev/apexnative/navigation/views/stack/ScreenEdge.kt +15 -0
- package/android/src/main/java/dev/apexnative/navigation/views/stack/StackLayout.kt +137 -1
- package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/Fab.kt +14 -10
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TitleBar.kt +8 -1
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TopBar.kt +26 -4
- 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 +12 -7
- package/docs-md/containers/modals.md +4 -2
- package/docs-md/containers/overlays.md +3 -1
- package/docs-md/containers/side-menu.md +4 -2
- package/docs-md/containers/tabs.md +2 -0
- package/docs-md/flows/forms-in-a-modal.md +1 -1
- package/docs-md/help/glossary.md +2 -2
- package/docs-md/help/troubleshooting.md +20 -3
- package/docs-md/index.json +12 -0
- package/docs-md/migration/from-react-native-navigation.md +8 -2
- package/docs-md/screen-ui/back-and-gestures.md +8 -2
- package/docs-md/screen-ui/system-ui.md +1 -1
- 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/hooks/useNavigation.js +8 -3
- package/lib/module/api/hooks/useNavigation.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 +15 -7
- 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/modals.d.ts +18 -13
- package/lib/typescript/interfaces/options/modals.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/system.d.ts +6 -3
- package/lib/typescript/interfaces/options/system.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 +9 -4
- 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 +15 -7
- package/src/interfaces/options/buttons.ts +13 -5
- package/src/interfaces/options/modals.ts +18 -13
- package/src/interfaces/options/system.ts +6 -3
- package/src/interfaces/options/topBar.ts +22 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.0.0-alpha.4](https://github.com/apexnative/navigation/compare/v1.0.0-alpha.3...v1.0.0-alpha.4) (2026-10-23)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **ci:** keep a closed React Native issue instead of opening a second one ([#37](https://github.com/apexnative/navigation/issues/37)) ([242030c](https://github.com/apexnative/navigation/commit/242030c8559a6fe186f44781ada85d09970c8318))
|
|
9
|
+
* **ios:** lay a waiting screen out in its place, so the large title comes in with it ([#41](https://github.com/apexnative/navigation/issues/41)) ([85bfaa8](https://github.com/apexnative/navigation/commit/85bfaa877d3775264c1ba1114942cf875f890f29))
|
|
10
|
+
* one native contract, options as layers and pushes that wait for the first render ([#39](https://github.com/apexnative/navigation/issues/39)) ([888f21e](https://github.com/apexnative/navigation/commit/888f21eb2b3e1aec413413bbdab9546a7a8f5a6b))
|
|
11
|
+
|
|
12
|
+
## [1.0.0-alpha.3](https://github.com/apexnative/navigation/compare/v1.0.0-alpha.2...v1.0.0-alpha.3) (2026-10-22)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* **android:** system stack transitions, an opaque default screen background and predictive back ([#26](https://github.com/apexnative/navigation/issues/26)) ([9f28969](https://github.com/apexnative/navigation/commit/9f28969a30a8902c1512cd2a24c3ddf5439f4642))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Bug Fixes
|
|
21
|
+
|
|
22
|
+
* **android:** center the back button and the bar buttons in the row ([#35](https://github.com/apexnative/navigation/issues/35)) ([c908971](https://github.com/apexnative/navigation/commit/c9089716ea4e3b3f4bcdc42b16e57134018d76c0))
|
|
23
|
+
* **android:** fade through on a tab switch, a new tab's first frame in place ([#29](https://github.com/apexnative/navigation/issues/29)) ([60d69b6](https://github.com/apexnative/navigation/commit/60d69b618a97c8f7da974fbdf6f427461960d672))
|
|
24
|
+
* **android:** let an overlay cover the status bar ([#27](https://github.com/apexnative/navigation/issues/27)) ([2632194](https://github.com/apexnative/navigation/commit/26321945cf0c457780b3eb7bba1f3d988dad4d96))
|
|
25
|
+
* **android:** match each modal presentation style to its Android equivalent ([#31](https://github.com/apexnative/navigation/issues/31)) ([54ab265](https://github.com/apexnative/navigation/commit/54ab2651f3139e869a2d99a43465b33bfb171414))
|
|
26
|
+
* **android:** move the top bar with its screen in transitions of their own ([#34](https://github.com/apexnative/navigation/issues/34)) ([4d12ee6](https://github.com/apexnative/navigation/commit/4d12ee6d6ec26a04e529bdef8b8111d20e5116aa))
|
|
27
|
+
* close the side menu as a pick in it changes the center ([#28](https://github.com/apexnative/navigation/issues/28)) ([c4b3e81](https://github.com/apexnative/navigation/commit/c4b3e81ba44d2edb6d93d3ddc906eca165d46e29))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
### Performance Improvements
|
|
31
|
+
|
|
32
|
+
* **android:** render the modals of a setRoot's layout together with its root ([#32](https://github.com/apexnative/navigation/issues/32)) ([cf3e1d9](https://github.com/apexnative/navigation/commit/cf3e1d98ff7c5d2a05aba6485203e6fe35a3423d))
|
|
33
|
+
|
|
3
34
|
## [1.0.0-alpha.2](https://github.com/apexnative/navigation/compare/v1.0.0-alpha.1...v1.0.0-alpha.2) (2026-10-22)
|
|
4
35
|
|
|
5
36
|
|
package/android/CLAUDE.md
CHANGED
|
@@ -21,8 +21,9 @@ top-level folder. Main thread only, unless a line says otherwise.
|
|
|
21
21
|
`OptionsReader`, `OptionsCache`, `DefaultOptions`. `params/` has the value types (`Bool`, `Text`, `Fraction`,
|
|
22
22
|
`Variant`, `ThemeColour`); `parsers/` reads them tolerantly from React Native data.
|
|
23
23
|
- `react/`: the way in and out: `NavigationTurboModule`, `CommandRunner`, `CommandAnswer`, `ErrorTexts`,
|
|
24
|
-
`OptionsCommands`, `
|
|
25
|
-
`ScreenRegions`, `ArrangementStates`, `Hinge`, `StartInfo`),
|
|
24
|
+
`OptionsCommands`, `PendingCalls` (a task behind the calls JavaScript made), `ReloadCoordinator`, `ReactGateway`,
|
|
25
|
+
`ReactView` (a React root), the stores (`ScreenInsets`, `ScreenRegions`, `ArrangementStates`, `Hinge`, `StartInfo`),
|
|
26
|
+
`LaunchLink`, `ScrollEvents`.
|
|
26
27
|
- `react/events/`: `AppearanceDispatcher`, `ScreenAppearance`, `EventEmitter`, `EventContract` (made by
|
|
27
28
|
`scripts/contract.mjs`), `NavigationStateReporter`, `ComponentType`.
|
|
28
29
|
- `viewcontrollers/viewcontroller/`: `ViewController` (the base of every node), `Presenter`, `IReactView` (what a
|
|
@@ -53,7 +54,8 @@ top-level folder. Main thread only, unless a line says otherwise.
|
|
|
53
54
|
- `slots/`, `safearea/`, `nativescreen/`: the views of `ANNSlot` and `ANNSafeAreaView`, the slot screen, the
|
|
54
55
|
native-screen context and calls.
|
|
55
56
|
- `splash/`, `customrow/`, `utils/`: the splash hold, theme and copy; the tabs' custom row options; helpers
|
|
56
|
-
(`UiThread`, `ImageLoader`, `MaterialTheme`, `TestIds`, `WindowFolds`,
|
|
57
|
+
(`UiThread`, `ImageLoader` with `OptionImages`, `ImageKey`, `ImageRequests`, `MaterialTheme`, `TestIds`, `WindowFolds`,
|
|
58
|
+
`DevelopmentLog`).
|
|
57
59
|
- `android/src/main/jni/`: `CMakeLists.txt` builds the codegen target with the C++ of `ios/cpp` (the slot's and the
|
|
58
60
|
safe area view's shadow nodes); `ApexNavigationSpec.h` replaces the generated header so autolinking sees their
|
|
59
61
|
descriptors.
|
|
@@ -73,8 +75,10 @@ top-level folder. Main thread only, unless a line says otherwise.
|
|
|
73
75
|
`ScreenFrame.kt` (bars' room, window insets, keyboard), `ScreenSafeArea.kt`, `ComponentPresenter.kt` (background),
|
|
74
76
|
`ScreenContentFactory.kt`, `ScreenFocus.kt`; `react/ReactView.kt`; `views/component/ComponentLayout.kt`.
|
|
75
77
|
- Stack (queue, commands, back): `viewcontrollers/stack/StackQueue.kt`, `StackCommands.kt`, `StackController.kt`,
|
|
76
|
-
`StackViews.kt`, `StackTransitions.kt`, `PreparedPushes.kt`; `views/stack/StackLayout.kt
|
|
77
|
-
`NavigationActivity.kt`, `viewcontrollers/navigator/BackCallbackState.kt
|
|
78
|
+
`StackViews.kt`, `StackTransitions.kt`, `PreparedPushes.kt`; `views/stack/StackLayout.kt`, `ScreenEdge.kt` (the edge
|
|
79
|
+
a moving screen extends); back: `NavigationActivity.kt`, `viewcontrollers/navigator/BackCallbackState.kt`; the
|
|
80
|
+
preview of a pop during the back gesture (Android 14+): `StackController.BackGesture`, `BackPreview.kt`,
|
|
81
|
+
`stackPoppedByBack` of the layouts.
|
|
78
82
|
- Stack FAB: `viewcontrollers/stack/fab/FabPresenter.kt`, `FabPlan.kt`, `FabPlacement.kt`; `views/stack/fab/`.
|
|
79
83
|
- Top bar (title, buttons, menus, large title): `viewcontrollers/stack/topbar/TopBarPresenter.kt`, `TopBarRoom.kt`,
|
|
80
84
|
`TopBarVisibility.kt`, `BarComponents.kt`, `ButtonPlaces.kt`, `LargeTitleCollapse.kt`, `BarRowTransition.kt`;
|
|
@@ -104,12 +108,14 @@ top-level folder. Main thread only, unless a line says otherwise.
|
|
|
104
108
|
`NavigationStateReporter.kt`; `react/CommandAnswer.kt`.
|
|
105
109
|
- Options (parsing, merging): `options/Options.kt`, `OptionsReader.kt`, `OptionsCache.kt`, `DefaultOptions.kt`,
|
|
106
110
|
`params/`, `parsers/`; `viewcontrollers/viewcontroller/ViewController.kt` (resolution).
|
|
107
|
-
- Error texts: `react/ErrorTexts.kt`, made by `scripts/contract.mjs` from
|
|
111
|
+
- Error texts and answers: `react/ErrorTexts.kt` and `react/CommandContract.kt`, made by `scripts/contract.mjs` from
|
|
112
|
+
`src/interfaces/contract/errors.json` and `commands.json`.
|
|
108
113
|
- Links: `react/LaunchLink.kt`, `NavigationActivity.kt` (`onNewIntent`); `react/StartInfo.kt`.
|
|
109
114
|
- Hinge and regions: `react/Hinge.kt`, `react/ScreenRegions.kt`, `react/ScreenInsets.kt`, `utils/WindowFolds.kt`.
|
|
110
115
|
- Native screens: `nativescreen/NativeScreenContext.kt`, `NativeScreenCalls.kt`, `NativeScreenCreator.kt`;
|
|
111
116
|
`viewcontrollers/externalcomponent/`.
|
|
112
|
-
- Slots: `slots/SlotHost.kt`, `SlotView.kt`, `SlotScreenView.kt`, `SlotContainers.kt
|
|
117
|
+
- Slots: `slots/SlotHost.kt`, `SlotView.kt`, `SlotScreenView.kt`, `SlotContainers.kt` (containers and slot views by
|
|
118
|
+
slot id: a change goes straight to the views of its id, `SlotViews.kt`), `SlotViewManager.kt`.
|
|
113
119
|
- Safe area: `safearea/SafeAreaView.kt`, `SafeAreaViewManager.kt`; `viewcontrollers/component/ScreenReadiness.kt`;
|
|
114
120
|
`react/ScreenInsets.kt`.
|
|
115
121
|
- The C++ of `ANNSlot`, `ANNSafeAreaView`: `android/src/main/jni/CMakeLists.txt`, `ApexNavigationSpec.h`; sources in
|
|
@@ -123,7 +129,11 @@ top-level folder. Main thread only, unless a line says otherwise.
|
|
|
123
129
|
- **Module thread.** The `check` of every command with a promise: `LayoutReader` and, through it, `OptionsReader` (also
|
|
124
130
|
for `setDefaultOptions`, `mergeOptions` and the options of `dismissModal`, in the runner's `prepare` step);
|
|
125
131
|
`ReadableJson` for native-screen props. The registry of native screens is read under its lock. Images are not loaded
|
|
126
|
-
here;
|
|
132
|
+
here; they load when the options apply, through the one cache of `utils/OptionImages` (by `ImageKey`): at once on
|
|
133
|
+
main where the first frame needs them (`ImageLoader`: the top bar's buttons, menus, back button and search
|
|
134
|
+
suggestions; a tab's first icon; a new FAB), on its background thread for a file or a drawable of the app where the
|
|
135
|
+
view takes them when ready (`ImageRequests`: a changed tab icon or one that didn't load, a FAB's changed icon, the
|
|
136
|
+
icon of a FAB hidden with `visible: false`, the FAB menu's actions).
|
|
127
137
|
- **Main thread.** Everything else: the command body, nodes, views, events, the stores' writes. Any thread may read
|
|
128
138
|
`ReloadCoordinator.generation`.
|
|
129
139
|
|
|
@@ -153,7 +163,9 @@ top-level folder. Main thread only, unless a line says otherwise.
|
|
|
153
163
|
skips the table that decides interrupts and answers.
|
|
154
164
|
- Every event of an appearance goes through `AppearanceDispatcher`. A container with its own loop of
|
|
155
165
|
`onViewWillAppear` / `onViewDidAppear` / `onViewDisappear` sends the events in the wrong order.
|
|
156
|
-
- Never change a resolved options object: it is cached and shared. Copy it, or write through `changeOptions
|
|
166
|
+
- Never change a resolved options object: it is cached and shared. Copy it, or write through `changeOptions`; a
|
|
167
|
+
user's choice goes through `changeChoices` (the user layer), never into own options, or the next `mergeOptions`
|
|
168
|
+
wipes it.
|
|
157
169
|
- A new option field that is not in `mergeWith`, `mergeWithDefault`, `copy` or the fixture is dropped silently somewhere
|
|
158
170
|
in the chain; `WholeTreeTest` exists to catch it.
|
|
159
171
|
- A new wait is a new `RenderWait` value, not a new timer. A wait that survives a destroy or a supersede hangs a promise;
|
|
@@ -167,6 +179,9 @@ top-level folder. Main thread only, unless a line says otherwise.
|
|
|
167
179
|
`OverlayCoordinator.guarded`), which rejects the open answer and ends the record, or an exception ends the app and
|
|
168
180
|
the command never answers.
|
|
169
181
|
- A node makes its view in `createView`, never in its constructor.
|
|
182
|
+
- Whether a node keeps the status bar's room (its margin, its background band) goes through
|
|
183
|
+
`StatusBarOptions.isHiddenOrDrawBehind(node.isInOverlay)`: an overlay covers the whole window, and a rule that reads
|
|
184
|
+
`drawBehind` alone puts a light band over an overlay's dimmed backdrop.
|
|
170
185
|
- Only `SystemUiWindow` writes to the window. A presenter that changes a bar option calls `SystemUiOwner.invalidate`.
|
|
171
186
|
- The core names a module container only in `LayoutFactory`; elsewhere use a capability or `visibleChildren()`.
|
|
172
187
|
- A hidden stack keeps a `mergeOptions` per entry; applying it for the wrong entry paints the wrong screen's bar.
|
|
@@ -177,6 +192,20 @@ top-level folder. Main thread only, unless a line says otherwise.
|
|
|
177
192
|
offset that `ScrollEventListener.lastOffset` keeps, never to 0, or it comes back expanded over a scrolled list.
|
|
178
193
|
- A view's place in the window while a modal slides or a sheet is dragged comes from layout (`RestPlace`), never from
|
|
179
194
|
`getLocationInWindow`, which counts the translations of the animation.
|
|
195
|
+
- The bar measures to its children: a picture laid into it for a transition that is taller than the bar the incoming
|
|
196
|
+
screen gives holds the bar at that height to the end (the row's picture of a pop from a large title). What doesn't
|
|
197
|
+
stay in the bar rides with the leaving screen (`TopBarPresenter.pictureUnderRow`).
|
|
198
|
+
- A `Toolbar` centers its navigation button and its menu in `min(minimumHeight, height)` (`buttonGravity` is top):
|
|
199
|
+
the row's toolbars take the row's height as their minimum (`TitleBar`); 0 pins the buttons to the row's top.
|
|
200
|
+
- A screen's overlay is clipped to the screen, and a screen without a large title is laid out under the bar (its `top`
|
|
201
|
+
is the bar's height): a picture of the bar in its overlay doesn't show. The bar's picture in a transition of the
|
|
202
|
+
stack's own is drawn by the stack's view with the screen's transform (`StackLayout.barTravels`).
|
|
203
|
+
- A React screen without `layout.backgroundColor` is opaque with the theme's `android:colorBackground`
|
|
204
|
+
(`ComponentPresenter`): a push keeps the covered screen under the new one until it ends, and a transparent screen
|
|
205
|
+
shows it. Only an explicit `'transparent'` / `null` (overlays, see-through modals) lets it through.
|
|
206
|
+
- The back gesture's preview shows the screen under the top outside any operation of the queue: it may only reveal
|
|
207
|
+
it and put it back, and its release goes through `StackQueue.cut(gesture = true)`. A command that comes meanwhile
|
|
208
|
+
stops it through `StackDisplay.stop`, like any transition it takes over.
|
|
180
209
|
- Without edge to edge the window resizes for the keyboard by the manifest's soft input mode, so
|
|
181
210
|
`layout.adjustResize: false` goes through the system UI owner's keyboard group (`SystemUiWindow.setKeyboardCovers`),
|
|
182
211
|
not through the screen's padding.
|
package/android/CORE.md
CHANGED
|
@@ -72,8 +72,13 @@ react/events/AppearanceDispatcher ─► NavigationStateReporter ─► EventEmi
|
|
|
72
72
|
5. **Make and join.** `LayoutFactory.create` makes Profile (a `ComponentViewController`, registered in the
|
|
73
73
|
`ChildControllersRegistry`); `StackController.join` adds it; `StackQueue.push` accepts it as an entry.
|
|
74
74
|
6. **`StackQueue`** is the only way to change a stack's children. `drain` starts the operation when nothing blocks it.
|
|
75
|
-
`startPush` decides the wait:
|
|
76
|
-
|
|
75
|
+
`startPush` decides the wait: a prepared push; `waitForRender: true` (as long as it takes); unset, for a screen with
|
|
76
|
+
React content, Profile's first mount, 0.5 s at most (`PUSH_WAIT_MILLIS`); none for `false`, a native screen or a
|
|
77
|
+
stack off screen. A wait is a `RenderWait` on Profile. Its mount reaches the push through the queue's `afterCalls`
|
|
78
|
+
(`PendingCalls`: once through the JavaScript thread, the module's thread and the main thread, the way of a
|
|
79
|
+
`mergeOptions`), so the `mergeOptions` of the commit that mounted it (`useScreenOptions`) has applied when the
|
|
80
|
+
transition starts, content mounted before the push too; the prepared push starts the same way after its slot
|
|
81
|
+
mounted. Unset, the targets of elements take what is left of the 0.5 s.
|
|
77
82
|
7. **Begin.** `beginPush` raises Profile's view (`StackViews`), moves the bar and the tab bar
|
|
78
83
|
(`StackQueue.Host.barTransition`, `tabBarTransition`), calls `StackController` through `currentChildChanged`, then
|
|
79
84
|
`AppearanceDispatcher.beginTransition`: **`ComponentWillAppear`** of Profile. The transition runs
|
|
@@ -82,7 +87,7 @@ react/events/AppearanceDispatcher ─► NavigationStateReporter ─► EventEmi
|
|
|
82
87
|
**`ComponentDidAppear`** of Profile, then `reportState` (**`NavigationStateChanged`**, only if it changed), then the
|
|
83
88
|
transition's own events (none for a push), then `CommandAnswer.resolve` with Profile's id.
|
|
84
89
|
9. **Answer.** `resolve` sends the state once more if it changed (`AnswerEvents.beforeAnswer`), resolves the promise,
|
|
85
|
-
then sends **`CommandCompleted`** (`
|
|
90
|
+
then sends **`CommandCompleted`** (`CommandContract.COMPLETED`). After the task, `BackCallbackState.update` recomputes what
|
|
86
91
|
back does.
|
|
87
92
|
|
|
88
93
|
The events go from the main thread, synchronously, through `EventEmitter.current()`; the promise is answered once.
|
|
@@ -91,7 +96,9 @@ The events go from the main thread, synchronously, through `EventEmitter.current
|
|
|
91
96
|
current root in the root layer, transparent (`RootLayerViews.place`), so its React roots start while the old root keeps
|
|
92
97
|
the touches. It waits (`RenderWait`, by `animations.setRoot.enter.waitForRender`), then fades through or plays its own
|
|
93
98
|
animation, the old root is destroyed with its `ComponentDidDisappear`, `NativeScreenReleased` and `SlotDestroyed`, and
|
|
94
|
-
the end steps and answer are the same. The layout's modals and overlays show after it, one by one, before the answer
|
|
99
|
+
the end steps and answer are the same. The layout's modals and overlays show after it, one by one, before the answer;
|
|
100
|
+
its modals are made with the root and render unseen in the modals layer, held (`ModalCoordinator.hold`), so each
|
|
101
|
+
shows in its turn without a wait of its own.
|
|
95
102
|
The last `setRoot` wins: an older one is rejected (`ErrorTexts.setRootReplaced`) or finished at once.
|
|
96
103
|
- **`showModal`** (`Navigator.showModal`, `ModalCoordinator.show`). One `ModalRecord` per modal; it joins the modals
|
|
97
104
|
layer unseen (`ModalViews`), waits with its own `RenderWait`, then slides in (or opens as a `ModalSheet`). It shows
|
package/android/RECIPES.md
CHANGED
|
@@ -14,7 +14,11 @@ The steps for adding an option and for adding a command to the Android core; rea
|
|
|
14
14
|
(a repaint after a light/dark switch or `setDefaultOptions`); a system-bar option goes through `SystemUiRules`.
|
|
15
15
|
Read colors with `ThemeColour.get` at apply so they follow the theme.
|
|
16
16
|
4. The cache needs nothing: own options are written only through `ViewController.options`, `changeOptions` and
|
|
17
|
-
`mergeOptions
|
|
17
|
+
`mergeOptions` (`mergeOwnOptions`), defaults only through `DefaultOptions.set`. Never change a resolved result.
|
|
18
|
+
What the user chooses in the native UI (a menu's check, a search scope) is not written into own options: it goes
|
|
19
|
+
into the node's user layer through `ViewController.changeChoices` (`options/UserChoices.kt`), which applies it by
|
|
20
|
+
id over the resolved options and lets it go when a merge changes that field. A new kind of choice is a field of
|
|
21
|
+
`UserChoices` with its `applyTo` and `yieldTo`; `changeOptions` stays for the library's own bookkeeping of a merge.
|
|
18
22
|
5. Tests: add the field to `OptionsFixture.full()`. `WholeTreeTest` walks the tree by reflection and fails when a field
|
|
19
23
|
is not read, copied, merged or defaulted. Add cases to `ReadersTest`, `MergeRuleTest` and the area's rules test.
|
|
20
24
|
6. The docs page and a Showcase control; one meaning on both platforms, and where Android can't do it the JSDoc says so
|
|
@@ -31,8 +35,10 @@ The steps for adding an option and for adding a command to the Android core; rea
|
|
|
31
35
|
4. New layout fields are read and checked in `LayoutReader`; checks against what shows are in `LayoutChecks` or in the
|
|
32
36
|
command, before any node is made. Every rejection text is a function in `ErrorTexts` (same words as iOS),
|
|
33
37
|
made by `node scripts/contract.mjs` from `src/interfaces/contract/errors.json`: a new text goes there.
|
|
34
|
-
5. The answer goes through `CommandAnswer`:
|
|
35
|
-
(
|
|
38
|
+
5. The answer goes through `CommandAnswer`: give the command its entry in `src/interfaces/contract/commands.json`
|
|
39
|
+
(what it resolves with, `state` when it may change what shows, `completed` when it shows or dismisses) and run
|
|
40
|
+
`node scripts/contract.mjs`, which makes `CommandContract` (then the state, the promise and `CommandCompleted` follow
|
|
41
|
+
in that order). A transition ends through
|
|
36
42
|
`AppearanceDispatcher.endTransition`.
|
|
37
43
|
6. Tests: `CommandRunnerTest`, `CommandAnswerTest`, `ErrorTextsTest`, `LayoutReaderTest`; a stack operation also needs a
|
|
38
44
|
case in the queue tests (`StackQueueHarness`).
|
|
@@ -11,6 +11,7 @@ import android.view.View
|
|
|
11
11
|
import android.view.ViewGroup
|
|
12
12
|
import android.view.ViewTreeObserver
|
|
13
13
|
import android.widget.EditText
|
|
14
|
+
import androidx.activity.BackEventCompat
|
|
14
15
|
import androidx.activity.OnBackPressedCallback
|
|
15
16
|
import androidx.activity.SystemBarStyle
|
|
16
17
|
import androidx.activity.enableEdgeToEdge as enableActivityEdgeToEdge
|
|
@@ -37,6 +38,7 @@ import dev.apexnative.navigation.utils.MaterialTheme
|
|
|
37
38
|
import dev.apexnative.navigation.viewcontrollers.navigator.BackCallbackState
|
|
38
39
|
import dev.apexnative.navigation.viewcontrollers.navigator.Navigator
|
|
39
40
|
import dev.apexnative.navigation.viewcontrollers.navigator.Navigators
|
|
41
|
+
import dev.apexnative.navigation.viewcontrollers.stack.StackController
|
|
40
42
|
|
|
41
43
|
/**
|
|
42
44
|
* The activity of an app with the navigation: its content holds, bottom to top, the copy of the splash and the layers
|
|
@@ -67,10 +69,36 @@ open class NavigationActivity : AppCompatActivity(), DefaultHardwareBackBtnHandl
|
|
|
67
69
|
private val reloadBroadcast = ReloadBroadcast(this) { reloadFromDeveloperTools() }
|
|
68
70
|
|
|
69
71
|
// Enabled while back has something to do in the app (BackCallbackState); otherwise the system does its own back.
|
|
72
|
+
// From Android 14, where the app hears the progress of the back gesture, a gesture that would pop a stack with the
|
|
73
|
+
// default transition previews the pop as the system previews back between activities (`StackController
|
|
74
|
+
// .BackGesture`); JavaScript's listeners hear back first, so with them there is no preview.
|
|
70
75
|
private val backCallback = object : OnBackPressedCallback(false) {
|
|
71
|
-
override fun
|
|
76
|
+
override fun handleOnBackStarted(backEvent: BackEventCompat) {
|
|
77
|
+
backGesture = null
|
|
78
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE || BackCallbackState.hasJsListeners) return
|
|
79
|
+
val stack = navigator?.stackPoppedByBack() ?: return
|
|
80
|
+
backGesture = stack.startBackGesture(backEvent.swipeEdge == BackEventCompat.EDGE_LEFT, backEvent.touchY)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
override fun handleOnBackProgressed(backEvent: BackEventCompat) {
|
|
84
|
+
backGesture?.progress(backEvent.progress, backEvent.touchY)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
override fun handleOnBackCancelled() {
|
|
88
|
+
backGesture?.cancel()
|
|
89
|
+
backGesture = null
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
override fun handleOnBackPressed() {
|
|
93
|
+
val gesture = backGesture
|
|
94
|
+
backGesture = null
|
|
95
|
+
if (gesture?.commit() == true) BackCallbackState.update() else passBack()
|
|
96
|
+
}
|
|
72
97
|
}
|
|
73
98
|
|
|
99
|
+
// The back gesture that previews a pop now; none.
|
|
100
|
+
private var backGesture: StackController.BackGesture? = null
|
|
101
|
+
|
|
74
102
|
// Before each frame of the window: what back does may have changed, and the system decides at the start of a
|
|
75
103
|
// gesture whether the app takes it.
|
|
76
104
|
private val backBeforeFrame = ViewTreeObserver.OnPreDrawListener {
|
|
@@ -3,32 +3,49 @@ package dev.apexnative.navigation
|
|
|
3
3
|
import android.app.Application
|
|
4
4
|
import android.util.Log
|
|
5
5
|
import com.facebook.react.ReactApplication
|
|
6
|
-
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint
|
|
7
|
-
import com.facebook.react.soloader.OpenSourceMergedSoMapping
|
|
8
6
|
import com.facebook.soloader.SoLoader
|
|
9
7
|
import dev.apexnative.navigation.nativescreen.NativeScreenCreator
|
|
10
8
|
import dev.apexnative.navigation.react.ReactGateway
|
|
11
9
|
import dev.apexnative.navigation.utils.NavigationLog
|
|
12
10
|
|
|
13
11
|
/**
|
|
14
|
-
* The application class of an app with the navigation: it
|
|
15
|
-
*
|
|
16
|
-
*
|
|
12
|
+
* The application class of an app with the navigation: it keeps one React host for every screen ([reactGateway]) and
|
|
13
|
+
* the native screens by name. The app gives the host (`reactHost`) and loads React Native in its `onCreate` as the
|
|
14
|
+
* React Native template does, so the load follows the app's Gradle settings (the New Architecture, edge-to-edge):
|
|
15
|
+
*
|
|
16
|
+
* ```kotlin
|
|
17
|
+
* override fun onCreate() {
|
|
18
|
+
* super.onCreate()
|
|
19
|
+
* loadReactNative(this)
|
|
20
|
+
* registerNativeScreen("Profile") { activity, context -> ProfileView(activity, context) }
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* The gateway, and with it the host, is made the first time something needs it (the first activity), after the app's
|
|
25
|
+
* `onCreate` loaded React Native.
|
|
17
26
|
*/
|
|
18
27
|
abstract class NavigationApplication : Application(), ReactApplication {
|
|
19
28
|
// Written on the main thread as the app registers, read on the module thread by the layout checks: under its lock.
|
|
20
29
|
private val nativeScreens = LinkedHashMap<String, NativeScreenCreator>()
|
|
21
30
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
31
|
+
// Made once, by the first activity ([reactGateway]); [reactGatewayOrNull] only reads it.
|
|
32
|
+
private val gateway: Lazy<ReactGateway> = lazy {
|
|
33
|
+
check(SoLoader.isInitialized()) {
|
|
34
|
+
"NavigationApplication needs React Native loaded first: call loadReactNative(this) in onCreate, " +
|
|
35
|
+
"as the React Native template does"
|
|
36
|
+
}
|
|
37
|
+
createReactGateway()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The app's React host as the navigation runs it, made when it is first asked for (the first activity's
|
|
42
|
+
* `onCreate`). React Native must be loaded by then: the app calls `loadReactNative(this)` in its `onCreate`.
|
|
43
|
+
*/
|
|
44
|
+
val reactGateway: ReactGateway by gateway
|
|
25
45
|
|
|
26
46
|
override fun onCreate() {
|
|
27
47
|
super.onCreate()
|
|
28
48
|
instance = this
|
|
29
|
-
SoLoader.init(this, OpenSourceMergedSoMapping)
|
|
30
|
-
DefaultNewArchitectureEntryPoint.load()
|
|
31
|
-
reactGateway = createReactGateway()
|
|
32
49
|
registerGeneratedScreens()
|
|
33
50
|
}
|
|
34
51
|
|
|
@@ -57,9 +74,13 @@ abstract class NavigationApplication : Application(), ReactApplication {
|
|
|
57
74
|
internal fun isNativeScreenRegistered(name: String): Boolean =
|
|
58
75
|
synchronized(nativeScreens) { nativeScreens.containsKey(name) }
|
|
59
76
|
|
|
60
|
-
/**
|
|
77
|
+
/**
|
|
78
|
+
* [reactGateway] once the first activity made it; none before. It never makes it: its callers (the log, the
|
|
79
|
+
* events, a React root) may run first, also off the main thread, and the app's `reactHost` isn't theirs to make.
|
|
80
|
+
* Any thread.
|
|
81
|
+
*/
|
|
61
82
|
internal val reactGatewayOrNull: ReactGateway?
|
|
62
|
-
get() = if (
|
|
83
|
+
get() = if (gateway.isInitialized()) gateway.value else null
|
|
63
84
|
|
|
64
85
|
// The list of `npx apex-navigation codegen`, found by its name: an app without native screens has none.
|
|
65
86
|
private fun registerGeneratedScreens() {
|
|
@@ -47,5 +47,9 @@ internal object LayoutChecks {
|
|
|
47
47
|
*/
|
|
48
48
|
@JvmStatic
|
|
49
49
|
fun checkStackChild(node: LayoutNode, stackId: String): String? =
|
|
50
|
-
if (node
|
|
50
|
+
if (isStackChild(node)) null else ErrorTexts.stackTakesScreens(stackId, node.type.jsName)
|
|
51
|
+
|
|
52
|
+
/** Whether a stack takes [node]: a screen, a native screen or a pair, not a container. */
|
|
53
|
+
@JvmStatic
|
|
54
|
+
fun isStackChild(node: LayoutNode): Boolean = node.type in stackChildren
|
|
51
55
|
}
|
|
@@ -82,10 +82,14 @@ internal data class LayoutNode(
|
|
|
82
82
|
private val COLUMN_NAMES = setOf(PRIMARY, SECONDARY, SUPPLEMENTARY, COMPACT, INSPECTOR)
|
|
83
83
|
private val COLUMNS_WITHOUT_NAMES = listOf(PRIMARY, SECONDARY)
|
|
84
84
|
|
|
85
|
-
/**
|
|
85
|
+
/**
|
|
86
|
+
* The column of the child at [index] of a split view with [columns]; none for a child without one, and none
|
|
87
|
+
* for a repeat of a name an earlier child has (the first takes the column), as on iOS.
|
|
88
|
+
*/
|
|
86
89
|
internal fun columnName(columns: List<String>, index: Int): String? {
|
|
87
90
|
val names = columns.ifEmpty { COLUMNS_WITHOUT_NAMES }
|
|
88
|
-
|
|
91
|
+
val name = names.getOrNull(index)?.takeIf { it in COLUMN_NAMES } ?: return null
|
|
92
|
+
return name.takeIf { names.indexOf(it) == index }
|
|
89
93
|
}
|
|
90
94
|
}
|
|
91
95
|
}
|
|
@@ -44,8 +44,8 @@ class NativeScreenContext @RestrictTo(RestrictTo.Scope.LIBRARY) constructor(
|
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
46
|
* Runs a command of the navigation from this screen in JS, as `useNavigation()` of a React screen does: push, pop,
|
|
47
|
-
* popTo, popToRoot, showInColumn, showModal, dismiss, showOverlay,
|
|
48
|
-
*
|
|
47
|
+
* popTo, popToRoot, showInColumn, showModal, dismiss, showOverlay, setOptions, selectTab, setTabBadge.
|
|
48
|
+
* [arguments] are those of the JS command; [result] gets what it resolved with.
|
|
49
49
|
*/
|
|
50
50
|
@JvmOverloads
|
|
51
51
|
fun navigate(command: String, arguments: JSONArray = JSONArray(), result: ((Result<Any?>) -> Unit)? = null) =
|
|
@@ -8,8 +8,8 @@ import dev.apexnative.navigation.options.parsers.IconParser
|
|
|
8
8
|
* A menu of the bar: `topBar.overflowMenu`, or the `menu` of a button. Its items, and submenus among them; with
|
|
9
9
|
* `singleSelection` one item of a level has a checkmark at a time.
|
|
10
10
|
*
|
|
11
|
-
* Copied options share it, so
|
|
12
|
-
*
|
|
11
|
+
* Copied options share it, so nobody changes a menu in place but in a copy of its own: the check a user's pick moves
|
|
12
|
+
* lives in the screen's user layer (`UserChoices`), which goes into a copy as the screen's options resolve.
|
|
13
13
|
*/
|
|
14
14
|
class ButtonMenuOptions(
|
|
15
15
|
@JvmField val singleSelection: Boolean,
|
|
@@ -197,8 +197,8 @@ class ButtonOptions(@JvmField var id: Text = NullText()) {
|
|
|
197
197
|
|
|
198
198
|
/**
|
|
199
199
|
* A copy of the menu with new items and submenus and the same values. A menu taken from the default options is copied
|
|
200
|
-
* first, so that
|
|
201
|
-
* own and resolved options share the menu
|
|
200
|
+
* first, so that nothing written into a resolved result reaches [DefaultOptions.current]; the user layer writes its
|
|
201
|
+
* checks into a copy too (`UserChoices`), and copies between a node's own and resolved options share the menu.
|
|
202
202
|
*/
|
|
203
203
|
internal fun ButtonMenuOptions.copyMenu(): ButtonMenuOptions =
|
|
204
204
|
ButtonMenuOptions(singleSelection, items.map(::copyElement), testID)
|
|
@@ -3,9 +3,10 @@ package dev.apexnative.navigation.options
|
|
|
3
3
|
/**
|
|
4
4
|
* The one point where the options of the app change: a process-wide generation that grows at each change. A node's
|
|
5
5
|
* resolved options are kept while the generation stays and the nodes they were built along are the same
|
|
6
|
-
* (`ViewController.resolveCurrentOptions`).
|
|
7
|
-
* (`ViewController.options`, and `changeOptions` through it)
|
|
8
|
-
* (
|
|
6
|
+
* (`ViewController.resolveCurrentOptions`). Three places call [optionsChanged]: the write of a node's own options
|
|
7
|
+
* (`ViewController.options`, and `changeOptions` and `mergeOwnOptions` through it), the write of a node's user layer
|
|
8
|
+
* (`ViewController.changeChoices`, and a merge that takes a choice away) and the store of new default options
|
|
9
|
+
* ([DefaultOptions.set]). Nothing else changes a node's own options, its user layer or the defaults. Main thread only.
|
|
9
10
|
*/
|
|
10
11
|
object OptionsCache {
|
|
11
12
|
/** The generation of the options; it only grows. */
|
|
@@ -13,7 +14,10 @@ object OptionsCache {
|
|
|
13
14
|
var generation: Long = 0
|
|
14
15
|
private set
|
|
15
16
|
|
|
16
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* A node's own options, its user layer or the default options changed: every resolved result built before is
|
|
19
|
+
* stale.
|
|
20
|
+
*/
|
|
17
21
|
@JvmStatic
|
|
18
22
|
fun optionsChanged() {
|
|
19
23
|
generation++
|
|
@@ -22,9 +22,13 @@ class StatusBarOptions {
|
|
|
22
22
|
@JvmField var hideWithTopBar: Bool = NullBool()
|
|
23
23
|
@JvmField var blur: Bool = NullBool()
|
|
24
24
|
|
|
25
|
-
/**
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
/**
|
|
26
|
+
* The status bar is hidden, or the screen is drawn under it: the screen keeps no room for it. Without `drawBehind`, a
|
|
27
|
+
* screen or a layout in an overlay ([inOverlay]) is drawn under it: an overlay covers the whole window, its dimmed
|
|
28
|
+
* backdrop the status bar too.
|
|
29
|
+
*/
|
|
30
|
+
fun isHiddenOrDrawBehind(inOverlay: Boolean): Boolean =
|
|
31
|
+
visible.isFalse || if (drawBehind.hasValue()) drawBehind.isTrue else inOverlay
|
|
28
32
|
|
|
29
33
|
fun hasValue(): Boolean = visible.hasValue() || style.hasValue() || backgroundColor.hasValue() ||
|
|
30
34
|
drawBehind.hasValue() || translucent.hasValue() || animate.hasValue() || hideWithTopBar.hasValue() ||
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
package dev.apexnative.navigation.options
|
|
2
|
+
|
|
3
|
+
import dev.apexnative.navigation.options.params.Text
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The user layer of a node: what the user chose in the native UI, kept apart from the node's own options so that the
|
|
7
|
+
* app's next `mergeOptions` or `setDefaultOptions` doesn't wipe it. The choices are the check of a single-selection
|
|
8
|
+
* menu (a bar button's `menu`, by the button's id; `topBar.overflowMenu`) and the search scope
|
|
9
|
+
* (`topBar.searchBar.selectedScope`).
|
|
10
|
+
*
|
|
11
|
+
* It goes over everything else as the node's options resolve (defaults < parents < own < user), by identity rather
|
|
12
|
+
* than as a tree merged over: a menu's check by the button's id and the item's id, the scope by its id. A choice whose
|
|
13
|
+
* button, item or scope is gone is not applied, and leaves at the node's next merge; a merge that gives a chosen field
|
|
14
|
+
* a value other than the one the app had there before takes the choice away ([yieldTo]). `setDefaultOptions` never
|
|
15
|
+
* clears it. The node owns it and writes it through `ViewController.changeChoices`, which moves [OptionsCache]. Main
|
|
16
|
+
* thread only.
|
|
17
|
+
*/
|
|
18
|
+
internal class UserChoices {
|
|
19
|
+
// The menus' checks: per menu (a button's id, OVERFLOW for `overflowMenu`) the item picked in each of its levels,
|
|
20
|
+
// in the order of the picks.
|
|
21
|
+
private val checks = LinkedHashMap<String, MutableList<String>>()
|
|
22
|
+
private var scope: String? = null
|
|
23
|
+
|
|
24
|
+
/** The user chose nothing that stays. */
|
|
25
|
+
val isEmpty: Boolean
|
|
26
|
+
get() = checks.isEmpty() && scope == null
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The user picked [itemId] in [menu], the menu of the button [buttonId] or `overflowMenu` (null), as it showed: it
|
|
30
|
+
* takes the place of an earlier pick in the same level.
|
|
31
|
+
*/
|
|
32
|
+
fun check(buttonId: String?, menu: ButtonMenuOptions, itemId: String) {
|
|
33
|
+
val picks = checks.getOrPut(keyOf(buttonId)) { mutableListOf() }
|
|
34
|
+
val level = levelItems(menu.items, itemId)?.map { it.id }.orEmpty()
|
|
35
|
+
picks.removeAll { it == itemId || it in level }
|
|
36
|
+
picks += itemId
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The user picked the search scope [scopeId]. */
|
|
40
|
+
fun pickScope(scopeId: String) {
|
|
41
|
+
scope = scopeId
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Writes the choices into [resolved], a result of resolution nobody else holds yet; the menus it changes are copied
|
|
46
|
+
* first, as a resolved result shares its menus with the layers they come from. Returns [resolved].
|
|
47
|
+
*/
|
|
48
|
+
fun applyTo(resolved: Options): Options {
|
|
49
|
+
if (isEmpty) return resolved
|
|
50
|
+
val topBar = resolved.topBar
|
|
51
|
+
for ((key, picks) in checks) {
|
|
52
|
+
if (key == OVERFLOW) {
|
|
53
|
+
val menu = topBar.overflowMenu ?: continue
|
|
54
|
+
topBar.overflowMenu = menu.copyMenu().also { copy -> picks.forEach { mark(copy, it) } }
|
|
55
|
+
continue
|
|
56
|
+
}
|
|
57
|
+
val button = buttonIn(topBar.buttons, key) ?: continue
|
|
58
|
+
val menu = button.menu ?: continue
|
|
59
|
+
button.menu = menu.copyMenu().also { copy -> picks.forEach { mark(copy, it) } }
|
|
60
|
+
}
|
|
61
|
+
scope?.let { picked ->
|
|
62
|
+
val searchBar = topBar.searchBar
|
|
63
|
+
if (searchBar.scopes?.any { it.id == picked } == true) searchBar.selectedScope = Text(picked)
|
|
64
|
+
}
|
|
65
|
+
return resolved
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The app merged [delta] into the node. A choice leaves when what it was made in is gone from [after] (the node's
|
|
70
|
+
* options as they resolve after the merge, without this layer), or when [delta] gives its field a value other than
|
|
71
|
+
* the one the node's own options had before the merge ([own]; where they had none, the app's resolution before the
|
|
72
|
+
* merge, [before]). The same value sent again (a re-render) keeps the choice. Returns whether a choice left.
|
|
73
|
+
*/
|
|
74
|
+
fun yieldTo(delta: Options, own: Options, before: Options, after: Options): Boolean {
|
|
75
|
+
var changed = false
|
|
76
|
+
val entries = checks.entries.iterator()
|
|
77
|
+
while (entries.hasNext()) {
|
|
78
|
+
val (key, picks) = entries.next()
|
|
79
|
+
val now = menuIn(after, key)
|
|
80
|
+
val given = menuIn(delta, key)
|
|
81
|
+
val was = menuIn(own, key) ?: menuIn(before, key)
|
|
82
|
+
val left = picks.removeAll { itemId ->
|
|
83
|
+
val gone = now == null || levelItems(now.items, itemId) == null
|
|
84
|
+
gone || (given != null && checkedInLevel(given, itemId) != was?.let { checkedInLevel(it, itemId) })
|
|
85
|
+
}
|
|
86
|
+
if (left) changed = true
|
|
87
|
+
if (picks.isEmpty()) entries.remove()
|
|
88
|
+
}
|
|
89
|
+
scope?.let { picked ->
|
|
90
|
+
val given = delta.topBar.searchBar.selectedScope
|
|
91
|
+
val ownScope = own.topBar.searchBar.selectedScope
|
|
92
|
+
val was = (if (ownScope.hasValue()) ownScope else before.topBar.searchBar.selectedScope).get(null)
|
|
93
|
+
val changedByApp = given.hasValue() && given.get(null) != was
|
|
94
|
+
val gone = after.topBar.searchBar.scopes?.any { it.id == picked } != true
|
|
95
|
+
if (changedByApp || gone) {
|
|
96
|
+
scope = null
|
|
97
|
+
changed = true
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return changed
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
companion object {
|
|
104
|
+
// The key of `overflowMenu`, which has no button; no placed button has an empty id.
|
|
105
|
+
private const val OVERFLOW = ""
|
|
106
|
+
|
|
107
|
+
private fun keyOf(buttonId: String?): String = buttonId ?: OVERFLOW
|
|
108
|
+
|
|
109
|
+
/** Whether picking [itemId] in [menu] moves a check: its level has `singleSelection`. */
|
|
110
|
+
fun checks(menu: ButtonMenuOptions, itemId: String): Boolean =
|
|
111
|
+
levelOf(menu.items, menu.singleSelection, itemId) == true
|
|
112
|
+
|
|
113
|
+
/** Moves the check of [itemId]'s level to it, in [menu] itself; nothing in a level without single selection. */
|
|
114
|
+
fun mark(menu: ButtonMenuOptions, itemId: String) {
|
|
115
|
+
mark(menu.items, menu.singleSelection, itemId)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private fun buttonIn(buttons: TopBarButtons, id: String): ButtonOptions? =
|
|
119
|
+
buttons.left?.firstOrNull { it.id.get(null) == id } ?: buttons.right?.firstOrNull { it.id.get(null) == id }
|
|
120
|
+
|
|
121
|
+
// The menu the key names in [options]: `overflowMenu`, or the menu of the button with that id.
|
|
122
|
+
private fun menuIn(options: Options, key: String): ButtonMenuOptions? =
|
|
123
|
+
if (key == OVERFLOW) options.topBar.overflowMenu else buttonIn(options.topBar.buttons, key)?.menu
|
|
124
|
+
|
|
125
|
+
// The checked items of the level that holds [itemId] in [menu]: the field a merge gives that level.
|
|
126
|
+
private fun checkedInLevel(menu: ButtonMenuOptions, itemId: String): Set<String>? =
|
|
127
|
+
levelItems(menu.items, itemId)?.filter { it.checked }?.map { it.id }?.toSet()
|
|
128
|
+
|
|
129
|
+
// The items of the level that holds [itemId]; null when it isn't in [elements].
|
|
130
|
+
private fun levelItems(
|
|
131
|
+
elements: List<ButtonMenuOptions.Element>,
|
|
132
|
+
itemId: String,
|
|
133
|
+
): List<ButtonMenuOptions.Item>? {
|
|
134
|
+
val items = elements.filterIsInstance<ButtonMenuOptions.Item>()
|
|
135
|
+
if (items.any { it.id == itemId }) return items
|
|
136
|
+
return elements.filterIsInstance<ButtonMenuOptions.Submenu>().firstNotNullOfOrNull {
|
|
137
|
+
levelItems(it.items, itemId)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Whether the level holding [itemId] has single selection; null when [itemId] isn't in [elements].
|
|
142
|
+
private fun levelOf(elements: List<ButtonMenuOptions.Element>, single: Boolean, itemId: String): Boolean? {
|
|
143
|
+
if (elements.any { it is ButtonMenuOptions.Item && it.id == itemId }) return single
|
|
144
|
+
for (element in elements) {
|
|
145
|
+
if (element !is ButtonMenuOptions.Submenu) continue
|
|
146
|
+
levelOf(element.items, element.singleSelection, itemId)?.let { return it }
|
|
147
|
+
}
|
|
148
|
+
return null
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private fun mark(elements: List<ButtonMenuOptions.Element>, single: Boolean, itemId: String): Boolean {
|
|
152
|
+
if (elements.any { it is ButtonMenuOptions.Item && it.id == itemId }) {
|
|
153
|
+
if (single) elements.forEach { if (it is ButtonMenuOptions.Item) it.checked = it.id == itemId }
|
|
154
|
+
return true
|
|
155
|
+
}
|
|
156
|
+
return elements.any { it is ButtonMenuOptions.Submenu && mark(it.items, it.singleSelection, itemId) }
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|