@apexnative/navigation 1.0.0-alpha.5 → 1.0.0-alpha.6
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 +25 -0
- package/README.md +1 -1
- package/android/CLAUDE.md +14 -12
- package/android/CORE.md +9 -6
- package/android/src/main/java/dev/apexnative/navigation/NavigationActivity.kt +36 -5
- package/android/src/main/java/dev/apexnative/navigation/options/BottomTabsOptions.kt +13 -8
- package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +3 -3
- package/android/src/main/java/dev/apexnative/navigation/react/ReloadCoordinator.kt +0 -2
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenEnvironment.kt +177 -0
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenRegions.kt +12 -49
- package/android/src/main/java/dev/apexnative/navigation/react/ScrollEvents.kt +49 -128
- package/android/src/main/java/dev/apexnative/navigation/safearea/SafeAreaView.kt +6 -6
- package/android/src/main/java/dev/apexnative/navigation/utils/ViewExtensions.kt +0 -13
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/arrangement/ArrangementController.kt +9 -7
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsCollapseBehavior.kt +5 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsController.kt +13 -10
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/CustomTabRowGeometry.kt +22 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/TabsRoom.kt +0 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ComponentViewController.kt +7 -8
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ScreenSafeArea.kt +29 -11
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/externalcomponent/ExternalComponentViewController.kt +2 -6
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalCoordinator.kt +20 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalSheet.kt +119 -8
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +7 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/SheetBack.kt +42 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt +4 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/ParentController.kt +3 -6
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/PreparedFrame.kt +3 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackCommands.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +4 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +3 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/viewcontroller/ScrollEventListener.kt +6 -9
- package/android/src/main/java/dev/apexnative/navigation/views/animations/AnimationLayer.kt +73 -0
- package/android/src/main/java/dev/apexnative/navigation/views/animations/ValueInterpolation.kt +14 -0
- package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/BottomTabsLayout.kt +61 -2
- package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/CustomTabRow.kt +72 -10
- package/android/src/main/java/dev/apexnative/navigation/views/element/ElementTransitions.kt +9 -2
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ElementPictures.kt +11 -0
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/FlightGeometry.kt +318 -25
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMatrixAnimator.kt +18 -20
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PlainViewAnimator.kt +17 -25
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ReactImageAnimator.kt +15 -22
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/SharedAnimatorFactory.kt +12 -9
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/TextChangeAnimator.kt +84 -35
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewChangeAnimator.kt +49 -40
- package/docs-md/architecture/transitions.md +4 -2
- package/docs-md/containers/modals.md +2 -0
- package/docs-md/containers/overlays.md +1 -1
- package/docs-md/containers/side-menu.md +1 -1
- package/docs-md/containers/tabs.md +2 -0
- package/docs-md/flows/bottom-sheets.md +1 -1
- package/docs-md/fundamentals/starting-the-app.md +1 -1
- package/docs-md/help/glossary.md +2 -2
- package/docs-md/help/troubleshooting.md +21 -0
- package/docs-md/index.json +12 -0
- package/docs-md/migration/from-expo-router.md +1 -1
- package/docs-md/migration/from-react-native-navigation.md +1 -1
- package/docs-md/migration/from-react-navigation.md +2 -2
- package/docs-md/screen-ui/back-and-gestures.md +14 -0
- package/docs-md/screen-ui/bar-buttons.md +4 -4
- package/docs-md/screen-ui/search.md +1 -1
- package/docs-md/screen-ui/system-ui.md +16 -11
- package/docs-md/screen-ui/toolbar-and-fab.md +2 -2
- package/docs-md/screen-ui/top-bar.md +8 -4
- package/ios/Animations/ANNFrameDriver.mm +2 -0
- package/ios/Animations/Elements/ANNSharedElementFlight.mm +70 -4
- package/ios/Animations/Interpolators/ANNInterpolate.h +2 -30
- package/ios/Animations/Interpolators/ANNTransformParts.h +48 -0
- package/ios/Animations/Interpolators/ANNTransformParts.mm +507 -0
- package/ios/Animations/Interpolators/ANNViewTracks.h +6 -4
- package/ios/Animations/Interpolators/ANNViewTracks.mm +49 -9
- package/ios/CLAUDE.md +17 -15
- package/ios/CORE.md +24 -20
- package/ios/Contract/ANNCommandRunner.h +8 -7
- package/ios/Contract/ANNCommandRunner.mm +21 -27
- package/ios/Contract/ANNLayoutNode.h +3 -3
- package/ios/Contract/ANNLayoutReader.h +3 -3
- package/ios/Core/ANNJavaScriptCalls.h +11 -12
- package/ios/Core/ANNJavaScriptCalls.mm +7 -16
- package/ios/Core/ANNReloadCoordinator.mm +4 -8
- package/ios/Core/ApexNavigation+Internal.h +1 -1
- package/ios/Layouts/Arrangement/ANNArrangementState.h +2 -17
- package/ios/Layouts/Arrangement/ANNArrangementState.mm +0 -38
- package/ios/Layouts/Arrangement/ANNArrangementViewController.mm +4 -4
- package/ios/Layouts/Screen/ANNComponentViewController.h +2 -2
- package/ios/Layouts/Screen/ANNComponentViewController.mm +9 -22
- package/ios/Layouts/Screen/ANNPreviewInteraction.h +7 -4
- package/ios/Layouts/Screen/ANNPreviewInteraction.mm +72 -10
- package/ios/Layouts/Screen/ANNScreenEnvironment.h +63 -0
- package/ios/Layouts/Screen/ANNScreenEnvironment.mm +181 -0
- package/ios/Layouts/Screen/ANNScreenRegions.h +3 -18
- package/ios/Layouts/Screen/ANNScreenRegions.mm +0 -38
- package/ios/Layouts/Screen/ANNScreenSafeArea.h +4 -9
- package/ios/Layouts/Screen/ANNScreenSafeArea.mm +14 -16
- package/ios/Layouts/Stack/ANNPreviewPush.h +7 -6
- package/ios/Layouts/Stack/ANNPreviewPush.mm +57 -5
- package/ios/Layouts/Tabs/ANNBottomTabsAccessoryView.h +12 -1
- package/ios/Layouts/Tabs/ANNBottomTabsAccessoryView.mm +23 -0
- package/ios/Layouts/Tabs/ANNBottomTabsController.mm +2 -1
- package/ios/Layouts/Tabs/ANNBottomTabsCustomRow.mm +8 -3
- package/ios/Layouts/Tabs/ANNBottomTabsPresenter.h +4 -0
- package/ios/Layouts/Tabs/ANNBottomTabsPresenter.mm +123 -36
- package/ios/Layouts/Tabs/ANNTabsDesign.h +6 -5
- package/ios/Layouts/Tabs/ANNTabsDesign.mm +9 -1
- package/ios/Layouts/Tabs/UITabBar+utils.h +5 -5
- package/ios/Layouts/Tabs/UITabBar+utils.mm +32 -13
- package/ios/Options/ANNBottomTabsOptions.h +2 -0
- package/ios/Options/ANNBottomTabsOptions.mm +1 -0
- package/ios/Options/ANNOptions.h +18 -0
- package/ios/Options/ANNOptions.mm +112 -1
- package/ios/Options/ANNOptionsReader.mm +110 -0
- package/ios/Options/Params/ANNParam.h +15 -0
- package/ios/Options/Params/ANNParam.mm +61 -0
- package/ios/Options/Params/Color.h +3 -0
- package/ios/Options/Params/Color.mm +21 -0
- package/ios/Options/Params/Image.h +4 -0
- package/ios/Options/Params/Image.mm +20 -0
- package/ios/React/ANNReactView.mm +6 -25
- package/ios/React/ANNSafeAreaViewComponentView.mm +26 -15
- package/ios/React/ANNSurfaceHostingView.h +6 -0
- package/ios/React/ANNSurfaceHostingView.mm +116 -0
- package/ios/TopBar/ANNBackButtonPresenter.mm +6 -5
- package/ios/TopBar/ANNBarAppearance.h +2 -1
- package/ios/TopBar/ANNBarAppearance.mm +111 -30
- package/ios/TopBar/ANNBarBackgroundComponent.mm +16 -0
- package/ios/TopBar/ANNBarButtonComponentView.h +4 -4
- package/ios/TopBar/ANNBarButtonComponentView.mm +1 -1
- package/ios/TopBar/ANNBarButtons.h +5 -3
- package/ios/TopBar/ANNBarButtons.mm +186 -17
- package/ios/TopBar/ANNLargeTitlePresenter.mm +24 -4
- package/ios/TopBar/ANNMainList.h +3 -0
- package/ios/TopBar/ANNMainList.mm +4 -0
- package/ios/TopBar/ANNScrollEdgeEffects.h +22 -6
- package/ios/TopBar/ANNScrollEdgeEffects.mm +123 -16
- package/ios/TopBar/ANNSearchPresenter.mm +2 -2
- package/ios/TopBar/ANNSearchResultsViewController.mm +5 -5
- package/ios/TopBar/ANNTopBarPresenter.h +3 -2
- package/ios/TopBar/ANNTopBarPresenter.mm +95 -22
- package/ios/TurboModules/ANNTurboModule.mm +20 -16
- package/lib/module/ARCHITECTURE.md +8 -4
- package/lib/module/api/hooks/useSafeAreaInsets.js +4 -3
- package/lib/module/api/hooks/useSafeAreaInsets.js.map +1 -1
- package/lib/module/api/options/OptionsChain.js +2 -2
- package/lib/module/engine/commands.js +21 -1
- package/lib/module/engine/commands.js.map +1 -1
- package/lib/module/engine/screens.js +29 -4
- package/lib/module/engine/screens.js.map +1 -1
- package/lib/typescript/api/app/createNavigation.d.ts +3 -3
- package/lib/typescript/api/hooks/useSafeAreaInsets.d.ts +4 -3
- package/lib/typescript/api/hooks/useSafeAreaInsets.d.ts.map +1 -1
- package/lib/typescript/api/options/OptionsChain.d.ts +2 -2
- package/lib/typescript/engine/commands.d.ts.map +1 -1
- package/lib/typescript/engine/screens.d.ts +20 -6
- package/lib/typescript/engine/screens.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/animations.d.ts +12 -7
- package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/bottomTabs.d.ts +18 -5
- package/lib/typescript/interfaces/options/bottomTabs.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/buttons.d.ts +8 -8
- package/lib/typescript/interfaces/options/system.d.ts +8 -5
- package/lib/typescript/interfaces/options/system.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/toolbar.d.ts +2 -2
- package/lib/typescript/interfaces/options/topBar.d.ts +32 -29
- package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/ARCHITECTURE.md +8 -4
- package/src/api/app/createNavigation.ts +3 -3
- package/src/api/hooks/useSafeAreaInsets.ts +4 -3
- package/src/api/options/OptionsChain.ts +2 -2
- package/src/engine/commands.ts +30 -6
- package/src/engine/screens.tsx +29 -8
- package/src/interfaces/options/animations.ts +12 -7
- package/src/interfaces/options/bottomTabs.ts +18 -5
- package/src/interfaces/options/buttons.ts +8 -8
- package/src/interfaces/options/system.ts +8 -5
- package/src/interfaces/options/toolbar.ts +2 -2
- package/src/interfaces/options/topBar.ts +32 -29
- package/android/src/main/java/dev/apexnative/navigation/react/ArrangementStates.kt +0 -42
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenInsets.kt +0 -70
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/CustomTabRowPlacer.kt +0 -223
- package/ios/Layouts/Screen/ANNScreenInsets.h +0 -30
- package/ios/Layouts/Screen/ANNScreenInsets.mm +0 -70
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.0.0-alpha.6](https://github.com/apexnative/navigation/compare/v1.0.0-alpha.5...v1.0.0-alpha.6) (2026-10-25)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### ⚠ BREAKING CHANGES
|
|
7
|
+
|
|
8
|
+
* leave the bottom edge of a screen free by default ([#49](https://github.com/apexnative/navigation/issues/49))
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
* a sheet's back preview on android, lighter tabs and top bar, the compatibility mode on ios 27 ([#46](https://github.com/apexnative/navigation/issues/46)) ([24d333d](https://github.com/apexnative/navigation/commit/24d333dd43fc5e056786f22dc8d7f1a0ff82a1ad))
|
|
13
|
+
* leave the bottom edge of a screen free by default ([#49](https://github.com/apexnative/navigation/issues/49)) ([b6bb165](https://github.com/apexnative/navigation/commit/b6bb165152114fb2886bd3c4ee55560c418f0cbe))
|
|
14
|
+
* **tabs:** choose whether a tab switch animates with bottomTabs.switchAnimation ([#52](https://github.com/apexnative/navigation/issues/52)) ([c6185b6](https://github.com/apexnative/navigation/commit/c6185b6b42574cff671def1ebdf8aa4f5e4be681))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
|
|
19
|
+
* an explicit top edge stays free under a large title ([#53](https://github.com/apexnative/navigation/issues/53)) ([188a89e](https://github.com/apexnative/navigation/commit/188a89e8d160c071f91cc010ed43ccc0683e12b3))
|
|
20
|
+
* shared elements fly turned and scaled views the same way on ios and android ([#50](https://github.com/apexnative/navigation/issues/50)) ([bf63ab6](https://github.com/apexnative/navigation/commit/bf63ab624d33ff9295af297ce6506206b1bfa0e6))
|
|
21
|
+
* the custom tab row moves with its tabs on android, and tab cells get selected and badge ([#51](https://github.com/apexnative/navigation/issues/51)) ([fd8f41d](https://github.com/apexnative/navigation/commit/fd8f41d2efd108741672220c54253a86704929a5))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
### Performance Improvements
|
|
25
|
+
|
|
26
|
+
* **ios:** render a preview's screen when a press on its view starts ([#48](https://github.com/apexnative/navigation/issues/48)) ([4a57d1a](https://github.com/apexnative/navigation/commit/4a57d1aee1df7690dbde4c7d08dca2a70773d1f5))
|
|
27
|
+
|
|
3
28
|
## [1.0.0-alpha.5](https://github.com/apexnative/navigation/compare/v1.0.0-alpha.4...v1.0.0-alpha.5) (2026-10-23)
|
|
4
29
|
|
|
5
30
|
|
package/README.md
CHANGED
|
@@ -97,7 +97,7 @@ navigation.push('Details', { props: { title: 'Order 42' } });
|
|
|
97
97
|
each other: tabs of stacks, a stack in a modal, tabs in a side menu.
|
|
98
98
|
- **Screen UI as options:** the top bar, bar buttons, search, the toolbar and the floating action button, the status
|
|
99
99
|
bar and the system bars.
|
|
100
|
-
- **No safe area code:** every screen keeps its
|
|
100
|
+
- **No safe area code:** every screen keeps its top and sides clear of the bars and the cutout by itself; the bottom is free, and a list adds `useSafeAreaInsets().bottom`.
|
|
101
101
|
- **Native screens:** SwiftUI, UIKit and Compose screens are pushed, presented and put in tabs the same way as React
|
|
102
102
|
screens.
|
|
103
103
|
- **One React tree when you want it:** each screen is its own React root by default; with `appProvider` all screens
|
package/android/CLAUDE.md
CHANGED
|
@@ -22,8 +22,8 @@ top-level folder. Main thread only, unless a line says otherwise.
|
|
|
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
24
|
`OptionsCommands`, `PendingCalls` (a task behind the calls JavaScript made), `ReloadCoordinator`, `ReactGateway`,
|
|
25
|
-
`ReactView` (a React root), the stores (`
|
|
26
|
-
`LaunchLink`, `ScrollEvents`.
|
|
25
|
+
`ReactView` (a React root), the stores (`ScreenEnvironment`: a screen's insets, padding, regions and pair state;
|
|
26
|
+
`Hinge`, `StartInfo`), `ScreenRegions` (measures a screen's regions), `LaunchLink`, `ScrollEvents`.
|
|
27
27
|
- `react/events/`: `AppearanceDispatcher`, `ScreenAppearance`, `EventEmitter`, `EventContract` (made by
|
|
28
28
|
`scripts/contract.mjs`), `NavigationState`, `ComponentType`.
|
|
29
29
|
- `viewcontrollers/viewcontroller/`: `ViewController` (the base of every node), `Presenter`, `IReactView` (what a
|
|
@@ -44,7 +44,7 @@ top-level folder. Main thread only, unless a line says otherwise.
|
|
|
44
44
|
- `viewcontrollers/sidemenu/`: `SideMenuController`, `SideMenuMotion`, `SideMenuPresenter`, `SideMenuRules`.
|
|
45
45
|
- `viewcontrollers/splitview/`, `arrangement/`: the split view and the pair (modules, plus the core's
|
|
46
46
|
`SplitViewPresenter`).
|
|
47
|
-
- `viewcontrollers/modal/`, `overlay/`: `ModalCoordinator`, `ModalRecord`, `ModalViews`, `ModalSheet`;
|
|
47
|
+
- `viewcontrollers/modal/`, `overlay/`: `ModalCoordinator`, `ModalRecord`, `ModalViews`, `ModalSheet`, `SheetBack`;
|
|
48
48
|
`OverlayCoordinator`, `OverlayRecord`, `OverlayViews`, `OverlayTouches`.
|
|
49
49
|
- `views/`: Android views and animators, no rules: `component/`, `externalcomponent/`, `stack/` (`StackLayout`,
|
|
50
50
|
`topbar/`, `fab/`), `bottomtabs/`, `sidemenu/`, `animations/` (curves, `SideAnimator`, `AnimationRules`), `element/`
|
|
@@ -78,7 +78,8 @@ top-level folder. Main thread only, unless a line says otherwise.
|
|
|
78
78
|
`StackViews.kt`, `StackTransitions.kt`, `PreparedPushes.kt`; `views/stack/StackLayout.kt`, `ScreenEdge.kt` (the edge
|
|
79
79
|
a moving screen extends); back: `NavigationActivity.kt`, `viewcontrollers/navigator/BackCallbackState.kt`; the
|
|
80
80
|
preview of a pop during the back gesture (Android 14+): `StackController.BackGesture`, `BackPreview.kt`,
|
|
81
|
-
`stackPoppedByBack` of the layouts.
|
|
81
|
+
`stackPoppedByBack` of the layouts; of a sheet's dismissal: `viewcontrollers/modal/SheetBack.kt`,
|
|
82
|
+
`ModalCoordinator.backTargetSheet`, `ModalSheet.BackPreview`.
|
|
82
83
|
- Stack FAB: `viewcontrollers/stack/fab/FabPresenter.kt`, `FabPlan.kt`, `FabPlacement.kt`; `views/stack/fab/`.
|
|
83
84
|
- Top bar (title, buttons, menus, large title): `viewcontrollers/stack/topbar/TopBarPresenter.kt`, `TopBarRoom.kt`,
|
|
84
85
|
`TopBarVisibility.kt`, `BarComponents.kt`, `ButtonPlaces.kt`, `LargeTitleCollapse.kt`, `BarRowTransition.kt`;
|
|
@@ -87,7 +88,7 @@ top-level folder. Main thread only, unless a line says otherwise.
|
|
|
87
88
|
`views/stack/topbar/search/`.
|
|
88
89
|
- Tabs: `viewcontrollers/bottomtabs/BottomTabsController.kt`, `BottomTabsPresenter.kt`, `TabSelection.kt`,
|
|
89
90
|
`TabItems.kt`, `TabsRoom.kt`; `views/bottomtabs/`; `customrow/BottomTabsCustomRowOptions.kt`,
|
|
90
|
-
`viewcontrollers/bottomtabs/
|
|
91
|
+
`viewcontrollers/bottomtabs/CustomTabRowGeometry.kt`.
|
|
91
92
|
- Modals and sheets: `viewcontrollers/modal/ModalCoordinator.kt`, `ModalRecord.kt`, `ModalViews.kt`, `ModalSheet.kt`,
|
|
92
93
|
`ModalAnimations.kt`; `options/SheetOptions.kt`.
|
|
93
94
|
- Overlays: `viewcontrollers/overlay/OverlayCoordinator.kt`, `OverlayViews.kt`, `OverlayTouches.kt`.
|
|
@@ -96,12 +97,13 @@ top-level folder. Main thread only, unless a line says otherwise.
|
|
|
96
97
|
- Split view: `viewcontrollers/splitview/SplitViewController.kt`, `SplitViewLayout.kt`, `SplitPanes.kt`,
|
|
97
98
|
`SplitViewPresenter.kt`; `StackCommands.showInColumn`.
|
|
98
99
|
- Pair (arrangement): `viewcontrollers/arrangement/ArrangementController.kt`, `ArrangementPlanner.kt`,
|
|
99
|
-
`ArrangementLayout.kt`, `ArrangementDefinition.kt`; `react/
|
|
100
|
+
`ArrangementLayout.kt`, `ArrangementDefinition.kt`; the pair's state in `react/ScreenEnvironment.kt`.
|
|
100
101
|
- Preview, key commands: iOS only. The options are parsed and merged (`options/PreviewOptions.kt`,
|
|
101
102
|
`options/KeyCommandOptions.kt`) and not applied; `dismissPreview` does nothing.
|
|
102
103
|
- Animations and transitions: `views/animations/` (`AnimationRules`, `Curves`, `SideAnimator`, `ValueInterpolation`,
|
|
103
|
-
`FadeThrough`, `SystemTransition`); `views/element/`
|
|
104
|
-
`animators/`); `options/StackAnimationOptions.kt`,
|
|
104
|
+
`FadeThrough`, `SystemTransition`, `AnimationLayer`: the hardware layer of a fade); `views/element/`
|
|
105
|
+
(`ElementTransitions`, `TransitionLayer`, `TargetsWait`, `animators/`); `options/StackAnimationOptions.kt`,
|
|
106
|
+
`ViewAnimationOptions.kt`.
|
|
105
107
|
- System bars: `system/SystemUiOwner.kt`, `SystemUiLayers.kt`, `SystemUiRules.kt`, `SystemUiWindow.kt`,
|
|
106
108
|
`AppDirection.kt`; `viewcontrollers/parent/ContainerBackground.kt`.
|
|
107
109
|
- Events and state: `react/events/AppearanceDispatcher.kt`, `ScreenAppearance.kt`, `EventEmitter.kt`,
|
|
@@ -111,21 +113,21 @@ top-level folder. Main thread only, unless a line says otherwise.
|
|
|
111
113
|
- Error texts and answers: `react/ErrorTexts.kt` and `react/CommandContract.kt`, made by `scripts/contract.mjs` from
|
|
112
114
|
`src/interfaces/contract/errors.json` and `commands.json`.
|
|
113
115
|
- Links: `react/LaunchLink.kt`, `NavigationActivity.kt` (`onNewIntent`); `react/StartInfo.kt`.
|
|
114
|
-
- Hinge and regions: `react/Hinge.kt`, `react/ScreenRegions.kt`, `react/
|
|
116
|
+
- Hinge and regions: `react/Hinge.kt`, `react/ScreenRegions.kt`, `react/ScreenEnvironment.kt`, `utils/WindowFolds.kt`.
|
|
115
117
|
- Native screens: `nativescreen/NativeScreenContext.kt`, `NativeScreenCalls.kt`, `NativeScreenCreator.kt`;
|
|
116
118
|
`viewcontrollers/externalcomponent/`.
|
|
117
119
|
- Slots: `slots/SlotHost.kt`, `SlotView.kt`, `SlotScreenView.kt`, `SlotContainers.kt` (containers and slot views by
|
|
118
120
|
slot id: a change goes straight to the views of its id, `SlotViews.kt`), `SlotViewManager.kt`.
|
|
119
121
|
- Safe area: `safearea/SafeAreaView.kt`, `SafeAreaViewManager.kt`; `viewcontrollers/component/ScreenReadiness.kt`;
|
|
120
|
-
`react/
|
|
122
|
+
`react/ScreenEnvironment.kt`.
|
|
121
123
|
- The C++ of `ANNSlot`, `ANNSafeAreaView`: `android/src/main/jni/CMakeLists.txt`, `ApexNavigationSpec.h`; sources in
|
|
122
124
|
`ios/cpp/`.
|
|
123
125
|
|
|
124
126
|
## Threads
|
|
125
127
|
|
|
126
128
|
- **JS thread.** The sync getters `getScreenInsets`, `getScreenRegions`, `getArrangementState`, `getHinge`,
|
|
127
|
-
`getStartInfo` read stores (`ConcurrentHashMap`
|
|
128
|
-
|
|
129
|
+
`getStartInfo` read stores (one `ConcurrentHashMap` of immutable records in `ScreenEnvironment`; `@Volatile` in
|
|
130
|
+
`Hinge`). They never touch a node.
|
|
129
131
|
- **Module thread.** The `check` of every command with a promise: `LayoutReader` and, through it, `OptionsReader` (also
|
|
130
132
|
for `setDefaultOptions`, `mergeOptions` and the options of `dismissModal`, in the runner's `prepare` step);
|
|
131
133
|
`ReadableJson` for native-screen props. The registry of native screens is read under its lock. Images are not loaded
|
package/android/CORE.md
CHANGED
|
@@ -10,7 +10,7 @@ otherwise.
|
|
|
10
10
|
```
|
|
11
11
|
JS ─ ANNTurboModule (codegen NativeANNTurboModuleSpec)
|
|
12
12
|
│ sync getters answer on the JS thread from stores the main thread fills
|
|
13
|
-
│ (
|
|
13
|
+
│ (ScreenEnvironment, Hinge, StartInfo)
|
|
14
14
|
▼ module thread
|
|
15
15
|
react/NavigationTurboModule one override per method
|
|
16
16
|
│ layout/LayoutReader ► options/OptionsReader the whole layout read and checked, its options read
|
|
@@ -188,7 +188,7 @@ Each rule has one owner. Change the owner, not a caller; if a new feature needs
|
|
|
188
188
|
- **Room for the bars.** A screen's room at the top and bottom is `ScreenFrame` with `ScreenSafeArea` and the
|
|
189
189
|
`ParentController` room hooks (`getTopInset`, `keepsStatusBarRoomOf`, …). A screen in a pair forwards its room to
|
|
190
190
|
the pair (`ScreenFrame.applyRoom`), and the pair's `getTopInset` uses the screens' rule (`ScreenFrame.topRoomOf`)
|
|
191
|
-
with the options it shows with. A view's place in the window for the safe area (the React root, the stack's header
|
|
191
|
+
with the options it shows with. The bottom edge is not padded by default (only `'bottom'` in the edges pads it); `roomPadding` still shrinks the view for an opaque navigation bar and the keyboard. A view's place in the window for the safe area (the React root, the stack's header
|
|
192
192
|
bottom in `headerBottomOf`, the tabs' top in `tabsTopOf`) is `RestPlace`: from layout, without translations, a sheet
|
|
193
193
|
at rest.
|
|
194
194
|
- **A container's own move.** A container that moves children in or out of the window without a command (the split
|
|
@@ -209,9 +209,9 @@ Modules are the feature classes that plug into the core through its contract (th
|
|
|
209
209
|
(`SlotHost`, `SlotView`, `SlotContainers`), safe area (`SafeAreaView`), native screens (`nativescreen/`), splash
|
|
210
210
|
(`splash/`), animations (`FadeThrough`, `SystemTransition`, `TransitionTargets`, three element animators), utils
|
|
211
211
|
(`MaterialTheme`, `NavigationLog`, `awaitBarComponents`, `whenDrawn`).
|
|
212
|
-
- **Stores and singletons:**
|
|
213
|
-
|
|
214
|
-
(`BackCallbackState`), stack (`PreparedFrame`).
|
|
212
|
+
- **Stores and singletons:** a screen's environment (`ScreenEnvironment`: its insets and padding, its regions as
|
|
213
|
+
`ScreenRegions` measures them, its pair state), the window (`Hinge`, `WindowFolds`), links (`LaunchLink`,
|
|
214
|
+
`StartInfo`), events (`NavigationState`), commands (`BackCallbackState`), stack (`PreparedFrame`).
|
|
215
215
|
|
|
216
216
|
How they plug in:
|
|
217
217
|
|
|
@@ -235,5 +235,8 @@ How they plug in:
|
|
|
235
235
|
- **Core code in a module's folder:** `viewcontrollers/splitview/SplitViewPresenter.kt`, `slots/SlotScreenView.kt`,
|
|
236
236
|
and in `views/animations/` and `views/element/` everything except the files named above.
|
|
237
237
|
- **Stores** are written on the main thread by the core and read from any thread; they are cleared with their nodes and on
|
|
238
|
-
reload.
|
|
238
|
+
reload. A screen's environment is one immutable record per id in `ScreenEnvironment` (insets for JavaScript, padding,
|
|
239
|
+
regions, pair state), replaced at once; the screen's node clears it with one `remove` when it is destroyed, unless a
|
|
240
|
+
later node has its id; the padding listeners of its safe area views stay until the views stop. Their events, and
|
|
241
|
+
the search's, go through the emitter of the JavaScript the screen was made for
|
|
239
242
|
(`EventEmitter.of(node.jsGeneration)`); the hinge keeps the `EventEmitter.current()` of the call that turned it on.
|
|
@@ -35,6 +35,7 @@ import dev.apexnative.navigation.splash.SplashTheme
|
|
|
35
35
|
import dev.apexnative.navigation.splash.SplashView
|
|
36
36
|
import dev.apexnative.navigation.system.SystemUiWindow
|
|
37
37
|
import dev.apexnative.navigation.utils.MaterialTheme
|
|
38
|
+
import dev.apexnative.navigation.viewcontrollers.modal.SheetBack
|
|
38
39
|
import dev.apexnative.navigation.viewcontrollers.navigator.BackCallbackCheck
|
|
39
40
|
import dev.apexnative.navigation.viewcontrollers.navigator.BackCallbackState
|
|
40
41
|
import dev.apexnative.navigation.viewcontrollers.navigator.Navigator
|
|
@@ -72,23 +73,33 @@ open class NavigationActivity : AppCompatActivity(), DefaultHardwareBackBtnHandl
|
|
|
72
73
|
// Enabled while back has something to do in the app (BackCallbackState); otherwise the system does its own back.
|
|
73
74
|
// From Android 14, where the app hears the progress of the back gesture, a gesture that would pop a stack with the
|
|
74
75
|
// default transition previews the pop as the system previews back between activities (`StackController
|
|
75
|
-
// .BackGesture`)
|
|
76
|
+
// .BackGesture`), and one that would dismiss a sheet previews its hide as Material's sheets do (`SheetBack`), its
|
|
77
|
+
// release closing it as a drag down does; JavaScript's listeners hear back first, so with them there is no preview.
|
|
76
78
|
private val backCallback = object : OnBackPressedCallback(false) {
|
|
77
79
|
override fun handleOnBackStarted(backEvent: BackEventCompat) {
|
|
78
80
|
backGesture = null
|
|
79
|
-
|
|
81
|
+
sheetGesture = null
|
|
82
|
+
val navigator = navigator ?: return
|
|
83
|
+
backCheck?.atBack(navigator)
|
|
80
84
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE || BackCallbackState.hasJsListeners) return
|
|
81
|
-
val stack = navigator
|
|
82
|
-
|
|
85
|
+
val stack = navigator.stackPoppedByBack()
|
|
86
|
+
if (stack != null) {
|
|
87
|
+
backGesture = stack.startBackGesture(backEvent.swipeEdge == BackEventCompat.EDGE_LEFT, backEvent.touchY)
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
sheetGesture = navigator.sheetDismissedByBack()?.startBackPreview(backEvent)
|
|
83
91
|
}
|
|
84
92
|
|
|
85
93
|
override fun handleOnBackProgressed(backEvent: BackEventCompat) {
|
|
86
94
|
backGesture?.progress(backEvent.progress, backEvent.touchY)
|
|
95
|
+
sheetGesture?.progress(backEvent)
|
|
87
96
|
}
|
|
88
97
|
|
|
89
98
|
override fun handleOnBackCancelled() {
|
|
90
99
|
backGesture?.cancel()
|
|
91
100
|
backGesture = null
|
|
101
|
+
sheetGesture?.cancel()
|
|
102
|
+
sheetGesture = null
|
|
92
103
|
navigator?.let(BackCallbackState::update)
|
|
93
104
|
}
|
|
94
105
|
|
|
@@ -96,13 +107,33 @@ open class NavigationActivity : AppCompatActivity(), DefaultHardwareBackBtnHandl
|
|
|
96
107
|
navigator?.let { backCheck?.atBack(it) }
|
|
97
108
|
val gesture = backGesture
|
|
98
109
|
backGesture = null
|
|
99
|
-
|
|
110
|
+
val sheet = sheetGesture
|
|
111
|
+
sheetGesture = null
|
|
112
|
+
when {
|
|
113
|
+
gesture?.commit() == true -> BackCallbackState.update()
|
|
114
|
+
sheet != null && commitSheet(sheet) -> BackCallbackState.update()
|
|
115
|
+
else -> passBack()
|
|
116
|
+
}
|
|
100
117
|
}
|
|
101
118
|
}
|
|
102
119
|
|
|
103
120
|
// The back gesture that previews a pop now; none.
|
|
104
121
|
private var backGesture: StackController.BackGesture? = null
|
|
105
122
|
|
|
123
|
+
// The back gesture that previews a sheet's hide now; none.
|
|
124
|
+
private var sheetGesture: SheetBack.Gesture? = null
|
|
125
|
+
|
|
126
|
+
// A sheet's preview released: the sheet goes as the user's drag down closes it, and back doesn't go on (no
|
|
127
|
+
// `passBack`, which would dismiss it a second time). A command that changed what back does since the gesture
|
|
128
|
+
// started (a push in the sheet, a modal over it, a hold) leaves it to the usual back, the sheet back in place.
|
|
129
|
+
private fun commitSheet(gesture: SheetBack.Gesture): Boolean {
|
|
130
|
+
if (navigator?.sheetDismissedByBack() !== gesture.sheet) {
|
|
131
|
+
gesture.cancel()
|
|
132
|
+
return false
|
|
133
|
+
}
|
|
134
|
+
return gesture.commit()
|
|
135
|
+
}
|
|
136
|
+
|
|
106
137
|
// Debug builds only: the check that what back does never changes without its trigger (BackCallbackState), sampled
|
|
107
138
|
// before frames and at back events. It only logs; release builds have neither the check nor the listener.
|
|
108
139
|
private val backCheck = if (BuildConfig.DEBUG) BackCallbackCheck() else null
|
|
@@ -95,6 +95,7 @@ class BottomTabsAccessoryOptions {
|
|
|
95
95
|
class BottomTabsOptions {
|
|
96
96
|
@JvmField var visible: Bool = NullBool()
|
|
97
97
|
@JvmField var animate: Bool = NullBool()
|
|
98
|
+
@JvmField var switchAnimation: Text = NullText()
|
|
98
99
|
/** Selects a tab once; a whole number, truncated where it is read. */
|
|
99
100
|
@JvmField var currentTabIndex: Fraction = NullFraction()
|
|
100
101
|
/** Selects a tab once. */
|
|
@@ -124,18 +125,19 @@ class BottomTabsOptions {
|
|
|
124
125
|
@JvmField var minimizeBehavior: Text = NullText()
|
|
125
126
|
@JvmField var bottomAccessory = BottomTabsAccessoryOptions()
|
|
126
127
|
|
|
127
|
-
fun hasValue(): Boolean = visible.hasValue() || animate.hasValue() ||
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
bottomAccessory.hasValue()
|
|
128
|
+
fun hasValue(): Boolean = visible.hasValue() || animate.hasValue() || switchAnimation.hasValue() ||
|
|
129
|
+
currentTabIndex.hasValue() || currentTabId.hasValue() || testID.hasValue() || accessibilityLabel.hasValue() ||
|
|
130
|
+
moreTestID.hasValue() || drawBehind.hasValue() || backgroundColor.hasValue() || tabsAttachMode.hasValue() ||
|
|
131
|
+
barStyle.hasValue() || layoutStyle.hasValue() || cornerRadius.hasValue() || bottomMargin.hasValue() ||
|
|
132
|
+
translucent.hasValue() || hideShadow.hasValue() || titleDisplayMode.hasValue() || elevation.hasValue() ||
|
|
133
|
+
hideOnScroll.hasValue() || borderColor.hasValue() || borderWidth.hasValue() || shadow.hasValue() ||
|
|
134
|
+
customRow.hasValue() || sidebar.hasValue() || sidebarLayout.hasValue() || customizationId.hasValue() ||
|
|
135
|
+
minimizeBehavior.hasValue() || bottomAccessory.hasValue()
|
|
135
136
|
|
|
136
137
|
fun mergeWith(other: BottomTabsOptions) {
|
|
137
138
|
if (other.visible.hasValue()) visible = other.visible
|
|
138
139
|
if (other.animate.hasValue()) animate = other.animate
|
|
140
|
+
if (other.switchAnimation.hasValue()) switchAnimation = other.switchAnimation
|
|
139
141
|
if (other.currentTabIndex.hasValue()) currentTabIndex = other.currentTabIndex
|
|
140
142
|
if (other.currentTabId.hasValue()) currentTabId = other.currentTabId
|
|
141
143
|
if (other.testID.hasValue()) testID = other.testID
|
|
@@ -167,6 +169,7 @@ class BottomTabsOptions {
|
|
|
167
169
|
fun mergeWithDefault(defaults: BottomTabsOptions) {
|
|
168
170
|
if (!visible.hasValue()) visible = defaults.visible
|
|
169
171
|
if (!animate.hasValue()) animate = defaults.animate
|
|
172
|
+
if (!switchAnimation.hasValue()) switchAnimation = defaults.switchAnimation
|
|
170
173
|
if (!currentTabIndex.hasValue()) currentTabIndex = defaults.currentTabIndex
|
|
171
174
|
if (!currentTabId.hasValue()) currentTabId = defaults.currentTabId
|
|
172
175
|
if (!testID.hasValue()) testID = defaults.testID
|
|
@@ -198,6 +201,7 @@ class BottomTabsOptions {
|
|
|
198
201
|
fun copy() = BottomTabsOptions().also { it.mergeWith(this) }
|
|
199
202
|
|
|
200
203
|
companion object {
|
|
204
|
+
private val SWITCH_ANIMATIONS = setOf("platform", "none")
|
|
201
205
|
private val TABS_ATTACH_MODES = setOf("together", "afterInitialTab", "onSwitchToTab")
|
|
202
206
|
private val BAR_STYLES = setOf("default", "black")
|
|
203
207
|
private val LAYOUT_STYLES = setOf("stretch", "compact")
|
|
@@ -212,6 +216,7 @@ class BottomTabsOptions {
|
|
|
212
216
|
if (map == null) return options
|
|
213
217
|
options.visible = BoolParser.parse(map, "visible")
|
|
214
218
|
options.animate = BoolParser.parse(map, "animate")
|
|
219
|
+
options.switchAnimation = TextParser.parse(map, "switchAnimation", SWITCH_ANIMATIONS)
|
|
215
220
|
options.currentTabIndex = FractionParser.parse(map, "currentTabIndex")
|
|
216
221
|
options.currentTabId = TextParser.parse(map, "currentTabId")
|
|
217
222
|
options.testID = TextParser.parse(map, "testID")
|
|
@@ -236,11 +236,11 @@ class NavigationTurboModule(reactContext: ReactApplicationContext) : NativeANNTu
|
|
|
236
236
|
check = {},
|
|
237
237
|
) { _, answer -> navigator().stackCommands.setColumnShown(componentId, column, shown, answer) }
|
|
238
238
|
|
|
239
|
-
override fun getArrangementState(componentId: String): WritableMap =
|
|
239
|
+
override fun getArrangementState(componentId: String): WritableMap = ScreenEnvironment.getArrangementState(componentId)
|
|
240
240
|
|
|
241
|
-
override fun getScreenInsets(componentId: String): WritableMap =
|
|
241
|
+
override fun getScreenInsets(componentId: String): WritableMap = ScreenEnvironment.getScreenInsets(componentId)
|
|
242
242
|
|
|
243
|
-
override fun getScreenRegions(componentId: String): WritableMap =
|
|
243
|
+
override fun getScreenRegions(componentId: String): WritableMap = ScreenEnvironment.getScreenRegions(componentId)
|
|
244
244
|
|
|
245
245
|
// With the activity the user sees, if there is one; the hinge remembers it and goes on with the next activity
|
|
246
246
|
// (`Hinge.attach`).
|
|
@@ -82,8 +82,6 @@ internal object ReloadCoordinator {
|
|
|
82
82
|
UiThread.runOnMain {
|
|
83
83
|
launches.contextReady(context)
|
|
84
84
|
NativeScreenCalls.failAll { name, componentId -> ErrorTexts.nativeCallReloaded(name, componentId) }
|
|
85
|
-
// Scroll listeners that found no event dispatcher listen now.
|
|
86
|
-
ScrollEvents.dispatcherReady()
|
|
87
85
|
Navigators.all().filter { it.isNavigationResumed }.forEach(::launchIfDue)
|
|
88
86
|
}
|
|
89
87
|
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
package dev.apexnative.navigation.react
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.bridge.WritableArray
|
|
5
|
+
import com.facebook.react.bridge.WritableMap
|
|
6
|
+
import dev.apexnative.navigation.react.events.EventContract
|
|
7
|
+
import dev.apexnative.navigation.react.events.EventEmitter
|
|
8
|
+
import java.util.concurrent.ConcurrentHashMap
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* What JavaScript knows of each screen's place, by the screen's id, in one record: its safe area (the insets JavaScript
|
|
12
|
+
* gets and the padding of its safe area view, `useSafeAreaInsets`), its reserved regions (`useReservedRegions`) and,
|
|
13
|
+
* in a pair, its arrangement (`useArrangement`). The core measures them (`ScreenSafeArea`, [ScreenRegions], the pair's
|
|
14
|
+
* plan) on the main thread and keeps them here; JavaScript reads them from its own thread for a screen's first render
|
|
15
|
+
* and gets every change as an event. The record is immutable and replaced at once, so a reader on another thread sees
|
|
16
|
+
* one whole record. When the screen goes, [remove] clears all of it. As on iOS (`ANNScreenEnvironment`).
|
|
17
|
+
*/
|
|
18
|
+
internal object ScreenEnvironment {
|
|
19
|
+
/** How much covers each edge of a screen's content, in dp. */
|
|
20
|
+
data class Insets(val top: Double, val right: Double, val bottom: Double, val left: Double)
|
|
21
|
+
|
|
22
|
+
/** A reserved region, in dp from the top left of the screen's content. */
|
|
23
|
+
data class Region(val x: Double, val y: Double, val width: Double, val height: Double)
|
|
24
|
+
|
|
25
|
+
/** Where a screen divides (`division`) and where hardware covers it (`occlusion`). */
|
|
26
|
+
data class Regions(val division: List<Region>, val occlusion: List<Region>) {
|
|
27
|
+
val isEmpty: Boolean get() = division.isEmpty() && occlusion.isEmpty()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Which screen of its pair a screen is, how the pair lies, whether it lies over the other one and is hidden. */
|
|
31
|
+
data class Arrangement(val placement: String, val layout: String, val front: Boolean, val hidden: Boolean)
|
|
32
|
+
|
|
33
|
+
/** Where the changes JavaScript hears go: the events of the screen's JavaScript, or a test's record. */
|
|
34
|
+
interface Changes {
|
|
35
|
+
fun insetsChanged(screenId: String, insets: Insets)
|
|
36
|
+
|
|
37
|
+
fun regionsChanged(screenId: String, regions: Regions)
|
|
38
|
+
|
|
39
|
+
fun arrangementChanged(screenId: String, arrangement: Arrangement)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// One screen's record; null for a fact not known yet (not laid out, outside a pair).
|
|
43
|
+
private data class Record(
|
|
44
|
+
val insets: Insets? = null,
|
|
45
|
+
val padding: Insets? = null,
|
|
46
|
+
val regions: Regions? = null,
|
|
47
|
+
val arrangement: Arrangement? = null,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
private val NO_REGIONS = Regions(emptyList(), emptyList())
|
|
51
|
+
private val records = ConcurrentHashMap<String, Record>()
|
|
52
|
+
// Main thread only.
|
|
53
|
+
private val paddingListeners = HashMap<String, MutableSet<() -> Unit>>()
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Keeps the screen's [insets] for JavaScript (what still covers its content) and the [padding] of its safe area
|
|
57
|
+
* view. Calls the screen's padding listeners when the padding changed, then tells JavaScript when the insets did.
|
|
58
|
+
* Main thread only.
|
|
59
|
+
*/
|
|
60
|
+
fun setInsets(screenId: String, insets: Insets, padding: Insets, changes: Changes) {
|
|
61
|
+
val before = update(screenId) { it.copy(insets = insets, padding = padding) }
|
|
62
|
+
if (before?.padding != padding) paddingListeners[screenId]?.toList()?.forEach { it() }
|
|
63
|
+
if (before?.insets != insets) changes.insetsChanged(screenId, insets)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Keeps the screen's [regions], and tells JavaScript when they changed; none at first is what JavaScript reads
|
|
68
|
+
* before the first layout, so it isn't told. Main thread only.
|
|
69
|
+
*/
|
|
70
|
+
fun setRegions(screenId: String, regions: Regions, changes: Changes) {
|
|
71
|
+
val before = update(screenId) { it.copy(regions = regions) }?.regions
|
|
72
|
+
if (before == regions || (before == null && regions.isEmpty)) return
|
|
73
|
+
changes.regionsChanged(screenId, regions)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Keeps the screen's [arrangement], and tells JavaScript when it changed and [notify] holds. Main thread only. */
|
|
77
|
+
fun setArrangement(screenId: String, arrangement: Arrangement, notify: Boolean, changes: Changes) {
|
|
78
|
+
val before = update(screenId) { it.copy(arrangement = arrangement) }?.arrangement
|
|
79
|
+
if (before == arrangement || !notify) return
|
|
80
|
+
changes.arrangementChanged(screenId, arrangement)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** The padding of the screen's safe area view; none before the screen was laid out. Any thread. */
|
|
84
|
+
fun padding(screenId: String): Insets? = records[screenId]?.padding
|
|
85
|
+
|
|
86
|
+
/** The insets JavaScript gets; none before the screen was laid out. Any thread. */
|
|
87
|
+
fun insets(screenId: String): Insets? = records[screenId]?.insets
|
|
88
|
+
|
|
89
|
+
/** The screen's reserved regions; none before its first layout. Any thread. */
|
|
90
|
+
fun regions(screenId: String): Regions? = records[screenId]?.regions
|
|
91
|
+
|
|
92
|
+
/** The screen's arrangement; none outside a pair. Any thread. */
|
|
93
|
+
fun arrangement(screenId: String): Arrangement? = records[screenId]?.arrangement
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Calls `listener` when the padding of the screen changes; returns the function that stops it. Main thread only.
|
|
97
|
+
*/
|
|
98
|
+
fun listenToPadding(screenId: String, listener: () -> Unit): () -> Unit {
|
|
99
|
+
paddingListeners.getOrPut(screenId) { LinkedHashSet() }.add(listener)
|
|
100
|
+
return {
|
|
101
|
+
paddingListeners[screenId]?.let {
|
|
102
|
+
it.remove(listener)
|
|
103
|
+
if (it.isEmpty()) paddingListeners.remove(screenId)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Forgets everything kept for the screen. Its padding listeners stay: a live safe area view keeps listening while
|
|
110
|
+
* it keeps the id, and stops itself.
|
|
111
|
+
*/
|
|
112
|
+
fun remove(screenId: String) {
|
|
113
|
+
records.remove(screenId)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** `getScreenInsets`: `{ top, right, bottom, left }`, empty before the screen was laid out. Any thread. */
|
|
117
|
+
fun getScreenInsets(screenId: String): WritableMap = insets(screenId)?.let(::toMap) ?: Arguments.createMap()
|
|
118
|
+
|
|
119
|
+
/** `getScreenRegions`: `{ division, occlusion }`, empty lists before the first layout. Any thread. */
|
|
120
|
+
fun getScreenRegions(screenId: String): WritableMap = toMap(regions(screenId) ?: NO_REGIONS)
|
|
121
|
+
|
|
122
|
+
/** `getArrangementState`: `{ placement, layout, front, hidden }`, empty outside a pair. Any thread. */
|
|
123
|
+
fun getArrangementState(screenId: String): WritableMap =
|
|
124
|
+
arrangement(screenId)?.let(::toMap) ?: Arguments.createMap()
|
|
125
|
+
|
|
126
|
+
/** The changes as events through [emitter], the one of the JavaScript the screen was made for. */
|
|
127
|
+
fun changesOf(emitter: EventEmitter): Changes = object : Changes {
|
|
128
|
+
override fun insetsChanged(screenId: String, insets: Insets) =
|
|
129
|
+
emitter.emitScreenInsetsChanged(screenId, toMap(insets))
|
|
130
|
+
|
|
131
|
+
override fun regionsChanged(screenId: String, regions: Regions) =
|
|
132
|
+
emitter.emitScreenRegionsChanged(screenId, toMap(regions))
|
|
133
|
+
|
|
134
|
+
override fun arrangementChanged(screenId: String, arrangement: Arrangement) =
|
|
135
|
+
emitter.emitArrangementChanged(screenId, toMap(arrangement))
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Replaces the screen's record at once with [change] of it; returns the record before, null for a new screen.
|
|
139
|
+
private fun update(screenId: String, change: (Record) -> Record): Record? {
|
|
140
|
+
var before: Record? = null
|
|
141
|
+
records.compute(screenId) { _, old ->
|
|
142
|
+
before = old
|
|
143
|
+
change(old ?: Record())
|
|
144
|
+
}
|
|
145
|
+
return before
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private fun toMap(value: Insets): WritableMap = Arguments.createMap().apply {
|
|
149
|
+
putDouble(EventContract.ScreenInsetsChanged.TOP, value.top)
|
|
150
|
+
putDouble(EventContract.ScreenInsetsChanged.RIGHT, value.right)
|
|
151
|
+
putDouble(EventContract.ScreenInsetsChanged.BOTTOM, value.bottom)
|
|
152
|
+
putDouble(EventContract.ScreenInsetsChanged.LEFT, value.left)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private fun toMap(value: Regions): WritableMap = Arguments.createMap().apply {
|
|
156
|
+
putArray(EventContract.ScreenRegionsChanged.DIVISION, toArray(value.division))
|
|
157
|
+
putArray(EventContract.ScreenRegionsChanged.OCCLUSION, toArray(value.occlusion))
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private fun toArray(regions: List<Region>): WritableArray = Arguments.createArray().apply {
|
|
161
|
+
for (region in regions) {
|
|
162
|
+
pushMap(Arguments.createMap().apply {
|
|
163
|
+
putDouble(EventContract.ScreenRegionRect.X, region.x)
|
|
164
|
+
putDouble(EventContract.ScreenRegionRect.Y, region.y)
|
|
165
|
+
putDouble(EventContract.ScreenRegionRect.WIDTH, region.width)
|
|
166
|
+
putDouble(EventContract.ScreenRegionRect.HEIGHT, region.height)
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private fun toMap(value: Arrangement): WritableMap = Arguments.createMap().apply {
|
|
172
|
+
putString(EventContract.ArrangementChanged.PLACEMENT, value.placement)
|
|
173
|
+
putString(EventContract.ArrangementChanged.LAYOUT, value.layout)
|
|
174
|
+
putBoolean(EventContract.ArrangementChanged.FRONT, value.front)
|
|
175
|
+
putBoolean(EventContract.ArrangementChanged.HIDDEN, value.hidden)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -3,36 +3,25 @@ package dev.apexnative.navigation.react
|
|
|
3
3
|
import android.app.Activity
|
|
4
4
|
import android.graphics.Rect
|
|
5
5
|
import androidx.core.view.WindowInsetsCompat
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import com.facebook.react.bridge.WritableMap
|
|
9
|
-
import dev.apexnative.navigation.react.events.EventContract
|
|
6
|
+
import dev.apexnative.navigation.react.ScreenEnvironment.Region
|
|
7
|
+
import dev.apexnative.navigation.react.ScreenEnvironment.Regions
|
|
10
8
|
import dev.apexnative.navigation.react.events.EventEmitter
|
|
11
9
|
import dev.apexnative.navigation.utils.WindowFolds
|
|
12
|
-
import java.util.concurrent.ConcurrentHashMap
|
|
13
10
|
|
|
14
11
|
/**
|
|
15
|
-
*
|
|
16
|
-
* (a fold the window is divided at, `division`) and where hardware covers it (a fold that hides what is
|
|
17
|
-
* camera cutout: `occlusion`). Only those on the screen, cut to it: a fold at the edge of a screen of a
|
|
18
|
-
* it. Measured on the main thread as each screen lays out, and again after a change of the window's
|
|
19
|
-
* the window out);
|
|
20
|
-
* `ANN.ScreenRegionsChanged`.
|
|
12
|
+
* Measures the reserved regions of each screen (`useReservedRegions`), in dp from the top left of its content: where
|
|
13
|
+
* it divides (a fold the window is divided at, `division`) and where hardware covers it (a fold that hides what is
|
|
14
|
+
* under it, the camera cutout: `occlusion`). Only those on the screen, cut to it: a fold at the edge of a screen of a
|
|
15
|
+
* pair is not on it. Measured on the main thread as each screen lays out, and again after a change of the window's
|
|
16
|
+
* folds (which lays the window out); [ScreenEnvironment] keeps them, answers JavaScript's reads and tells it of every
|
|
17
|
+
* change as `ANN.ScreenRegionsChanged`.
|
|
21
18
|
*/
|
|
22
19
|
internal object ScreenRegions {
|
|
23
|
-
data class Region(val x: Double, val y: Double, val width: Double, val height: Double)
|
|
24
|
-
|
|
25
|
-
data class Regions(val division: List<Region>, val occlusion: List<Region>) {
|
|
26
|
-
val isEmpty: Boolean get() = division.isEmpty() && occlusion.isEmpty()
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
private val NONE = Regions(emptyList(), emptyList())
|
|
30
|
-
private val regions = ConcurrentHashMap<String, Regions>()
|
|
31
|
-
|
|
32
20
|
/**
|
|
33
21
|
* Measures the regions of the screen whose content the layout puts at [content] in the window (pixels), with the
|
|
34
|
-
* window's [insets] (the camera cutout), and
|
|
35
|
-
* JavaScript the screen was made for: silent once it went. Main thread
|
|
22
|
+
* window's [insets] (the camera cutout), and keeps them in [ScreenEnvironment], which tells JavaScript when they
|
|
23
|
+
* changed through [emitter], the one of the JavaScript the screen was made for: silent once it went. Main thread
|
|
24
|
+
* only.
|
|
36
25
|
*/
|
|
37
26
|
fun update(
|
|
38
27
|
activity: Activity,
|
|
@@ -49,17 +38,7 @@ internal object ScreenRegions {
|
|
|
49
38
|
occlusion = (folds.filter { it.occludes }.map { it.bounds } + cutouts)
|
|
50
39
|
.mapNotNull { onScreen(it, content, density) },
|
|
51
40
|
)
|
|
52
|
-
|
|
53
|
-
// None at first is what JavaScript reads before the first layout.
|
|
54
|
-
if (previous == value || (previous == null && value.isEmpty)) return
|
|
55
|
-
emitter.emitScreenRegionsChanged(screenId, toMap(value))
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** `{ division, occlusion }`, empty lists for an unknown screen and before its first layout. Any thread. */
|
|
59
|
-
fun get(screenId: String): WritableMap = toMap(regions[screenId] ?: NONE)
|
|
60
|
-
|
|
61
|
-
fun remove(screenId: String) {
|
|
62
|
-
regions.remove(screenId)
|
|
41
|
+
ScreenEnvironment.setRegions(screenId, value, ScreenEnvironment.changesOf(emitter))
|
|
63
42
|
}
|
|
64
43
|
|
|
65
44
|
// The part of [bounds] (window pixels) on the screen, in dp from its top left; null off it. A fold without width
|
|
@@ -74,20 +53,4 @@ internal object ScreenRegions {
|
|
|
74
53
|
height = part.height() / density.toDouble(),
|
|
75
54
|
)
|
|
76
55
|
}
|
|
77
|
-
|
|
78
|
-
private fun toMap(value: Regions): WritableMap = Arguments.createMap().apply {
|
|
79
|
-
putArray(EventContract.ScreenRegionsChanged.DIVISION, toArray(value.division))
|
|
80
|
-
putArray(EventContract.ScreenRegionsChanged.OCCLUSION, toArray(value.occlusion))
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
private fun toArray(regions: List<Region>): WritableArray = Arguments.createArray().apply {
|
|
84
|
-
for (region in regions) {
|
|
85
|
-
pushMap(Arguments.createMap().apply {
|
|
86
|
-
putDouble(EventContract.ScreenRegionRect.X, region.x)
|
|
87
|
-
putDouble(EventContract.ScreenRegionRect.Y, region.y)
|
|
88
|
-
putDouble(EventContract.ScreenRegionRect.WIDTH, region.width)
|
|
89
|
-
putDouble(EventContract.ScreenRegionRect.HEIGHT, region.height)
|
|
90
|
-
})
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
56
|
}
|