@apexnative/navigation 1.0.0-alpha.3 → 1.0.0-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/android/CLAUDE.md +26 -9
- package/android/CORE.md +32 -17
- package/android/RECIPES.md +9 -3
- package/android/src/main/java/dev/apexnative/navigation/NavigationActivity.kt +25 -9
- package/android/src/main/java/dev/apexnative/navigation/NavigationApplication.kt +34 -13
- package/android/src/main/java/dev/apexnative/navigation/layout/LayoutChecks.kt +5 -1
- package/android/src/main/java/dev/apexnative/navigation/layout/LayoutNode.kt +6 -2
- package/android/src/main/java/dev/apexnative/navigation/nativescreen/NativeScreenContext.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/options/AnimationsOptions.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/options/ButtonMenuOptions.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/options/ButtonOptions.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/options/HardwareBackButtonOptions.kt +9 -1
- package/android/src/main/java/dev/apexnative/navigation/options/OptionsCache.kt +8 -4
- package/android/src/main/java/dev/apexnative/navigation/options/StackAnimationOptions.kt +40 -2
- package/android/src/main/java/dev/apexnative/navigation/options/UserChoices.kt +159 -0
- package/android/src/main/java/dev/apexnative/navigation/react/ArrangementStates.kt +6 -6
- package/android/src/main/java/dev/apexnative/navigation/react/CommandAnswer.kt +29 -36
- package/android/src/main/java/dev/apexnative/navigation/react/CommandContract.kt +59 -0
- package/android/src/main/java/dev/apexnative/navigation/react/Hinge.kt +33 -22
- package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +5 -4
- package/android/src/main/java/dev/apexnative/navigation/react/PendingCalls.kt +48 -0
- package/android/src/main/java/dev/apexnative/navigation/react/ReactView.kt +39 -20
- package/android/src/main/java/dev/apexnative/navigation/react/ReloadCoordinator.kt +7 -4
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenInsets.kt +6 -6
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenRegions.kt +10 -5
- package/android/src/main/java/dev/apexnative/navigation/react/events/AppearanceDispatcher.kt +47 -46
- package/android/src/main/java/dev/apexnative/navigation/react/events/EventEmitter.kt +34 -7
- package/android/src/main/java/dev/apexnative/navigation/react/events/NavigationState.kt +154 -0
- package/android/src/main/java/dev/apexnative/navigation/safearea/SafeAreaView.kt +6 -4
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotContainers.kt +10 -7
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotScreenView.kt +12 -12
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotView.kt +4 -3
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotViews.kt +40 -0
- package/android/src/main/java/dev/apexnative/navigation/utils/ImageKey.kt +66 -0
- package/android/src/main/java/dev/apexnative/navigation/utils/ImageLoader.kt +7 -89
- package/android/src/main/java/dev/apexnative/navigation/utils/ImageRequests.kt +76 -0
- package/android/src/main/java/dev/apexnative/navigation/utils/OptionImages.kt +262 -0
- package/android/src/main/java/dev/apexnative/navigation/utils/ViewController.kt +3 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/arrangement/ArrangementController.kt +3 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/AppliedTabCalls.kt +49 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsController.kt +25 -17
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsPresenter.kt +25 -24
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ComponentViewController.kt +6 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ContentReadiness.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/RenderWait.kt +27 -7
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ScreenReadiness.kt +103 -56
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalCoordinator.kt +5 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/BackCallbackCheck.kt +50 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/BackCallbackState.kt +45 -14
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt +25 -8
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/RootCoordinator.kt +8 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/overlay/OverlayCoordinator.kt +8 -8
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/Capabilities.kt +11 -4
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuController.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitPanes.kt +23 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitViewController.kt +61 -15
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackCommands.kt +28 -18
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +25 -12
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackDisplay.kt +2 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackQueue.kt +85 -29
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabPresenter.kt +7 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/ScreenSearch.kt +3 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/StackSearchPresenter.kt +5 -8
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/BarComponents.kt +5 -51
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/MenuChecks.kt +13 -56
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +14 -11
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/viewcontroller/ViewController.kt +56 -21
- package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/BottomTabs.kt +4 -2
- package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/CustomBottomTabItemView.kt +0 -5
- package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/Fab.kt +14 -10
- package/cli/doctor/checks/app.js +61 -13
- package/cli/doctor/checks/splash.js +4 -22
- package/cli/migrate/native.js +18 -0
- package/cli/setup/source.js +2 -1
- package/cli/setup/steps/mainApplication.js +134 -19
- package/cli/splash/index.js +3 -1
- package/docs-md/architecture/transitions.md +6 -7
- package/docs-md/containers/modals.md +2 -2
- package/docs-md/guides/cli.md +1 -2
- package/docs-md/help/glossary.md +1 -1
- package/docs-md/help/troubleshooting.md +22 -5
- package/docs-md/index.json +4 -0
- package/docs-md/migration/from-react-native-navigation.md +8 -2
- package/docs-md/screen-ui/back-and-gestures.md +24 -2
- package/docs-md/start/installation.md +4 -2
- package/ios/CLAUDE.md +17 -5
- package/ios/CORE.md +18 -13
- package/ios/Contract/ANNCommandAnswer.h +9 -2
- package/ios/Contract/ANNCommandAnswer.mm +28 -30
- package/ios/Contract/ANNCommandContract.h +15 -0
- package/ios/Contract/ANNCommandContract.mm +78 -0
- package/ios/Core/ANNAppearanceDispatcher.h +5 -4
- package/ios/Core/ANNAppearanceDispatcher.mm +12 -1
- package/ios/Core/ANNErrorHandler.h +1 -1
- package/ios/Core/ANNErrorHandler.mm +3 -1
- package/ios/Core/ANNEventEmitter.h +1 -1
- package/ios/Core/ANNEventEmitter.mm +2 -2
- package/ios/Core/ANNJavaScriptCalls.h +34 -0
- package/ios/Core/ANNJavaScriptCalls.mm +61 -0
- package/ios/Core/ANNNavigationState.h +7 -5
- package/ios/Core/ANNNavigationState.mm +8 -7
- package/ios/Core/ANNNodeHelper.h +32 -12
- package/ios/Core/ANNNodeHelper.mm +100 -3
- package/ios/Core/ANNRenderWait.h +15 -1
- package/ios/Core/ANNRenderWait.mm +119 -17
- package/ios/Core/UIViewController+LayoutProtocol.h +9 -2
- package/ios/Core/UIViewController+LayoutProtocol.mm +8 -0
- package/ios/Layouts/Modal/ANNModalCoordinator.h +1 -1
- package/ios/Layouts/Modal/ANNModalCoordinator.mm +55 -14
- package/ios/Layouts/Root/ANNSplashViewController.h +5 -3
- package/ios/Layouts/Root/ANNSplashViewController.mm +147 -0
- package/ios/Layouts/Screen/ANNComponentViewController.h +8 -0
- package/ios/Layouts/Screen/ANNComponentViewController.mm +32 -5
- package/ios/Layouts/Screen/ANNScreenPlace.h +41 -0
- package/ios/Layouts/Screen/ANNScreenPlace.mm +175 -0
- package/ios/Layouts/SplitView/ANNSplitViewCommands.h +3 -2
- package/ios/Layouts/SplitView/ANNSplitViewCommands.mm +105 -39
- package/ios/Layouts/SplitView/ANNSplitViewController.mm +4 -2
- package/ios/Layouts/Stack/ANNPreviewPush.h +5 -5
- package/ios/Layouts/Stack/ANNPreviewPush.mm +85 -22
- package/ios/Layouts/Stack/ANNStackCommands.mm +1 -0
- package/ios/Layouts/Stack/ANNStackController.mm +26 -3
- package/ios/Layouts/Stack/ANNStackQueue.h +3 -1
- package/ios/Layouts/Stack/ANNStackQueue.mm +56 -43
- package/ios/Layouts/Stack/UINavigationController+ANNCommands.h +4 -2
- package/ios/Layouts/Stack/UINavigationController+ANNCommands.mm +2 -3
- package/ios/Layouts/Tabs/ANNBottomTabItemBuilder.h +4 -0
- package/ios/Layouts/Tabs/ANNBottomTabItemBuilder.mm +11 -0
- package/ios/Layouts/Tabs/ANNBottomTabsController.mm +13 -5
- package/ios/Layouts/Tabs/ANNBottomTabsPresenter.h +2 -2
- package/ios/Layouts/Tabs/ANNBottomTabsPresenter.mm +109 -7
- package/ios/Options/ANNOptionsCache.h +7 -6
- package/ios/Options/ANNSearchBarOptions.h +4 -3
- package/ios/Options/ANNUserChoice.h +57 -0
- package/ios/Options/ANNUserChoice.mm +44 -0
- package/ios/Options/Params/ANNImageSource.h +61 -0
- package/ios/Options/Params/ANNImageSource.mm +101 -0
- package/ios/Options/Params/ANNOptionImages.h +37 -0
- package/ios/Options/Params/ANNOptionImages.mm +284 -0
- package/ios/Options/Params/Image.h +11 -2
- package/ios/Options/Params/Image.mm +15 -2
- package/ios/Options/Params/ImageParser.h +5 -5
- package/ios/Options/Params/ImageParser.mm +28 -46
- package/ios/Public/ApexNavigation/ApexNavigation.h +1 -1
- package/ios/RECIPES.md +11 -5
- package/ios/React/Slots/ANNSlotComponentView.mm +24 -17
- package/ios/React/Slots/ANNSlotContainers.h +14 -6
- package/ios/React/Slots/ANNSlotContainers.mm +75 -2
- package/ios/TopBar/ANNBarButtons.h +9 -5
- package/ios/TopBar/ANNBarButtons.mm +211 -63
- package/ios/TopBar/ANNIconButtonImage.h +1 -0
- package/ios/TopBar/ANNIconButtonImage.mm +34 -1
- package/ios/TopBar/ANNLargeTitlePresenter.h +13 -0
- package/ios/TopBar/ANNLargeTitlePresenter.mm +26 -3
- package/ios/TopBar/ANNSearchPresenter.mm +170 -19
- package/ios/TopBar/ANNTopBarPresenter.h +5 -0
- package/ios/TopBar/ANNTopBarPresenter.mm +60 -11
- package/ios/TurboModules/ANNTurboModule.mm +39 -21
- package/lib/module/ARCHITECTURE.md +18 -18
- package/lib/module/adapters/NativeANNTurboModule.js.map +1 -1
- package/lib/module/api/commands/commands.js +54 -6
- package/lib/module/api/commands/commands.js.map +1 -1
- package/lib/module/api/navigation.js.map +1 -1
- package/lib/module/engine/commands.js +9 -7
- package/lib/module/engine/commands.js.map +1 -1
- package/lib/module/engine/layout.js +13 -9
- package/lib/module/engine/layout.js.map +1 -1
- package/lib/module/engine/options.js +44 -11
- package/lib/module/engine/options.js.map +1 -1
- package/lib/module/engine/places.js +52 -7
- package/lib/module/engine/places.js.map +1 -1
- package/lib/module/engine/props.js +30 -1
- package/lib/module/engine/props.js.map +1 -1
- package/lib/module/engine/rememberedOptions.js +1 -1
- package/lib/module/engine/rememberedOptions.js.map +1 -1
- package/lib/module/engine/screens.js +22 -12
- package/lib/module/engine/screens.js.map +1 -1
- package/lib/module/interfaces/contract/commands.json +101 -0
- package/lib/typescript/adapters/NativeANNTurboModule.d.ts +5 -2
- package/lib/typescript/adapters/NativeANNTurboModule.d.ts.map +1 -1
- package/lib/typescript/api/commands/commands.d.ts.map +1 -1
- package/lib/typescript/api/commands/presentation.d.ts +4 -3
- package/lib/typescript/api/commands/presentation.d.ts.map +1 -1
- package/lib/typescript/api/events/navigationEvents.d.ts +1 -1
- package/lib/typescript/api/events/navigationEvents.d.ts.map +1 -1
- package/lib/typescript/api/hooks/useNavigation.d.ts +1 -1
- package/lib/typescript/api/hooks/useNavigation.d.ts.map +1 -1
- package/lib/typescript/api/navigation.d.ts +4 -1
- package/lib/typescript/api/navigation.d.ts.map +1 -1
- package/lib/typescript/engine/commands.d.ts.map +1 -1
- package/lib/typescript/engine/options.d.ts +5 -2
- package/lib/typescript/engine/options.d.ts.map +1 -1
- package/lib/typescript/engine/places.d.ts +7 -2
- package/lib/typescript/engine/places.d.ts.map +1 -1
- package/lib/typescript/engine/props.d.ts +8 -1
- package/lib/typescript/engine/props.d.ts.map +1 -1
- package/lib/typescript/engine/rememberedOptions.d.ts.map +1 -1
- package/lib/typescript/engine/screens.d.ts.map +1 -1
- package/lib/typescript/interfaces/ComponentEvents.d.ts +2 -2
- package/lib/typescript/interfaces/ComponentEvents.d.ts.map +1 -1
- package/lib/typescript/interfaces/Events.d.ts +19 -10
- package/lib/typescript/interfaces/Events.d.ts.map +1 -1
- package/lib/typescript/interfaces/Options.d.ts +1 -1
- package/lib/typescript/interfaces/Options.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/animations.d.ts +23 -4
- package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/buttons.d.ts +13 -5
- package/lib/typescript/interfaces/options/buttons.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/stack.d.ts +9 -0
- package/lib/typescript/interfaces/options/stack.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/topBar.d.ts +22 -9
- package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/ARCHITECTURE.md +18 -18
- package/src/adapters/NativeANNTurboModule.ts +5 -2
- package/src/api/commands/commands.ts +48 -7
- package/src/api/commands/presentation.ts +4 -3
- package/src/api/events/navigationEvents.ts +1 -1
- package/src/api/hooks/useNavigation.ts +1 -1
- package/src/api/navigation.ts +4 -1
- package/src/engine/commands.ts +19 -7
- package/src/engine/layout.ts +16 -9
- package/src/engine/options.ts +39 -11
- package/src/engine/places.ts +65 -7
- package/src/engine/props.ts +30 -1
- package/src/engine/rememberedOptions.ts +4 -1
- package/src/engine/screens.tsx +23 -13
- package/src/interfaces/ComponentEvents.ts +2 -2
- package/src/interfaces/Events.ts +19 -10
- package/src/interfaces/Options.ts +1 -0
- package/src/interfaces/contract/commands.json +101 -0
- package/src/interfaces/options/animations.ts +24 -4
- package/src/interfaces/options/buttons.ts +13 -5
- package/src/interfaces/options/stack.ts +9 -0
- package/src/interfaces/options/topBar.ts +22 -9
- package/android/src/main/java/dev/apexnative/navigation/react/events/NavigationStateReporter.kt +0 -62
- package/android/src/main/java/dev/apexnative/navigation/react/events/StateHold.kt +0 -62
|
@@ -99,8 +99,8 @@ class BottomTabsEnterExitAnimationOptions {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
/**
|
|
102
|
-
* `StackAnimationOptions`: the
|
|
103
|
-
*
|
|
102
|
+
* `StackAnimationOptions`: the base of the animations of `push`, `pop` and `setStackRoot`; over the shared elements and
|
|
103
|
+
* element transitions of [LayoutAnimation].
|
|
104
104
|
*/
|
|
105
105
|
open class StackAnimationOptions : LayoutAnimation() {
|
|
106
106
|
@JvmField var enabled: Bool = NullBool()
|
|
@@ -188,6 +188,44 @@ class StackPushAnimationOptions : StackAnimationOptions() {
|
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
+
/**
|
|
192
|
+
* `StackPopAnimationOptions`: the animation of `pop`, and `preview`: whether a back gesture over the stack previews the
|
|
193
|
+
* pop of its top (Android 14+); `false` pops it when the gesture is released, as on Android 13.
|
|
194
|
+
*/
|
|
195
|
+
class StackPopAnimationOptions : StackAnimationOptions() {
|
|
196
|
+
@JvmField var preview: Bool = NullBool()
|
|
197
|
+
|
|
198
|
+
override fun hasValue(): Boolean = super.hasValue() || preview.hasValue()
|
|
199
|
+
|
|
200
|
+
fun mergeWith(other: StackPopAnimationOptions) {
|
|
201
|
+
super.mergeWith(other)
|
|
202
|
+
if (other.preview.hasValue()) preview = other.preview
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
fun mergeWithDefault(defaults: StackPopAnimationOptions) {
|
|
206
|
+
super.mergeWithDefault(defaults)
|
|
207
|
+
if (!preview.hasValue()) preview = defaults.preview
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
override fun copy(): StackPopAnimationOptions = StackPopAnimationOptions().also { it.mergeWith(this) }
|
|
211
|
+
|
|
212
|
+
// Reads the fields of this class from [map], after those of its parent.
|
|
213
|
+
private fun readStackPopAnimationOptions(map: ReadableMap) {
|
|
214
|
+
readStackAnimationOptions(map)
|
|
215
|
+
preview = BoolParser.parse(map, "preview")
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
companion object {
|
|
219
|
+
@JvmStatic
|
|
220
|
+
fun parse(map: ReadableMap?): StackPopAnimationOptions {
|
|
221
|
+
val options = StackPopAnimationOptions()
|
|
222
|
+
if (map == null) return options
|
|
223
|
+
options.readStackPopAnimationOptions(map)
|
|
224
|
+
return options
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
191
229
|
/** `SetStackRootAnimationOptions`: the animation of `setStackRoot`. */
|
|
192
230
|
class SetStackRootAnimationOptions : StackAnimationOptions() {
|
|
193
231
|
@JvmField var asPush: Bool = NullBool()
|
|
@@ -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
|
+
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
package dev.apexnative.navigation.react
|
|
2
2
|
|
|
3
|
-
import android.content.Context
|
|
4
|
-
import com.facebook.react.ReactApplication
|
|
5
3
|
import com.facebook.react.bridge.Arguments
|
|
6
4
|
import com.facebook.react.bridge.WritableMap
|
|
7
5
|
import dev.apexnative.navigation.react.events.EventContract
|
|
@@ -19,11 +17,13 @@ internal object ArrangementStates {
|
|
|
19
17
|
|
|
20
18
|
private val states = ConcurrentHashMap<String, State>()
|
|
21
19
|
|
|
22
|
-
/**
|
|
23
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Keeps the screen's state, and tells JavaScript when it changed and [notify] holds, through [emitter], the one of
|
|
22
|
+
* the JavaScript the screen was made for: silent once it went. Main thread only.
|
|
23
|
+
*/
|
|
24
|
+
fun set(screenId: String, state: State, notify: Boolean, emitter: EventEmitter) {
|
|
24
25
|
if (states.put(screenId, state) == state || !notify) return
|
|
25
|
-
|
|
26
|
-
EventEmitter(reactContext).emitArrangementChanged(screenId, toMap(state))
|
|
26
|
+
emitter.emitArrangementChanged(screenId, toMap(state))
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
/** `{ placement, layout, front, hidden }`, empty for a screen outside a pair. Any thread. */
|
|
@@ -3,13 +3,14 @@ package dev.apexnative.navigation.react
|
|
|
3
3
|
import android.util.Log
|
|
4
4
|
import com.facebook.react.bridge.Promise
|
|
5
5
|
import dev.apexnative.navigation.react.events.EventEmitter
|
|
6
|
-
import dev.apexnative.navigation.react.events.
|
|
6
|
+
import dev.apexnative.navigation.react.events.NavigationState
|
|
7
7
|
import dev.apexnative.navigation.utils.NavigationLog
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* The one answer of a command with a promise: [resolve] or [reject], once; after that, and once the JavaScript it came
|
|
11
|
-
* from went ([generation]), nothing.
|
|
12
|
-
* a [CommandListener] answers through [asListener]. Main
|
|
11
|
+
* from went ([generation]), nothing. It owns the command's one state step ([reportStateNow]). [CommandRunner] makes it
|
|
12
|
+
* on the main thread for each command; area code that takes a [CommandListener] answers through [asListener]. Main
|
|
13
|
+
* thread only.
|
|
13
14
|
*/
|
|
14
15
|
internal class CommandAnswer(
|
|
15
16
|
/** The method of the command: `push`, `setRoot`, … */
|
|
@@ -27,22 +28,39 @@ internal class CommandAnswer(
|
|
|
27
28
|
var isAnswered = false
|
|
28
29
|
private set
|
|
29
30
|
|
|
31
|
+
// The state step of this answer ran ([reportStateNow]).
|
|
32
|
+
private var stateReported = false
|
|
33
|
+
|
|
30
34
|
/** Whether an answer now would reach the JavaScript the command came from. */
|
|
31
35
|
val canAnswer: Boolean
|
|
32
36
|
get() = !isAnswered && ReloadCoordinator.isCurrent(generation)
|
|
33
37
|
|
|
34
38
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
39
|
+
* The navigation state now, sent when it changed, once per answer: the end of the command's transition asks for it
|
|
40
|
+
* after the DidAppear it sends and before the transition's own events (`AppearanceDispatcher.endTransition`);
|
|
41
|
+
* otherwise [resolve] asks for it before the promise. A later call sends nothing, and nothing goes for a JavaScript
|
|
42
|
+
* that went. A failure of the step is logged and costs only the state.
|
|
43
|
+
*/
|
|
44
|
+
fun reportStateNow() {
|
|
45
|
+
if (stateReported) return
|
|
46
|
+
stateReported = true
|
|
47
|
+
if (!ReloadCoordinator.isCurrent(generation)) return
|
|
48
|
+
guarded("report the navigation state") { events.stateNow() }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Resolves the promise with [value]. For the commands that change what shows (`CommandContract.STATE`), the
|
|
53
|
+
* navigation state goes first, when it changed and the command's transition didn't send it already
|
|
54
|
+
* ([reportStateNow]), so JavaScript has it when its `await` goes on; `ANN.CommandCompleted` follows the promise,
|
|
55
|
+
* for the commands that show or dismiss. A failure of either is logged; the promise goes all the same.
|
|
38
56
|
*/
|
|
39
57
|
fun resolve(value: Any?) {
|
|
40
58
|
if (!canAnswer) return
|
|
41
59
|
isAnswered = true
|
|
42
|
-
if (commandName in
|
|
60
|
+
if (commandName in CommandContract.STATE) reportStateNow()
|
|
43
61
|
promise.resolve(value)
|
|
44
62
|
val commandId = commandId
|
|
45
|
-
if (commandName in
|
|
63
|
+
if (commandName in CommandContract.COMPLETED && commandId != null) {
|
|
46
64
|
guarded("send ANN.CommandCompleted") { events.commandCompleted(commandName, commandId, clock()) }
|
|
47
65
|
}
|
|
48
66
|
}
|
|
@@ -65,44 +83,19 @@ internal class CommandAnswer(
|
|
|
65
83
|
log("[ApexNavigation] $commandName: ${ErrorTexts.couldNot(what, ErrorTexts.reasonOf(e))}")
|
|
66
84
|
}
|
|
67
85
|
}
|
|
68
|
-
|
|
69
|
-
companion object {
|
|
70
|
-
// The commands that send `ANN.CommandCompleted` (EVT-CommandCompleted).
|
|
71
|
-
val COMPLETED_COMMANDS = setOf(
|
|
72
|
-
"setRoot",
|
|
73
|
-
"push",
|
|
74
|
-
"pop",
|
|
75
|
-
"popTo",
|
|
76
|
-
"popToRoot",
|
|
77
|
-
"setStackRoot",
|
|
78
|
-
"showInColumn",
|
|
79
|
-
"showModal",
|
|
80
|
-
"dismissModal",
|
|
81
|
-
"dismissAllModals",
|
|
82
|
-
"showOverlay",
|
|
83
|
-
"dismissOverlay",
|
|
84
|
-
"dismissAllOverlays",
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
// The commands whose answer comes after the navigation state: those and `setColumnShown`.
|
|
88
|
-
val STATE_COMMANDS = COMPLETED_COMMANDS + "setColumnShown"
|
|
89
|
-
}
|
|
90
86
|
}
|
|
91
87
|
|
|
92
88
|
/** What goes to JavaScript around the answer of a command. Main thread only. */
|
|
93
89
|
internal interface AnswerEvents {
|
|
94
|
-
/**
|
|
95
|
-
fun
|
|
90
|
+
/** The answer's one state step ([CommandAnswer.reportStateNow]): the navigation state now, when it changed. */
|
|
91
|
+
fun stateNow()
|
|
96
92
|
|
|
97
93
|
/** After the promise resolved: `ANN.CommandCompleted`. */
|
|
98
94
|
fun commandCompleted(commandName: String, commandId: String, completionTime: Long)
|
|
99
95
|
|
|
100
96
|
/** Through the sender of the JavaScript that runs now. */
|
|
101
97
|
object Current : AnswerEvents {
|
|
102
|
-
override fun
|
|
103
|
-
val emitter = EventEmitter.current()
|
|
104
|
-
if (emitter.canSend) NavigationStateReporter.updateNow(emitter)
|
|
105
|
-
}
|
|
98
|
+
override fun stateNow() = NavigationState.updateNow()
|
|
106
99
|
|
|
107
100
|
override fun commandCompleted(commandName: String, commandId: String, completionTime: Long) {
|
|
108
101
|
EventEmitter.current().emitCommandCompleted(commandName, commandId, completionTime)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Made by scripts/contract.mjs from src/interfaces/contract/commands.json: change that file and run the script again.
|
|
2
|
+
|
|
3
|
+
package dev.apexnative.navigation.react
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* What the commands with a promise answer, the same table as iOS. What each resolves with:
|
|
7
|
+
* - `setRoot`: the id of the root, once the root and the layout's modals and overlays show.
|
|
8
|
+
* - `push`: the id of the screen that shows; of a pair, the pair's id.
|
|
9
|
+
* - `preparePush`: the frame `{ width, height, padding }` of the prepared screen, or null; always null on iOS.
|
|
10
|
+
* - `pop`: the id of the screen that went.
|
|
11
|
+
* - `popTo`: the id of the screen it went back to.
|
|
12
|
+
* - `popToRoot`: the id of the first screen of the stack.
|
|
13
|
+
* - `setStackRoot`: the id of the new top screen.
|
|
14
|
+
* - `showModal`: the id of the modal's root.
|
|
15
|
+
* - `dismissModal`: the id of the modal's root.
|
|
16
|
+
* - `dismissAllModals`: an empty string.
|
|
17
|
+
* - `showOverlay`: the id of the overlay's root.
|
|
18
|
+
* - `dismissOverlay`: the id of the overlay's root.
|
|
19
|
+
* - `dismissAllOverlays`: an empty string.
|
|
20
|
+
* - `showInColumn`: the id of the screen that shows.
|
|
21
|
+
* - `setColumnShown`: nothing once the columns stopped moving (`undefined` on iOS, `null` on Android).
|
|
22
|
+
* - `getLaunchLink`: the link that launched the app, or null.
|
|
23
|
+
*/
|
|
24
|
+
internal object CommandContract {
|
|
25
|
+
/** The commands whose answer comes after the navigation state: every one that may change what shows. */
|
|
26
|
+
val STATE: Set<String> = setOf(
|
|
27
|
+
"setRoot",
|
|
28
|
+
"push",
|
|
29
|
+
"pop",
|
|
30
|
+
"popTo",
|
|
31
|
+
"popToRoot",
|
|
32
|
+
"setStackRoot",
|
|
33
|
+
"showModal",
|
|
34
|
+
"dismissModal",
|
|
35
|
+
"dismissAllModals",
|
|
36
|
+
"showOverlay",
|
|
37
|
+
"dismissOverlay",
|
|
38
|
+
"dismissAllOverlays",
|
|
39
|
+
"showInColumn",
|
|
40
|
+
"setColumnShown",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
/** The commands that send `ANN.CommandCompleted` after their answer: every one that shows or dismisses. */
|
|
44
|
+
val COMPLETED: Set<String> = setOf(
|
|
45
|
+
"setRoot",
|
|
46
|
+
"push",
|
|
47
|
+
"pop",
|
|
48
|
+
"popTo",
|
|
49
|
+
"popToRoot",
|
|
50
|
+
"setStackRoot",
|
|
51
|
+
"showModal",
|
|
52
|
+
"dismissModal",
|
|
53
|
+
"dismissAllModals",
|
|
54
|
+
"showOverlay",
|
|
55
|
+
"dismissOverlay",
|
|
56
|
+
"dismissAllOverlays",
|
|
57
|
+
"showInColumn",
|
|
58
|
+
)
|
|
59
|
+
}
|
|
@@ -9,7 +9,6 @@ import android.os.Build
|
|
|
9
9
|
import androidx.lifecycle.DefaultLifecycleObserver
|
|
10
10
|
import androidx.lifecycle.Lifecycle
|
|
11
11
|
import androidx.lifecycle.LifecycleOwner
|
|
12
|
-
import com.facebook.react.ReactApplication
|
|
13
12
|
import com.facebook.react.bridge.Arguments
|
|
14
13
|
import com.facebook.react.bridge.WritableMap
|
|
15
14
|
import dev.apexnative.navigation.react.events.EventContract
|
|
@@ -19,17 +18,23 @@ import java.lang.ref.WeakReference
|
|
|
19
18
|
|
|
20
19
|
/**
|
|
21
20
|
* The device's hinge (`useHinge`): its state from the fold in the activity's window, its angle from the hinge angle
|
|
22
|
-
* sensor (Android 11+). Android has no public state of a closed device: closed is a
|
|
23
|
-
* angle under 90
|
|
24
|
-
* (`setHingeUpdates`), the sensor only while the activity is resumed too; the same value
|
|
25
|
-
*
|
|
21
|
+
* sensor (Android 11+), in degrees to a tenth as on iOS. Android has no public state of a closed device: closed is a
|
|
22
|
+
* window without a fold, or an angle under 90° ([valueOf]). No sensor and no fold: no hinge, `{}`. It runs while
|
|
23
|
+
* JavaScript has a hook mounted (`setHingeUpdates`), the sensor only while the activity is resumed too; the same value
|
|
24
|
+
* is not sent twice. The values go to the JavaScript that turned the updates on (`EventEmitter.current` then): after a
|
|
25
|
+
* reload, until the updates stop, they go nowhere. Main thread only, but [get].
|
|
26
26
|
*/
|
|
27
27
|
internal object Hinge {
|
|
28
|
-
|
|
28
|
+
/** A value of the hinge: its state, and its angle in degrees to a tenth; none while the state is unknown. */
|
|
29
|
+
data class Value(val state: String, val angle: Double?)
|
|
30
|
+
|
|
31
|
+
private const val UNKNOWN = "unknown"
|
|
29
32
|
|
|
30
33
|
private const val CLOSED_BELOW = 90f
|
|
31
34
|
|
|
32
35
|
private var enabled = false
|
|
36
|
+
// The sender of the JavaScript that turned the updates on; none while they are off.
|
|
37
|
+
private var emitter: EventEmitter? = null
|
|
33
38
|
private var activity: WeakReference<Activity>? = null
|
|
34
39
|
private var folds: WindowFolds? = null
|
|
35
40
|
private var sensorManager: SensorManager? = null
|
|
@@ -60,9 +65,11 @@ internal object Hinge {
|
|
|
60
65
|
fun setEnabled(activity: Activity?, enabled: Boolean) {
|
|
61
66
|
this.enabled = enabled
|
|
62
67
|
if (!enabled) {
|
|
68
|
+
emitter = null
|
|
63
69
|
detach()
|
|
64
70
|
return
|
|
65
71
|
}
|
|
72
|
+
emitter = EventEmitter.current()
|
|
66
73
|
activity?.let(::attachTo)
|
|
67
74
|
update()
|
|
68
75
|
}
|
|
@@ -128,28 +135,32 @@ internal object Hinge {
|
|
|
128
135
|
if (!enabled) return
|
|
129
136
|
val folds = folds ?: return
|
|
130
137
|
if (!folds.hasInfo || (listening && angle == null)) return
|
|
131
|
-
val
|
|
132
|
-
val angle = angle
|
|
133
|
-
val value = if (fold == null && sensor == null) {
|
|
134
|
-
null
|
|
135
|
-
} else {
|
|
136
|
-
val state = when {
|
|
137
|
-
fold?.halfOpened == true -> "partiallyOpen"
|
|
138
|
-
fold != null -> "fullyOpen"
|
|
139
|
-
angle != null && angle < CLOSED_BELOW -> "closed"
|
|
140
|
-
else -> "unknown"
|
|
141
|
-
}
|
|
142
|
-
Value(state, angle)
|
|
143
|
-
}
|
|
138
|
+
val value = valueOf(folds.folds.firstOrNull(), sensor != null, angle)
|
|
144
139
|
if (value == last) return
|
|
145
140
|
last = value
|
|
146
|
-
|
|
147
|
-
|
|
141
|
+
emitter?.emitHingeChanged(toMap(value))
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* The value of the window's first [fold] and the sensor's [angle]: none without a fold and without a sensor
|
|
146
|
+
* ([hasSensor]). `closed` without a fold or under 90°, else `partiallyOpen` for a half-opened fold and `fullyOpen`
|
|
147
|
+
* for a flat one. The angle goes to a tenth, as iOS gives it: the sensor's noise doesn't make a new value, and it
|
|
148
|
+
* is left out while the state is unknown.
|
|
149
|
+
*/
|
|
150
|
+
fun valueOf(fold: WindowFolds.Fold?, hasSensor: Boolean, angle: Float?): Value? {
|
|
151
|
+
if (fold == null && !hasSensor) return null
|
|
152
|
+
val state = when {
|
|
153
|
+
fold == null || (angle != null && angle < CLOSED_BELOW) -> "closed"
|
|
154
|
+
fold.halfOpened -> "partiallyOpen"
|
|
155
|
+
else -> "fullyOpen"
|
|
156
|
+
}
|
|
157
|
+
val tenths = angle?.let { Math.round(it * 10.0) / 10.0 }
|
|
158
|
+
return Value(state, tenths.takeIf { state != UNKNOWN })
|
|
148
159
|
}
|
|
149
160
|
|
|
150
161
|
private fun toMap(value: Value?): WritableMap = Arguments.createMap().apply {
|
|
151
162
|
if (value == null) return@apply
|
|
152
163
|
putString(EventContract.HingeChanged.STATE, value.state)
|
|
153
|
-
value.angle?.let { putDouble(EventContract.HingeChanged.ANGLE, it
|
|
164
|
+
if (value.state != UNKNOWN) value.angle?.let { putDouble(EventContract.HingeChanged.ANGLE, it) }
|
|
154
165
|
}
|
|
155
166
|
}
|
|
@@ -211,7 +211,7 @@ class NavigationTurboModule(reactContext: ReactApplicationContext) : NativeANNTu
|
|
|
211
211
|
layout: ReadableMap,
|
|
212
212
|
promise: Promise,
|
|
213
213
|
) {
|
|
214
|
-
// Android
|
|
214
|
+
// Android's split view has the primary and the secondary pane; another column rejects (`showInColumn`).
|
|
215
215
|
val what = "show the screen '${LayoutReader.idOf(layout)}' in the '$column' column"
|
|
216
216
|
runner.run(
|
|
217
217
|
name = "showInColumn",
|
|
@@ -219,7 +219,7 @@ class NavigationTurboModule(reactContext: ReactApplicationContext) : NativeANNTu
|
|
|
219
219
|
what = what,
|
|
220
220
|
promise = promise,
|
|
221
221
|
check = { LayoutReader.node(layout, context = appContext()) },
|
|
222
|
-
) { node, answer -> navigator().stackCommands.showInColumn(componentId, node, answer, what) }
|
|
222
|
+
) { node, answer -> navigator().stackCommands.showInColumn(componentId, column, node, answer, what) }
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
override fun setColumnShown(
|
|
@@ -295,9 +295,10 @@ class NavigationTurboModule(reactContext: ReactApplicationContext) : NativeANNTu
|
|
|
295
295
|
})
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
-
// The navigation of the activity the command runs in; the runner checked there is
|
|
298
|
+
// The navigation of the activity the command runs in; the runner checked there is an activity. One without its
|
|
299
|
+
// navigation yet (or any more) has nowhere to show screens either: the command rejects with the runner's own text.
|
|
299
300
|
private fun navigator(): Navigator =
|
|
300
|
-
|
|
301
|
+
Navigators.current()?.navigator ?: throw LayoutException(ErrorTexts.nowhereToShow())
|
|
301
302
|
|
|
302
303
|
// The app's context, which the colors of the options resolve platform colors with.
|
|
303
304
|
private fun appContext(): Context = reactApplicationContext.applicationContext
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
package dev.apexnative.navigation.react
|
|
2
|
+
|
|
3
|
+
import dev.apexnative.navigation.NavigationApplication
|
|
4
|
+
import dev.apexnative.navigation.utils.UiThread
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The calls JavaScript made to the module that may not have reached the main thread yet: [after] runs a task on the
|
|
8
|
+
* main thread behind every one of them. A screen's first mount reaches the main thread from Fabric while a
|
|
9
|
+
* `mergeOptions` of the same React commit (its layout effects: `useScreenOptions`) may still be on its way: an
|
|
10
|
+
* asynchronous method goes from the JavaScript thread to the module's thread (`BridgelessNativeMethodCallInvoker`),
|
|
11
|
+
* which reads its options there and posts its body to the main thread ([CommandRunner]). The task goes the same way: a
|
|
12
|
+
* turn of the JavaScript thread, which comes after the task of the commit with its layout effects; then the module's
|
|
13
|
+
* thread, behind the calls that task made; then the main thread, behind their bodies. Each of the three runs its queue
|
|
14
|
+
* in order, so every call JavaScript made before the mount has applied when the task runs.
|
|
15
|
+
*
|
|
16
|
+
* Without JavaScript (none yet, a reload, a queue that ended) the task is posted to the main thread at once: nothing is
|
|
17
|
+
* on its way. A queue that ends while the task is on it drops it, and a wait with no limit of its own (a push with
|
|
18
|
+
* `waitForRender: true` whose content has mounted: the task is its only end) would not end; a queue ends only as the
|
|
19
|
+
* JavaScript goes (a reload, the host destroyed), and then the layouts go with it (`ReloadCoordinator`), the stack
|
|
20
|
+
* and its waiting push too. Main thread.
|
|
21
|
+
*/
|
|
22
|
+
internal object PendingCalls {
|
|
23
|
+
/** A queue the task goes through: it runs what it takes in order, and says false when it ended and took nothing. */
|
|
24
|
+
fun interface Queue {
|
|
25
|
+
fun run(task: Runnable): Boolean
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Runs [task] on the main thread, on a later turn, after every call JavaScript made so far has applied there. */
|
|
29
|
+
fun after(task: () -> Unit) {
|
|
30
|
+
val context = NavigationApplication.instanceOrNull?.reactGatewayOrNull?.currentReactContext
|
|
31
|
+
?.takeIf { it.hasActiveReactInstance() }
|
|
32
|
+
val js = context?.jsMessageQueueThread?.let { Queue(it::runOnQueue) }
|
|
33
|
+
val module = context?.nativeModulesMessageQueueThread?.let { Queue(it::runOnQueue) }
|
|
34
|
+
after(js, module, UiThread::post, task)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The way of [after] over the queues given: [js], then [module], then [main]; straight to [main] without one of
|
|
39
|
+
* them, or from where a queue refused it. The unit tests run it over their own queues.
|
|
40
|
+
*/
|
|
41
|
+
fun after(js: Queue?, module: Queue?, main: (Runnable) -> Unit, task: () -> Unit) {
|
|
42
|
+
val onMain = Runnable(task)
|
|
43
|
+
if (js == null || module == null) return main(onMain)
|
|
44
|
+
val toMain = Runnable { main(onMain) }
|
|
45
|
+
val toModule = Runnable { if (!module.run(toMain)) main(onMain) }
|
|
46
|
+
if (!js.run(toModule)) main(onMain)
|
|
47
|
+
}
|
|
48
|
+
}
|