@apexnative/navigation 1.0.0-alpha.3 → 1.0.0-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/android/CLAUDE.md +26 -9
- package/android/CORE.md +32 -17
- package/android/RECIPES.md +9 -3
- package/android/src/main/java/dev/apexnative/navigation/NavigationActivity.kt +25 -9
- package/android/src/main/java/dev/apexnative/navigation/NavigationApplication.kt +34 -13
- package/android/src/main/java/dev/apexnative/navigation/layout/LayoutChecks.kt +5 -1
- package/android/src/main/java/dev/apexnative/navigation/layout/LayoutNode.kt +6 -2
- package/android/src/main/java/dev/apexnative/navigation/nativescreen/NativeScreenContext.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/options/AnimationsOptions.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/options/ButtonMenuOptions.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/options/ButtonOptions.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/options/HardwareBackButtonOptions.kt +9 -1
- package/android/src/main/java/dev/apexnative/navigation/options/OptionsCache.kt +8 -4
- package/android/src/main/java/dev/apexnative/navigation/options/StackAnimationOptions.kt +40 -2
- package/android/src/main/java/dev/apexnative/navigation/options/UserChoices.kt +159 -0
- package/android/src/main/java/dev/apexnative/navigation/react/ArrangementStates.kt +6 -6
- package/android/src/main/java/dev/apexnative/navigation/react/CommandAnswer.kt +29 -36
- package/android/src/main/java/dev/apexnative/navigation/react/CommandContract.kt +59 -0
- package/android/src/main/java/dev/apexnative/navigation/react/Hinge.kt +33 -22
- package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +5 -4
- package/android/src/main/java/dev/apexnative/navigation/react/PendingCalls.kt +48 -0
- package/android/src/main/java/dev/apexnative/navigation/react/ReactView.kt +39 -20
- package/android/src/main/java/dev/apexnative/navigation/react/ReloadCoordinator.kt +7 -4
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenInsets.kt +6 -6
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenRegions.kt +10 -5
- package/android/src/main/java/dev/apexnative/navigation/react/events/AppearanceDispatcher.kt +47 -46
- package/android/src/main/java/dev/apexnative/navigation/react/events/EventEmitter.kt +34 -7
- package/android/src/main/java/dev/apexnative/navigation/react/events/NavigationState.kt +154 -0
- package/android/src/main/java/dev/apexnative/navigation/safearea/SafeAreaView.kt +6 -4
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotContainers.kt +10 -7
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotScreenView.kt +12 -12
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotView.kt +4 -3
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotViews.kt +40 -0
- package/android/src/main/java/dev/apexnative/navigation/utils/ImageKey.kt +66 -0
- package/android/src/main/java/dev/apexnative/navigation/utils/ImageLoader.kt +7 -89
- package/android/src/main/java/dev/apexnative/navigation/utils/ImageRequests.kt +76 -0
- package/android/src/main/java/dev/apexnative/navigation/utils/OptionImages.kt +262 -0
- package/android/src/main/java/dev/apexnative/navigation/utils/ViewController.kt +3 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/arrangement/ArrangementController.kt +3 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/AppliedTabCalls.kt +49 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsController.kt +25 -17
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsPresenter.kt +25 -24
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ComponentViewController.kt +6 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ContentReadiness.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/RenderWait.kt +27 -7
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ScreenReadiness.kt +103 -56
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalCoordinator.kt +5 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/BackCallbackCheck.kt +50 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/BackCallbackState.kt +45 -14
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt +25 -8
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/RootCoordinator.kt +8 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/overlay/OverlayCoordinator.kt +8 -8
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/Capabilities.kt +11 -4
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuController.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitPanes.kt +23 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitViewController.kt +61 -15
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackCommands.kt +28 -18
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +25 -12
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackDisplay.kt +2 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackQueue.kt +85 -29
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabPresenter.kt +7 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/ScreenSearch.kt +3 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/StackSearchPresenter.kt +5 -8
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/BarComponents.kt +5 -51
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/MenuChecks.kt +13 -56
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +14 -11
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/viewcontroller/ViewController.kt +56 -21
- package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/BottomTabs.kt +4 -2
- package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/CustomBottomTabItemView.kt +0 -5
- package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/Fab.kt +14 -10
- package/cli/doctor/checks/app.js +61 -13
- package/cli/doctor/checks/splash.js +4 -22
- package/cli/migrate/native.js +18 -0
- package/cli/setup/source.js +2 -1
- package/cli/setup/steps/mainApplication.js +134 -19
- package/cli/splash/index.js +3 -1
- package/docs-md/architecture/transitions.md +6 -7
- package/docs-md/containers/modals.md +2 -2
- package/docs-md/guides/cli.md +1 -2
- package/docs-md/help/glossary.md +1 -1
- package/docs-md/help/troubleshooting.md +22 -5
- package/docs-md/index.json +4 -0
- package/docs-md/migration/from-react-native-navigation.md +8 -2
- package/docs-md/screen-ui/back-and-gestures.md +24 -2
- package/docs-md/start/installation.md +4 -2
- package/ios/CLAUDE.md +17 -5
- package/ios/CORE.md +18 -13
- package/ios/Contract/ANNCommandAnswer.h +9 -2
- package/ios/Contract/ANNCommandAnswer.mm +28 -30
- package/ios/Contract/ANNCommandContract.h +15 -0
- package/ios/Contract/ANNCommandContract.mm +78 -0
- package/ios/Core/ANNAppearanceDispatcher.h +5 -4
- package/ios/Core/ANNAppearanceDispatcher.mm +12 -1
- package/ios/Core/ANNErrorHandler.h +1 -1
- package/ios/Core/ANNErrorHandler.mm +3 -1
- package/ios/Core/ANNEventEmitter.h +1 -1
- package/ios/Core/ANNEventEmitter.mm +2 -2
- package/ios/Core/ANNJavaScriptCalls.h +34 -0
- package/ios/Core/ANNJavaScriptCalls.mm +61 -0
- package/ios/Core/ANNNavigationState.h +7 -5
- package/ios/Core/ANNNavigationState.mm +8 -7
- package/ios/Core/ANNNodeHelper.h +32 -12
- package/ios/Core/ANNNodeHelper.mm +100 -3
- package/ios/Core/ANNRenderWait.h +15 -1
- package/ios/Core/ANNRenderWait.mm +119 -17
- package/ios/Core/UIViewController+LayoutProtocol.h +9 -2
- package/ios/Core/UIViewController+LayoutProtocol.mm +8 -0
- package/ios/Layouts/Modal/ANNModalCoordinator.h +1 -1
- package/ios/Layouts/Modal/ANNModalCoordinator.mm +55 -14
- package/ios/Layouts/Root/ANNSplashViewController.h +5 -3
- package/ios/Layouts/Root/ANNSplashViewController.mm +147 -0
- package/ios/Layouts/Screen/ANNComponentViewController.h +8 -0
- package/ios/Layouts/Screen/ANNComponentViewController.mm +32 -5
- package/ios/Layouts/Screen/ANNScreenPlace.h +41 -0
- package/ios/Layouts/Screen/ANNScreenPlace.mm +175 -0
- package/ios/Layouts/SplitView/ANNSplitViewCommands.h +3 -2
- package/ios/Layouts/SplitView/ANNSplitViewCommands.mm +105 -39
- package/ios/Layouts/SplitView/ANNSplitViewController.mm +4 -2
- package/ios/Layouts/Stack/ANNPreviewPush.h +5 -5
- package/ios/Layouts/Stack/ANNPreviewPush.mm +85 -22
- package/ios/Layouts/Stack/ANNStackCommands.mm +1 -0
- package/ios/Layouts/Stack/ANNStackController.mm +26 -3
- package/ios/Layouts/Stack/ANNStackQueue.h +3 -1
- package/ios/Layouts/Stack/ANNStackQueue.mm +56 -43
- package/ios/Layouts/Stack/UINavigationController+ANNCommands.h +4 -2
- package/ios/Layouts/Stack/UINavigationController+ANNCommands.mm +2 -3
- package/ios/Layouts/Tabs/ANNBottomTabItemBuilder.h +4 -0
- package/ios/Layouts/Tabs/ANNBottomTabItemBuilder.mm +11 -0
- package/ios/Layouts/Tabs/ANNBottomTabsController.mm +13 -5
- package/ios/Layouts/Tabs/ANNBottomTabsPresenter.h +2 -2
- package/ios/Layouts/Tabs/ANNBottomTabsPresenter.mm +109 -7
- package/ios/Options/ANNOptionsCache.h +7 -6
- package/ios/Options/ANNSearchBarOptions.h +4 -3
- package/ios/Options/ANNUserChoice.h +57 -0
- package/ios/Options/ANNUserChoice.mm +44 -0
- package/ios/Options/Params/ANNImageSource.h +61 -0
- package/ios/Options/Params/ANNImageSource.mm +101 -0
- package/ios/Options/Params/ANNOptionImages.h +37 -0
- package/ios/Options/Params/ANNOptionImages.mm +284 -0
- package/ios/Options/Params/Image.h +11 -2
- package/ios/Options/Params/Image.mm +15 -2
- package/ios/Options/Params/ImageParser.h +5 -5
- package/ios/Options/Params/ImageParser.mm +28 -46
- package/ios/Public/ApexNavigation/ApexNavigation.h +1 -1
- package/ios/RECIPES.md +11 -5
- package/ios/React/Slots/ANNSlotComponentView.mm +24 -17
- package/ios/React/Slots/ANNSlotContainers.h +14 -6
- package/ios/React/Slots/ANNSlotContainers.mm +75 -2
- package/ios/TopBar/ANNBarButtons.h +9 -5
- package/ios/TopBar/ANNBarButtons.mm +211 -63
- package/ios/TopBar/ANNIconButtonImage.h +1 -0
- package/ios/TopBar/ANNIconButtonImage.mm +34 -1
- package/ios/TopBar/ANNLargeTitlePresenter.h +13 -0
- package/ios/TopBar/ANNLargeTitlePresenter.mm +26 -3
- package/ios/TopBar/ANNSearchPresenter.mm +170 -19
- package/ios/TopBar/ANNTopBarPresenter.h +5 -0
- package/ios/TopBar/ANNTopBarPresenter.mm +60 -11
- package/ios/TurboModules/ANNTurboModule.mm +39 -21
- package/lib/module/ARCHITECTURE.md +18 -18
- package/lib/module/adapters/NativeANNTurboModule.js.map +1 -1
- package/lib/module/api/commands/commands.js +54 -6
- package/lib/module/api/commands/commands.js.map +1 -1
- package/lib/module/api/navigation.js.map +1 -1
- package/lib/module/engine/commands.js +9 -7
- package/lib/module/engine/commands.js.map +1 -1
- package/lib/module/engine/layout.js +13 -9
- package/lib/module/engine/layout.js.map +1 -1
- package/lib/module/engine/options.js +44 -11
- package/lib/module/engine/options.js.map +1 -1
- package/lib/module/engine/places.js +52 -7
- package/lib/module/engine/places.js.map +1 -1
- package/lib/module/engine/props.js +30 -1
- package/lib/module/engine/props.js.map +1 -1
- package/lib/module/engine/rememberedOptions.js +1 -1
- package/lib/module/engine/rememberedOptions.js.map +1 -1
- package/lib/module/engine/screens.js +22 -12
- package/lib/module/engine/screens.js.map +1 -1
- package/lib/module/interfaces/contract/commands.json +101 -0
- package/lib/typescript/adapters/NativeANNTurboModule.d.ts +5 -2
- package/lib/typescript/adapters/NativeANNTurboModule.d.ts.map +1 -1
- package/lib/typescript/api/commands/commands.d.ts.map +1 -1
- package/lib/typescript/api/commands/presentation.d.ts +4 -3
- package/lib/typescript/api/commands/presentation.d.ts.map +1 -1
- package/lib/typescript/api/events/navigationEvents.d.ts +1 -1
- package/lib/typescript/api/events/navigationEvents.d.ts.map +1 -1
- package/lib/typescript/api/hooks/useNavigation.d.ts +1 -1
- package/lib/typescript/api/hooks/useNavigation.d.ts.map +1 -1
- package/lib/typescript/api/navigation.d.ts +4 -1
- package/lib/typescript/api/navigation.d.ts.map +1 -1
- package/lib/typescript/engine/commands.d.ts.map +1 -1
- package/lib/typescript/engine/options.d.ts +5 -2
- package/lib/typescript/engine/options.d.ts.map +1 -1
- package/lib/typescript/engine/places.d.ts +7 -2
- package/lib/typescript/engine/places.d.ts.map +1 -1
- package/lib/typescript/engine/props.d.ts +8 -1
- package/lib/typescript/engine/props.d.ts.map +1 -1
- package/lib/typescript/engine/rememberedOptions.d.ts.map +1 -1
- package/lib/typescript/engine/screens.d.ts.map +1 -1
- package/lib/typescript/interfaces/ComponentEvents.d.ts +2 -2
- package/lib/typescript/interfaces/ComponentEvents.d.ts.map +1 -1
- package/lib/typescript/interfaces/Events.d.ts +19 -10
- package/lib/typescript/interfaces/Events.d.ts.map +1 -1
- package/lib/typescript/interfaces/Options.d.ts +1 -1
- package/lib/typescript/interfaces/Options.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/animations.d.ts +23 -4
- package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/buttons.d.ts +13 -5
- package/lib/typescript/interfaces/options/buttons.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/stack.d.ts +9 -0
- package/lib/typescript/interfaces/options/stack.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/topBar.d.ts +22 -9
- package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/ARCHITECTURE.md +18 -18
- package/src/adapters/NativeANNTurboModule.ts +5 -2
- package/src/api/commands/commands.ts +48 -7
- package/src/api/commands/presentation.ts +4 -3
- package/src/api/events/navigationEvents.ts +1 -1
- package/src/api/hooks/useNavigation.ts +1 -1
- package/src/api/navigation.ts +4 -1
- package/src/engine/commands.ts +19 -7
- package/src/engine/layout.ts +16 -9
- package/src/engine/options.ts +39 -11
- package/src/engine/places.ts +65 -7
- package/src/engine/props.ts +30 -1
- package/src/engine/rememberedOptions.ts +4 -1
- package/src/engine/screens.tsx +23 -13
- package/src/interfaces/ComponentEvents.ts +2 -2
- package/src/interfaces/Events.ts +19 -10
- package/src/interfaces/Options.ts +1 -0
- package/src/interfaces/contract/commands.json +101 -0
- package/src/interfaces/options/animations.ts +24 -4
- package/src/interfaces/options/buttons.ts +13 -5
- package/src/interfaces/options/stack.ts +9 -0
- package/src/interfaces/options/topBar.ts +22 -9
- package/android/src/main/java/dev/apexnative/navigation/react/events/NavigationStateReporter.kt +0 -62
- package/android/src/main/java/dev/apexnative/navigation/react/events/StateHold.kt +0 -62
package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/RenderWait.kt
CHANGED
|
@@ -16,8 +16,10 @@ import dev.apexnative.navigation.viewcontrollers.viewcontroller.ViewController
|
|
|
16
16
|
* without a limit.
|
|
17
17
|
*
|
|
18
18
|
* What differs between its users are its values, never its steps: the readiness and the clock (fakes in the unit
|
|
19
|
-
* tests), [countsUnknownContent], how a readiness signal is delivered ([deliver]: at once,
|
|
20
|
-
*
|
|
19
|
+
* tests), [countsUnknownContent], how a readiness signal is delivered ([deliver]: at once, posted to the next turn of
|
|
20
|
+
* the main loop, or behind the calls of JavaScript), whether content ready at once is delivered too
|
|
21
|
+
* ([deliversReadyContent]), and [step], the guard the user runs every later step in (a timer, a signal). Main thread
|
|
22
|
+
* only.
|
|
21
23
|
*/
|
|
22
24
|
internal class RenderWait(
|
|
23
25
|
private val readiness: Readiness,
|
|
@@ -32,6 +34,12 @@ internal class RenderWait(
|
|
|
32
34
|
private val countsUnknownContent: Boolean,
|
|
33
35
|
/** How a readiness signal reaches the wait: at once by default; posted, when a signal may come inside a layout. */
|
|
34
36
|
private val deliver: (() -> Unit) -> Unit = { it() },
|
|
37
|
+
/**
|
|
38
|
+
* Content ready when the wait starts (or retakes) still reaches its end through [deliver], within the limit that
|
|
39
|
+
* runs: what [deliver] waits behind (a push's: the calls JavaScript made in the commit of that content) comes
|
|
40
|
+
* first. Off: such content ends the wait at once.
|
|
41
|
+
*/
|
|
42
|
+
private val deliversReadyContent: Boolean = false,
|
|
35
43
|
/** Runs a later step of the wait (a timer, a signal): the user's guards and protection; as it is by default. */
|
|
36
44
|
private val step: (() -> Unit) -> Unit = { it() },
|
|
37
45
|
private val onEnd: () -> Unit,
|
|
@@ -70,7 +78,8 @@ internal class RenderWait(
|
|
|
70
78
|
|
|
71
79
|
/**
|
|
72
80
|
* Starts waiting for [screens], and for the content of [unseen] within its first [UNSEEN_LIMIT_MILLIS]; with all
|
|
73
|
-
* of them ready (and their bars' components, when they count within the limit) it ends at once, before it returns
|
|
81
|
+
* of them ready (and their bars' components, when they count within the limit) it ends at once, before it returns,
|
|
82
|
+
* or through [deliver] with [deliversReadyContent].
|
|
74
83
|
*/
|
|
75
84
|
fun start(screens: List<ViewController<*>>, unseen: List<ViewController<*>> = emptyList()) {
|
|
76
85
|
this.screens = screens
|
|
@@ -78,15 +87,18 @@ internal class RenderWait(
|
|
|
78
87
|
unseenDone = unseen.isEmpty()
|
|
79
88
|
waiting = true
|
|
80
89
|
token = Any()
|
|
81
|
-
// Content ready at once ends the wait, unless the bar's components count within a limit
|
|
82
|
-
|
|
90
|
+
// Content ready at once ends the wait, unless the bar's components count within a limit (it runs for them) or
|
|
91
|
+
// the content goes through [deliver] too.
|
|
92
|
+
val ready = contentReady()
|
|
93
|
+
if (ready && !deliversReadyContent && !(bar == Bar.WithinLimit && limitMillis != null)) return contentDone()
|
|
94
|
+
// The timers first: a [deliver] that runs at once may end the wait before this returns.
|
|
83
95
|
cancelContentTimer = when {
|
|
84
96
|
limitMillis != null -> scheduler.schedule(limitMillis) { step { atLimit() } }
|
|
85
97
|
countsUnknownContent -> scheduler.schedule(UNKNOWN_CONTENT_MILLIS) { step { atUnknownContentLimit() } }
|
|
86
98
|
else -> null
|
|
87
99
|
}
|
|
88
100
|
startUnseenLimit()
|
|
89
|
-
await()
|
|
101
|
+
if (ready && deliversReadyContent) deliverReady() else await()
|
|
90
102
|
}
|
|
91
103
|
|
|
92
104
|
/**
|
|
@@ -98,7 +110,7 @@ internal class RenderWait(
|
|
|
98
110
|
this.screens = screens
|
|
99
111
|
this.unseen = unseen
|
|
100
112
|
token = Any()
|
|
101
|
-
await()
|
|
113
|
+
if (deliversReadyContent && contentReady()) deliverReady() else await()
|
|
102
114
|
}
|
|
103
115
|
|
|
104
116
|
/** Ends the wait without [onEnd]: its timers stop, and the signals still to come do nothing. */
|
|
@@ -129,6 +141,14 @@ internal class RenderWait(
|
|
|
129
141
|
if (contentReady() && cancelBarPhase == null) contentDone()
|
|
130
142
|
}
|
|
131
143
|
|
|
144
|
+
// The content is ready already: it goes once through [deliver] as a signal would, then on as [await] says.
|
|
145
|
+
private fun deliverReady() {
|
|
146
|
+
val token = token
|
|
147
|
+
deliver {
|
|
148
|
+
step { if (waiting && this.token === token) await() }
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
132
152
|
// The content of every screen ready: on to the bars; else the signals of those not ready.
|
|
133
153
|
private fun await() {
|
|
134
154
|
if (contentReady()) return contentDone()
|
package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ScreenReadiness.kt
CHANGED
|
@@ -1,93 +1,128 @@
|
|
|
1
1
|
package dev.apexnative.navigation.viewcontrollers.component
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* The readiness of each screen by its id: the safe area view of a screen (`ANNSafeAreaView`) tells it here that it
|
|
5
|
-
* laid out in its place and that the screen's content rendered into it, and asks whether the
|
|
6
|
-
* all. The screen
|
|
7
|
-
* thread only.
|
|
4
|
+
* The readiness of each screen by its id: the safe area view of a screen (`ANNSafeAreaView`) tells it here that it
|
|
5
|
+
* has one, that it is laid out in its place and that the screen's content rendered into it, and asks whether the
|
|
6
|
+
* screen waits for that at all. The content of a screen or of a component listens by the id: no view walks up or down
|
|
7
|
+
* the views to find the other. Main thread only.
|
|
8
|
+
*
|
|
9
|
+
* As on iOS (`ANNScreenReadiness`), what a safe area view said is kept: a listener that comes later hears it at once.
|
|
10
|
+
* Several listeners may wait on one id (the roots of a bar component that several screens show with one id), each
|
|
11
|
+
* under a key of its own, which removes it. Each signal carries its source, the React surface of the safe area view
|
|
12
|
+
* that gave it, and a listener hears only the sources it [accepts][Listener.accepts]: React Native never gives a
|
|
13
|
+
* surface's id twice in a process, so the late signal of another root with the same id (a root of the JavaScript that
|
|
14
|
+
* went, a destroyed screen, another live root of a bar component) never makes a new root ready.
|
|
8
15
|
*/
|
|
9
16
|
object ScreenReadiness {
|
|
10
|
-
/**
|
|
17
|
+
/** The source of a signal that names none: a listener that accepts every source hears it. */
|
|
18
|
+
const val NO_SOURCE = -1
|
|
19
|
+
|
|
20
|
+
/** What the content of a screen or of a component hears from the safe area view of its id. */
|
|
11
21
|
interface Listener {
|
|
22
|
+
/**
|
|
23
|
+
* Whether a signal of [source] (the id of the React surface of the safe area view that gave it) is this
|
|
24
|
+
* listener's: by default every one is.
|
|
25
|
+
*/
|
|
26
|
+
fun accepts(source: Int): Boolean = true
|
|
27
|
+
|
|
12
28
|
/** The safe area view is laid out in its place, with a size. */
|
|
13
|
-
fun onLaidOut()
|
|
29
|
+
fun onLaidOut() = Unit
|
|
14
30
|
|
|
15
31
|
/** The screen rendered its content into its safe area view, also when it renders nothing (`null`). */
|
|
16
32
|
fun onContentRendered()
|
|
17
33
|
}
|
|
18
34
|
|
|
19
|
-
|
|
20
|
-
private
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
35
|
+
// What the safe area views of one id said: that one exists, and the sources that are laid out and rendered.
|
|
36
|
+
private class Entry {
|
|
37
|
+
var wrapped = false
|
|
38
|
+
val laidOut = HashSet<Int>()
|
|
39
|
+
val rendered = HashSet<Int>()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private val entries = HashMap<String, Entry>()
|
|
43
|
+
private val listeners = HashMap<String, LinkedHashMap<Any, Listener>>()
|
|
44
|
+
// The roots that render at once, by id: an id renders at once while one of them lives.
|
|
45
|
+
private val atOnce = HashMap<String, MutableSet<Any>>()
|
|
24
46
|
|
|
25
47
|
/**
|
|
26
48
|
* Whether the screen [screenId] waits for its safe area view to be laid out before its content renders: every
|
|
27
|
-
* screen does, as on iOS, unless
|
|
28
|
-
*
|
|
49
|
+
* screen does, as on iOS, unless a root of the id renders at once ([setRendersAtOnce]) — a React component of a
|
|
50
|
+
* bar or a tab item, whose size comes from its content.
|
|
29
51
|
*/
|
|
30
|
-
fun waitsForReadiness(screenId: String): Boolean = screenId
|
|
52
|
+
fun waitsForReadiness(screenId: String): Boolean = atOnce[screenId].isNullOrEmpty()
|
|
31
53
|
|
|
32
|
-
/**
|
|
33
|
-
|
|
34
|
-
|
|
54
|
+
/**
|
|
55
|
+
* The root [key] of [screenId] renders at once (a component of a bar, a tab item), or stopped. The id renders at
|
|
56
|
+
* once while a root marks it; when the last one stops, what was said of the id goes too, so a later root with it
|
|
57
|
+
* starts anew.
|
|
58
|
+
*/
|
|
59
|
+
fun setRendersAtOnce(screenId: String, key: Any, rendersAtOnce: Boolean) {
|
|
60
|
+
if (rendersAtOnce) {
|
|
61
|
+
atOnce.getOrPut(screenId, ::HashSet).add(key)
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
val keys = atOnce[screenId] ?: return
|
|
65
|
+
if (!keys.remove(key) || keys.isNotEmpty()) return
|
|
66
|
+
atOnce.remove(screenId)
|
|
67
|
+
entries.remove(screenId)
|
|
35
68
|
}
|
|
36
69
|
|
|
37
70
|
/** The screen [screenId] has a safe area view: it is rendered only once that view says so. */
|
|
38
71
|
fun wrapped(screenId: String) {
|
|
39
|
-
wrapped
|
|
72
|
+
entryOf(screenId).wrapped = true
|
|
40
73
|
}
|
|
41
74
|
|
|
42
|
-
/** The safe area view of [screenId] is laid out in its place, with a size. */
|
|
43
|
-
fun laidOut(screenId: String) {
|
|
44
|
-
|
|
75
|
+
/** The safe area view of [screenId], in the React surface [source], is laid out in its place, with a size. */
|
|
76
|
+
fun laidOut(screenId: String, source: Int = NO_SOURCE) {
|
|
77
|
+
val entry = entryOf(screenId)
|
|
78
|
+
entry.wrapped = true
|
|
79
|
+
if (entry.laidOut.add(source)) tell(screenId, source, Listener::onLaidOut)
|
|
45
80
|
}
|
|
46
81
|
|
|
47
|
-
/** The screen [screenId] rendered its content into its safe area view. */
|
|
48
|
-
fun contentRendered(screenId: String) {
|
|
49
|
-
|
|
82
|
+
/** The screen [screenId] rendered its content into its safe area view, in the React surface [source]. */
|
|
83
|
+
fun contentRendered(screenId: String, source: Int = NO_SOURCE) {
|
|
84
|
+
val entry = entryOf(screenId)
|
|
85
|
+
entry.wrapped = true
|
|
86
|
+
if (entry.rendered.add(source)) tell(screenId, source, Listener::onContentRendered)
|
|
50
87
|
}
|
|
51
88
|
|
|
52
89
|
/**
|
|
53
|
-
* Whether the screen [screenId] rendered its content into its safe area view
|
|
54
|
-
* anything of
|
|
90
|
+
* Whether the screen [screenId] rendered its content into its safe area view, in a source [listener] accepts (any
|
|
91
|
+
* source without one); null when no safe area view told anything of the id (a screen without one, of the old API:
|
|
92
|
+
* its content decides by its own children).
|
|
55
93
|
*/
|
|
56
|
-
fun isContentRendered(screenId: String
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
else -> null
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** [listener] hears the signals of [screenId] from now on; null stops it. */
|
|
63
|
-
fun setListener(screenId: String, listener: Listener?) {
|
|
64
|
-
if (listener == null) listeners.remove(screenId) else listeners[screenId] = listener
|
|
94
|
+
fun isContentRendered(screenId: String, listener: Listener? = null): Boolean? {
|
|
95
|
+
val entry = entries[screenId] ?: return null
|
|
96
|
+
return entry.rendered.any { listener?.accepts(it) ?: true }
|
|
65
97
|
}
|
|
66
98
|
|
|
67
99
|
/**
|
|
68
|
-
* [listener]
|
|
69
|
-
*
|
|
100
|
+
* [listener] hears the signals of [screenId] under [key] (its owner) until [removeListener] with the key: a key
|
|
101
|
+
* has one listener per id, the other keys keep theirs. What the safe area views said before reaches it at once, in
|
|
102
|
+
* order, for the sources it accepts.
|
|
70
103
|
*/
|
|
71
|
-
fun
|
|
72
|
-
|
|
104
|
+
fun addListener(screenId: String, key: Any, listener: Listener) {
|
|
105
|
+
listeners.getOrPut(screenId, ::LinkedHashMap)[key] = listener
|
|
106
|
+
val entry = entries[screenId] ?: return
|
|
107
|
+
if (entry.laidOut.any(listener::accepts) && isListening(screenId, key, listener)) listener.onLaidOut()
|
|
108
|
+
if (entry.rendered.any(listener::accepts) && isListening(screenId, key, listener)) listener.onContentRendered()
|
|
73
109
|
}
|
|
74
110
|
|
|
75
|
-
/**
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
laidOut -= screenId
|
|
81
|
-
rendered -= screenId
|
|
111
|
+
/** The listener of [key] stops hearing [screenId]; those of other keys (a later root with the id) keep theirs. */
|
|
112
|
+
fun removeListener(screenId: String, key: Any) {
|
|
113
|
+
val keys = listeners[screenId] ?: return
|
|
114
|
+
keys.remove(key)
|
|
115
|
+
if (keys.isEmpty()) listeners.remove(screenId)
|
|
82
116
|
}
|
|
83
117
|
|
|
84
|
-
/**
|
|
118
|
+
/**
|
|
119
|
+
* The screen [screenId] owns the id from now on (made), or went while no other node has it (destroyed): what its
|
|
120
|
+
* safe area views said and the marks of rendering at once go, so a later screen with the id starts anew. The
|
|
121
|
+
* listeners stay with their keys, which remove them.
|
|
122
|
+
*/
|
|
85
123
|
fun remove(screenId: String) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
laidOut -= screenId
|
|
89
|
-
rendered -= screenId
|
|
90
|
-
listeners -= screenId
|
|
124
|
+
entries.remove(screenId)
|
|
125
|
+
atOnce.remove(screenId)
|
|
91
126
|
}
|
|
92
127
|
|
|
93
128
|
/**
|
|
@@ -95,10 +130,22 @@ object ScreenReadiness {
|
|
|
95
130
|
* "rendered" kept for a screen of the old one would make its new screen with the same id ready at once.
|
|
96
131
|
*/
|
|
97
132
|
fun reset() {
|
|
98
|
-
|
|
99
|
-
wrapped.clear()
|
|
100
|
-
laidOut.clear()
|
|
101
|
-
rendered.clear()
|
|
133
|
+
entries.clear()
|
|
102
134
|
listeners.clear()
|
|
135
|
+
atOnce.clear()
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private fun entryOf(screenId: String): Entry = entries.getOrPut(screenId, ::Entry)
|
|
139
|
+
|
|
140
|
+
private fun isListening(screenId: String, key: Any, listener: Listener): Boolean =
|
|
141
|
+
listeners[screenId]?.get(key) === listener
|
|
142
|
+
|
|
143
|
+
// Each listener of the id that accepts the source, as they are now: one that a listener before it removed hears
|
|
144
|
+
// nothing.
|
|
145
|
+
private fun tell(screenId: String, source: Int, signal: (Listener) -> Unit) {
|
|
146
|
+
val now = listeners[screenId]?.entries?.toList() ?: return
|
|
147
|
+
for ((key, listener) in now) {
|
|
148
|
+
if (isListening(screenId, key, listener) && listener.accepts(source)) signal(listener)
|
|
149
|
+
}
|
|
103
150
|
}
|
|
104
151
|
}
|
package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalCoordinator.kt
CHANGED
|
@@ -731,7 +731,7 @@ internal class ModalCoordinator(
|
|
|
731
731
|
// without the events not sent yet; what waits for it rejects, the layer it took out comes back, and the modals that
|
|
732
732
|
// wait go on.
|
|
733
733
|
private fun dismissFailed(record: ModalRecord, e: Throwable) {
|
|
734
|
-
val text = ErrorTexts.couldNot("dismiss the modal
|
|
734
|
+
val text = ErrorTexts.couldNot("dismiss the modal '${record.rootId}'", ErrorTexts.reasonOf(e))
|
|
735
735
|
val listed = record in records
|
|
736
736
|
val under = layerUnder(record)
|
|
737
737
|
runCatching { record.stopAnimation?.invoke() }
|
|
@@ -835,8 +835,8 @@ internal class ModalCoordinator(
|
|
|
835
835
|
State.Presenting -> record.waitingDismissals += dismissal
|
|
836
836
|
State.Shown -> if (isTop(record)) beginDismissal(record, dismissal) else closeBelow(record, dismissal)
|
|
837
837
|
State.Dismissing -> dismissal.answer?.let(record.answers::add)
|
|
838
|
-
|
|
839
|
-
State.WithOldRoot, State.Gone -> dismissal.answer?.
|
|
838
|
+
// It went already, or goes with the old root: the dismissal just completes, as on iOS.
|
|
839
|
+
State.ClosedBelow, State.WithOldRoot, State.Gone -> dismissal.answer?.resolve(record.rootId)
|
|
840
840
|
}
|
|
841
841
|
}
|
|
842
842
|
|
|
@@ -850,7 +850,7 @@ internal class ModalCoordinator(
|
|
|
850
850
|
record.state = State.Gone
|
|
851
851
|
records.remove(record)
|
|
852
852
|
views.remove(record)
|
|
853
|
-
val screen = AppearanceDispatcher.
|
|
853
|
+
val screen = AppearanceDispatcher.modalScreen(record.root)
|
|
854
854
|
record.showAnswer?.resolve(record.rootId)
|
|
855
855
|
record.showAnswer = null
|
|
856
856
|
record.chain?.let {
|
|
@@ -1012,7 +1012,7 @@ internal class ModalCoordinator(
|
|
|
1012
1012
|
val wasOut = isOut(revealed)
|
|
1013
1013
|
val counted = members.filter { it.state != State.ClosedBelow && !it.reported }
|
|
1014
1014
|
batch.count = counted.size
|
|
1015
|
-
batch.screen = counted.lastOrNull()?.let { AppearanceDispatcher.
|
|
1015
|
+
batch.screen = counted.lastOrNull()?.let { AppearanceDispatcher.modalScreen(it.root) }
|
|
1016
1016
|
// The top that leaves with its animation: one already leaving keeps its own.
|
|
1017
1017
|
val top = members.lastOrNull { it.state in ANIMATED_IN_BATCH }
|
|
1018
1018
|
batch.top = top
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
package dev.apexnative.navigation.viewcontrollers.navigator
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
import dev.apexnative.navigation.utils.NavigationLog
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The check of [BackCallbackState]'s triggers in debug builds (`NavigationActivity` makes it only when
|
|
8
|
+
* `BuildConfig.DEBUG`): it compares an activity's back callback with what back does now
|
|
9
|
+
* ([BackCallbackState.mismatchOf]) and writes a mismatch to the log once per state, with the screen on top. It never
|
|
10
|
+
* changes the callback, so a missing trigger behaves as in a release build. A [sample] before a frame runs at most
|
|
11
|
+
* every [SAMPLE_MILLIS] and reports a mismatch only when the sample before saw the same one: a change whose trigger
|
|
12
|
+
* comes at the end of its motion (a pane that slides, a menu dragged open) isn't reported while it moves. A back event
|
|
13
|
+
* ([atBack]) reports at once. Main thread only.
|
|
14
|
+
*/
|
|
15
|
+
internal class BackCallbackCheck(
|
|
16
|
+
private val log: (String) -> Unit = { Log.w(NavigationLog.TAG, it) },
|
|
17
|
+
) {
|
|
18
|
+
private var lastSample: Long? = null
|
|
19
|
+
private var previous: String? = null
|
|
20
|
+
private val logged = HashSet<String>()
|
|
21
|
+
|
|
22
|
+
/** Before a frame of the activity of [navigator], at [now] (`SystemClock.uptimeMillis`). */
|
|
23
|
+
fun sample(navigator: Navigator, now: Long) {
|
|
24
|
+
val last = lastSample
|
|
25
|
+
if (last != null && now - last < SAMPLE_MILLIS) return
|
|
26
|
+
lastSample = now
|
|
27
|
+
val mismatch = BackCallbackState.mismatchOf(navigator)
|
|
28
|
+
if (mismatch != null && mismatch == previous) report(mismatch)
|
|
29
|
+
previous = mismatch
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** A back event reached the callback of the activity of [navigator], before it acts. */
|
|
33
|
+
fun atBack(navigator: Navigator) {
|
|
34
|
+
BackCallbackState.mismatchOf(navigator)?.let(::report)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Once per state, and not without end.
|
|
38
|
+
private fun report(mismatch: String) {
|
|
39
|
+
if (logged.size >= MAX_LOGGED || !logged.add(mismatch)) return
|
|
40
|
+
log("[ApexNavigation] $mismatch: a change of what back does came without its trigger")
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
companion object {
|
|
44
|
+
/** The least time between two samples. */
|
|
45
|
+
const val SAMPLE_MILLIS = 500L
|
|
46
|
+
|
|
47
|
+
// The states logged at most: a debug check, not a second log.
|
|
48
|
+
private const val MAX_LOGGED = 32
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -2,21 +2,38 @@ package dev.apexnative.navigation.viewcontrollers.navigator
|
|
|
2
2
|
|
|
3
3
|
import android.util.Log
|
|
4
4
|
import androidx.activity.OnBackPressedCallback
|
|
5
|
+
import dev.apexnative.navigation.react.events.AppearanceDispatcher
|
|
5
6
|
import dev.apexnative.navigation.utils.NavigationLog
|
|
6
7
|
import java.lang.ref.WeakReference
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Keeps the back callback of each navigation activity enabled only while back has something to do in the app: its
|
|
10
|
-
* navigation can go back ([Navigator.canHandleBack]),
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* navigation can go back ([Navigator.canHandleBack]), the screen back acts on keeps it from the system
|
|
12
|
+
* (`hardwareBackButton.systemAnimation: false`, [Navigator.keepsBackFromSystem]), or JavaScript listens to it
|
|
13
|
+
* (`BackHandler`). Otherwise the callback is disabled and the system does its own back. The system plays its back
|
|
14
|
+
* animation (to the home screen, to another task) only when no callback of the app is enabled as the gesture starts,
|
|
15
|
+
* and it decides that before the app hears of the gesture.
|
|
14
16
|
*
|
|
15
17
|
* A stale "enabled" only loses that animation; a stale "disabled" would leave the app where back has to go back. So
|
|
16
|
-
* the value is computed again
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
18
|
+
* the value is computed again on every event that can change what back does, never by walking the layouts before
|
|
19
|
+
* each frame:
|
|
20
|
+
* - the end of each command's main-thread task, `setDefaultOptions` and `mergeOptions` too (`CommandRunner`);
|
|
21
|
+
* - a stack's operation accepted, its current child changed, and the operation ended or rejected
|
|
22
|
+
* (`StackController`'s queue host), which covers a push or a `setStackRoot` that waited for its screen, a prepared
|
|
23
|
+
* push, back of the user and the back arrow;
|
|
24
|
+
* - a layout that changed the screens it shows (`Navigator.shownScreensChanged`: the tabs, a side menu, a split view,
|
|
25
|
+
* a pair) and the end of a tab's switch;
|
|
26
|
+
* - every change of the modals (`ModalCoordinator.Host.backChanged`): a show, a dismissal by a command, by back or by
|
|
27
|
+
* the user (a sheet dragged down, its scrim), a failure, a `setRoot` that takes them;
|
|
28
|
+
* - a root that changed or showed, and the layers that went (a reload: `Navigator.destroy`);
|
|
29
|
+
* - a side menu, the panes of a split view or a search that opened or closed (the first two after their last frame),
|
|
30
|
+
* and a split view's width that changed;
|
|
31
|
+
* - the release and the cancel of a back gesture, and each back press;
|
|
32
|
+
* - JavaScript's listeners ([setJsListeners]), and the activity resumed or focused, as a safety net.
|
|
33
|
+
*
|
|
34
|
+
* Overlays don't count: back never goes to them. Each activity has its own navigator and callback; the listeners of
|
|
35
|
+
* JavaScript count for all of them. Debug builds check the triggers ([mismatchOf], `BackCallbackCheck`). Main thread
|
|
36
|
+
* only.
|
|
20
37
|
*/
|
|
21
38
|
internal object BackCallbackState {
|
|
22
39
|
private class Entry(navigator: Navigator, callback: OnBackPressedCallback) {
|
|
@@ -56,22 +73,36 @@ internal object BackCallbackState {
|
|
|
56
73
|
for (entry in entries.toList()) update(entry)
|
|
57
74
|
}
|
|
58
75
|
|
|
59
|
-
/** The callback of the activity of [navigator]:
|
|
76
|
+
/** The callback of the activity of [navigator]: after a change in its navigation. */
|
|
60
77
|
fun update(navigator: Navigator) {
|
|
61
78
|
entries.firstOrNull { it.navigator.get() === navigator }?.let(::update)
|
|
62
79
|
}
|
|
63
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Debug builds only: a callback of [navigator]'s activity that differs from what back does now — a change without
|
|
83
|
+
* its trigger —, described with the screen on top; null when it is right or not attached. It changes nothing.
|
|
84
|
+
*/
|
|
85
|
+
fun mismatchOf(navigator: Navigator): String? {
|
|
86
|
+
val callback = entries.firstOrNull { it.navigator.get() === navigator }?.callback?.get() ?: return null
|
|
87
|
+
val expected = jsListeners || takesBack(navigator)
|
|
88
|
+
if (callback.isEnabled == expected) return null
|
|
89
|
+
val top = navigator.visibleLayout?.let { AppearanceDispatcher.visibleNode(it).id } ?: "none"
|
|
90
|
+
val state = if (callback.isEnabled) "enabled" else "disabled"
|
|
91
|
+
val back = if (expected) "back stays in the app" else "back leaves the app"
|
|
92
|
+
return "the back callback is $state where $back (on top: '$top')"
|
|
93
|
+
}
|
|
94
|
+
|
|
64
95
|
// Enables or disables the callback when what back does changed.
|
|
65
96
|
private fun update(entry: Entry) {
|
|
66
97
|
val callback = entry.callback.get() ?: return
|
|
67
|
-
val enabled = jsListeners ||
|
|
98
|
+
val enabled = jsListeners || takesBack(entry.navigator.get())
|
|
68
99
|
if (callback.isEnabled != enabled) callback.isEnabled = enabled
|
|
69
100
|
}
|
|
70
101
|
|
|
71
|
-
// A layout that fails to answer leaves back to the navigation, as before: this runs
|
|
72
|
-
// exception here would end the app.
|
|
73
|
-
private fun
|
|
74
|
-
navigator
|
|
102
|
+
// A layout that fails to answer leaves back to the navigation, as before: this runs from the triggers of layouts
|
|
103
|
+
// and animators, and an exception here would end the app.
|
|
104
|
+
private fun takesBack(navigator: Navigator?): Boolean = try {
|
|
105
|
+
navigator != null && (navigator.canHandleBack() || navigator.keepsBackFromSystem())
|
|
75
106
|
} catch (e: RuntimeException) {
|
|
76
107
|
if (!failureLogged) {
|
|
77
108
|
failureLogged = true
|
package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt
CHANGED
|
@@ -40,7 +40,6 @@ import dev.apexnative.navigation.viewcontrollers.parent.ParentController
|
|
|
40
40
|
import dev.apexnative.navigation.viewcontrollers.stack.PreparedPushes
|
|
41
41
|
import dev.apexnative.navigation.viewcontrollers.stack.StackCommands
|
|
42
42
|
import dev.apexnative.navigation.viewcontrollers.stack.StackController
|
|
43
|
-
import dev.apexnative.navigation.viewcontrollers.stack.topbar.BarComponents
|
|
44
43
|
import dev.apexnative.navigation.viewcontrollers.viewcontroller.ViewController
|
|
45
44
|
import dev.apexnative.navigation.views.animations.FadeThrough
|
|
46
45
|
import dev.apexnative.navigation.views.animations.SideAnimator
|
|
@@ -105,7 +104,10 @@ class Navigator(
|
|
|
105
104
|
readiness = ContentReadiness,
|
|
106
105
|
scheduler = scheduler,
|
|
107
106
|
firstRoot = firstRootDraw,
|
|
108
|
-
onRootChanged = {
|
|
107
|
+
onRootChanged = {
|
|
108
|
+
root = it
|
|
109
|
+
BackCallbackState.update(this)
|
|
110
|
+
},
|
|
109
111
|
onRootShown = {
|
|
110
112
|
onRootShown?.invoke()
|
|
111
113
|
BackCallbackState.update(this)
|
|
@@ -142,10 +144,21 @@ class Navigator(
|
|
|
142
144
|
|
|
143
145
|
/**
|
|
144
146
|
* Whether back has something to do in the navigation: the mirror of [handleBack], without acting — the top modal
|
|
145
|
-
* first, then the root. The back callback asks it
|
|
147
|
+
* first, then the root. The back callback asks it whenever what back does can have changed (`BackCallbackState`).
|
|
146
148
|
*/
|
|
147
149
|
fun canHandleBack(): Boolean = modals.canHandleBack() ?: (root?.canHandleBack() == true)
|
|
148
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Whether back stays in the app also when the navigation has nothing to do: the layout back acts on — the top
|
|
153
|
+
* modal's, else the root — shows a screen with `hardwareBackButton.systemAnimation: false`. The system then doesn't
|
|
154
|
+
* play its animation of going home, and the press does the activity's own back ([handleBack] is false). The back
|
|
155
|
+
* callback asks it with [canHandleBack] (`BackCallbackState`).
|
|
156
|
+
*/
|
|
157
|
+
fun keepsBackFromSystem(): Boolean {
|
|
158
|
+
val layout = modals.backTargetRoot() ?: root ?: return false
|
|
159
|
+
return layout.resolveShownOptions(DefaultOptions.current).hardwareBackButton.systemAnimation.isFalse
|
|
160
|
+
}
|
|
161
|
+
|
|
149
162
|
/**
|
|
150
163
|
* The navigation's step of back, after JavaScript's listeners didn't take it: the top modal first (its content,
|
|
151
164
|
* else it closes or holds), else the root does the first thing it can (a stack's top leaves or holds, an open side
|
|
@@ -187,12 +200,14 @@ class Navigator(
|
|
|
187
200
|
* A layout of [node] (a stack, the tabs, a side menu, a split view, a pair) changed the screens it shows: when it
|
|
188
201
|
* is in a layer that still waits, the layer waits for the screens it shows now, and no longer for one that went
|
|
189
202
|
* ([WaitingLayer.shownScreensChanged]); so does the operation of its stack that waits for [node] as its entry (a
|
|
190
|
-
* pushed pair); a modal's sheet takes the hold of the screen it shows now
|
|
203
|
+
* pushed pair); a modal's sheet takes the hold of the screen it shows now; what back does is computed again (the
|
|
204
|
+
* tab, the pane or the menu back goes to, and the options of the screen it shows).
|
|
191
205
|
*/
|
|
192
206
|
internal fun shownScreensChanged(node: ViewController<*>) {
|
|
193
207
|
waitingLayerOf(node)?.shownScreensChanged()
|
|
194
208
|
node.findParentStack()?.queue?.shownScreensChanged(node)
|
|
195
209
|
modals.visibleScreenChanged(node)
|
|
210
|
+
BackCallbackState.update(this)
|
|
196
211
|
}
|
|
197
212
|
|
|
198
213
|
// The waiting layer [node] is in, up through the layouts around it; none.
|
|
@@ -435,13 +450,15 @@ class Navigator(
|
|
|
435
450
|
destroyLayers(answerPending = false)
|
|
436
451
|
// Nothing the old screens told of their readiness holds for the screens of the new JavaScript.
|
|
437
452
|
ScreenReadiness.reset()
|
|
438
|
-
|
|
453
|
+
// Without layers back leaves the app until the new JavaScript sets its root.
|
|
454
|
+
BackCallbackState.update(this)
|
|
439
455
|
}
|
|
440
456
|
|
|
441
457
|
/**
|
|
442
|
-
* The activity is destroyed: every layer goes, with the events of a destroy; a `setRoot`
|
|
443
|
-
* `''` (JavaScript sets its root again in the next activity),
|
|
444
|
-
* wait for a modal with their roots' ids.
|
|
458
|
+
* The activity is destroyed: every layer goes, with the events of a destroy; a `setRoot` whose root still waited
|
|
459
|
+
* resolves with `''` (JavaScript sets its root again in the next activity), one whose root showed resolves with
|
|
460
|
+
* its id, and a modal's or an overlay's show and the commands that wait for a modal resolve with their roots' ids.
|
|
461
|
+
* Main thread only.
|
|
445
462
|
*/
|
|
446
463
|
internal fun destroyWithActivity() {
|
|
447
464
|
destroyLayers(answerPending = true)
|
package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/RootCoordinator.kt
CHANGED
|
@@ -240,8 +240,9 @@ internal class RootCoordinator(
|
|
|
240
240
|
/**
|
|
241
241
|
* The activity's layers go: the `setRoot` on its way ends, then the root it replaced, the current root and its root
|
|
242
242
|
* are destroyed, as at any destroy (their events reach only their own JavaScript). [answerPending]: the activity
|
|
243
|
-
* went
|
|
244
|
-
* (
|
|
243
|
+
* went; a `setRoot` whose root still waited resolves with `''` (JavaScript sets the root again in the next
|
|
244
|
+
* activity), one whose root showed (its transition ran, or the modals and overlays of its layout were showing)
|
|
245
|
+
* resolves with the root's id. Otherwise (a reload) it gets no answer.
|
|
245
246
|
*/
|
|
246
247
|
fun destroyAll(answerPending: Boolean) {
|
|
247
248
|
val pending = pending
|
|
@@ -256,7 +257,9 @@ internal class RootCoordinator(
|
|
|
256
257
|
modals.replacedRootGone()
|
|
257
258
|
pending?.let(::releaseState)
|
|
258
259
|
if (pending != null && pending.phase == Phase.Waiting) firstRoot.rootDestroyed()
|
|
259
|
-
if (answerPending
|
|
260
|
+
if (!answerPending || pending == null) return
|
|
261
|
+
// An empty id tells JavaScript the root didn't show: its start waits for the next activity and sets it there.
|
|
262
|
+
pending.answer.resolve(if (pending.phase == Phase.Waiting) "" else pending.root.id)
|
|
260
263
|
}
|
|
261
264
|
|
|
262
265
|
// Waits for the screens that show when the root shows, then runs its transition. Without `waitForRender` one limit
|
|
@@ -485,8 +488,8 @@ internal class RootCoordinator(
|
|
|
485
488
|
}
|
|
486
489
|
|
|
487
490
|
// The navigation state goes once, when the root is in place and its layers have shown, right before the answer:
|
|
488
|
-
// what the old root's destroy, the root's appearance and each layer ask for meanwhile waits (
|
|
489
|
-
// per `setRoot`, before the old root goes.
|
|
491
|
+
// what the old root's destroy, the root's appearance and each layer ask for meanwhile waits (the hold of
|
|
492
|
+
// NavigationState). Taken once per `setRoot`, before the old root goes.
|
|
490
493
|
private fun holdState(pending: Pending) {
|
|
491
494
|
if (pending.holdsState) return
|
|
492
495
|
pending.holdsState = true
|
|
@@ -124,18 +124,18 @@ internal class OverlayCoordinator(
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
/**
|
|
127
|
-
* `dismissOverlay`: the
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
* overlay`.
|
|
127
|
+
* `dismissOverlay`: the overlay [componentId] is in — its root or any screen inside it — goes with every overlay of
|
|
128
|
+
* the same root id: one that waits is cancelled, one that shows goes with its events (both, when a replacement of
|
|
129
|
+
* a shown one waits). [answer] resolves with the overlay's root id, as on iOS. An id no node has: `not shown`; a
|
|
130
|
+
* node in no overlay: `not in an overlay`.
|
|
131
131
|
*/
|
|
132
132
|
fun dismiss(componentId: String, answer: CommandAnswer) {
|
|
133
|
-
|
|
134
|
-
val
|
|
135
|
-
|
|
133
|
+
val node = host.find(componentId) ?: return answer.reject(ErrorTexts.screenNotShown(componentId))
|
|
134
|
+
val rootId = recordOf(node)?.rootId ?: return answer.reject(ErrorTexts.notInOverlay(componentId))
|
|
135
|
+
val matches = records.filter { it.rootId == rootId }
|
|
136
136
|
matches.filter { it.state == OverlayRecord.State.Waiting }.forEach(::cancel)
|
|
137
137
|
val shown = matches.filter { it.state == OverlayRecord.State.Shown }
|
|
138
|
-
if (shown.isEmpty()) return answer.resolve(
|
|
138
|
+
if (shown.isEmpty()) return answer.resolve(rootId)
|
|
139
139
|
shown.dropLast(1).forEach { remove(it, null) }
|
|
140
140
|
remove(shown.last(), answer)
|
|
141
141
|
}
|