@apexnative/navigation 1.0.0-alpha.2 → 1.0.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/android/CLAUDE.md +38 -9
- package/android/CORE.md +11 -4
- package/android/RECIPES.md +9 -3
- package/android/src/main/java/dev/apexnative/navigation/NavigationActivity.kt +29 -1
- package/android/src/main/java/dev/apexnative/navigation/NavigationApplication.kt +34 -13
- package/android/src/main/java/dev/apexnative/navigation/layout/LayoutChecks.kt +5 -1
- package/android/src/main/java/dev/apexnative/navigation/layout/LayoutNode.kt +6 -2
- package/android/src/main/java/dev/apexnative/navigation/nativescreen/NativeScreenContext.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/options/ButtonMenuOptions.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/options/ButtonOptions.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/options/OptionsCache.kt +8 -4
- package/android/src/main/java/dev/apexnative/navigation/options/StatusBarOptions.kt +7 -3
- package/android/src/main/java/dev/apexnative/navigation/options/UserChoices.kt +159 -0
- package/android/src/main/java/dev/apexnative/navigation/react/CommandAnswer.kt +2 -24
- package/android/src/main/java/dev/apexnative/navigation/react/CommandContract.kt +59 -0
- package/android/src/main/java/dev/apexnative/navigation/react/Hinge.kt +27 -19
- package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +5 -4
- package/android/src/main/java/dev/apexnative/navigation/react/PendingCalls.kt +48 -0
- package/android/src/main/java/dev/apexnative/navigation/react/events/AppearanceDispatcher.kt +17 -7
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotContainers.kt +10 -7
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotView.kt +4 -3
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotViews.kt +40 -0
- package/android/src/main/java/dev/apexnative/navigation/utils/ImageKey.kt +66 -0
- package/android/src/main/java/dev/apexnative/navigation/utils/ImageLoader.kt +7 -89
- package/android/src/main/java/dev/apexnative/navigation/utils/ImageRequests.kt +76 -0
- package/android/src/main/java/dev/apexnative/navigation/utils/OptionImages.kt +262 -0
- package/android/src/main/java/dev/apexnative/navigation/utils/ViewController.kt +3 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/arrangement/SecondaryScreenPresenter.kt +13 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/AppliedTabCalls.kt +49 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsController.kt +92 -19
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsPresenter.kt +25 -24
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ComponentPresenter.kt +28 -7
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ContentReadiness.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/RenderWait.kt +27 -7
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ScreenFrame.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalAnimations.kt +22 -24
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalCoordinator.kt +79 -13
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalRecord.kt +21 -6
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalSheet.kt +29 -12
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +22 -18
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/BackCallbackState.kt +4 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt +45 -6
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/RootCoordinator.kt +35 -9
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/overlay/OverlayCoordinator.kt +10 -8
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/Capabilities.kt +11 -4
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/ContainerBackground.kt +3 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/ParentController.kt +3 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuController.kt +5 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitPanes.kt +23 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitViewController.kt +72 -16
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/BackPreview.kt +287 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackCommands.kt +28 -18
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +164 -13
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackDisplay.kt +26 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackQueue.kt +136 -40
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackTransitions.kt +26 -8
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +107 -4
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabPresenter.kt +7 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/StackSearchPresenter.kt +3 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/MenuChecks.kt +13 -56
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +131 -9
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/viewcontroller/ViewController.kt +75 -21
- package/android/src/main/java/dev/apexnative/navigation/views/animations/Curves.kt +29 -11
- package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/BottomTabs.kt +4 -2
- package/android/src/main/java/dev/apexnative/navigation/views/stack/ScreenEdge.kt +15 -0
- package/android/src/main/java/dev/apexnative/navigation/views/stack/StackLayout.kt +137 -1
- package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/Fab.kt +14 -10
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TitleBar.kt +8 -1
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TopBar.kt +26 -4
- package/cli/doctor/checks/app.js +61 -13
- package/cli/migrate/native.js +18 -0
- package/cli/setup/source.js +2 -1
- package/cli/setup/steps/mainApplication.js +134 -19
- package/docs-md/architecture/transitions.md +12 -7
- package/docs-md/containers/modals.md +4 -2
- package/docs-md/containers/overlays.md +3 -1
- package/docs-md/containers/side-menu.md +4 -2
- package/docs-md/containers/tabs.md +2 -0
- package/docs-md/flows/forms-in-a-modal.md +1 -1
- package/docs-md/help/glossary.md +2 -2
- package/docs-md/help/troubleshooting.md +20 -3
- package/docs-md/index.json +12 -0
- package/docs-md/migration/from-react-native-navigation.md +8 -2
- package/docs-md/screen-ui/back-and-gestures.md +8 -2
- package/docs-md/screen-ui/system-ui.md +1 -1
- package/docs-md/start/installation.md +3 -1
- package/ios/CLAUDE.md +17 -5
- package/ios/CORE.md +13 -8
- package/ios/Contract/ANNCommandAnswer.mm +5 -20
- package/ios/Contract/ANNCommandContract.h +15 -0
- package/ios/Contract/ANNCommandContract.mm +78 -0
- package/ios/Core/ANNAppearanceDispatcher.mm +1 -1
- package/ios/Core/ANNErrorHandler.h +1 -1
- package/ios/Core/ANNErrorHandler.mm +3 -1
- package/ios/Core/ANNEventEmitter.h +1 -1
- package/ios/Core/ANNEventEmitter.mm +2 -2
- package/ios/Core/ANNJavaScriptCalls.h +34 -0
- package/ios/Core/ANNJavaScriptCalls.mm +61 -0
- package/ios/Core/ANNNavigationState.h +7 -3
- package/ios/Core/ANNNavigationState.mm +10 -5
- package/ios/Core/ANNNodeHelper.h +32 -12
- package/ios/Core/ANNNodeHelper.mm +100 -3
- package/ios/Core/ANNRenderWait.h +15 -1
- package/ios/Core/ANNRenderWait.mm +119 -17
- package/ios/Core/UIViewController+LayoutProtocol.h +9 -2
- package/ios/Core/UIViewController+LayoutProtocol.mm +8 -0
- package/ios/Layouts/Modal/ANNModalCoordinator.h +1 -1
- package/ios/Layouts/Modal/ANNModalCoordinator.mm +55 -14
- package/ios/Layouts/Screen/ANNComponentViewController.h +8 -0
- package/ios/Layouts/Screen/ANNComponentViewController.mm +32 -5
- package/ios/Layouts/Screen/ANNScreenPlace.h +41 -0
- package/ios/Layouts/Screen/ANNScreenPlace.mm +175 -0
- package/ios/Layouts/SplitView/ANNSplitViewCommands.h +3 -2
- package/ios/Layouts/SplitView/ANNSplitViewCommands.mm +105 -39
- package/ios/Layouts/SplitView/ANNSplitViewController.mm +4 -2
- package/ios/Layouts/Stack/ANNStackCommands.mm +1 -0
- package/ios/Layouts/Stack/ANNStackController.mm +26 -3
- package/ios/Layouts/Stack/ANNStackQueue.h +3 -1
- package/ios/Layouts/Stack/ANNStackQueue.mm +56 -43
- package/ios/Layouts/Stack/UINavigationController+ANNCommands.h +4 -2
- package/ios/Layouts/Stack/UINavigationController+ANNCommands.mm +2 -3
- package/ios/Layouts/Tabs/ANNBottomTabItemBuilder.h +4 -0
- package/ios/Layouts/Tabs/ANNBottomTabItemBuilder.mm +11 -0
- package/ios/Layouts/Tabs/ANNBottomTabsController.mm +13 -5
- package/ios/Layouts/Tabs/ANNBottomTabsPresenter.h +2 -2
- package/ios/Layouts/Tabs/ANNBottomTabsPresenter.mm +109 -7
- package/ios/Options/ANNOptionsCache.h +7 -6
- package/ios/Options/ANNSearchBarOptions.h +4 -3
- package/ios/Options/ANNUserChoice.h +57 -0
- package/ios/Options/ANNUserChoice.mm +44 -0
- package/ios/Options/Params/ANNImageSource.h +61 -0
- package/ios/Options/Params/ANNImageSource.mm +101 -0
- package/ios/Options/Params/ANNOptionImages.h +37 -0
- package/ios/Options/Params/ANNOptionImages.mm +284 -0
- package/ios/Options/Params/Image.h +11 -2
- package/ios/Options/Params/Image.mm +15 -2
- package/ios/Options/Params/ImageParser.h +5 -5
- package/ios/Options/Params/ImageParser.mm +28 -46
- package/ios/Public/ApexNavigation/ApexNavigation.h +1 -1
- package/ios/RECIPES.md +11 -5
- package/ios/React/Slots/ANNSlotComponentView.mm +24 -17
- package/ios/React/Slots/ANNSlotContainers.h +14 -6
- package/ios/React/Slots/ANNSlotContainers.mm +75 -2
- package/ios/TopBar/ANNBarButtons.h +9 -5
- package/ios/TopBar/ANNBarButtons.mm +211 -63
- package/ios/TopBar/ANNIconButtonImage.h +1 -0
- package/ios/TopBar/ANNIconButtonImage.mm +34 -1
- package/ios/TopBar/ANNLargeTitlePresenter.h +13 -0
- package/ios/TopBar/ANNLargeTitlePresenter.mm +26 -3
- package/ios/TopBar/ANNSearchPresenter.mm +170 -19
- package/ios/TopBar/ANNTopBarPresenter.h +5 -0
- package/ios/TopBar/ANNTopBarPresenter.mm +60 -11
- package/ios/TurboModules/ANNTurboModule.mm +39 -21
- package/lib/module/ARCHITECTURE.md +18 -18
- package/lib/module/adapters/NativeANNTurboModule.js.map +1 -1
- package/lib/module/api/hooks/useNavigation.js +8 -3
- package/lib/module/api/hooks/useNavigation.js.map +1 -1
- package/lib/module/api/navigation.js.map +1 -1
- package/lib/module/engine/commands.js +9 -7
- package/lib/module/engine/commands.js.map +1 -1
- package/lib/module/engine/layout.js +13 -9
- package/lib/module/engine/layout.js.map +1 -1
- package/lib/module/engine/options.js +44 -11
- package/lib/module/engine/options.js.map +1 -1
- package/lib/module/engine/places.js +44 -7
- package/lib/module/engine/places.js.map +1 -1
- package/lib/module/engine/props.js +30 -1
- package/lib/module/engine/props.js.map +1 -1
- package/lib/module/engine/rememberedOptions.js +1 -1
- package/lib/module/engine/rememberedOptions.js.map +1 -1
- package/lib/module/engine/screens.js +22 -12
- package/lib/module/engine/screens.js.map +1 -1
- package/lib/module/interfaces/contract/commands.json +101 -0
- package/lib/typescript/adapters/NativeANNTurboModule.d.ts +5 -2
- package/lib/typescript/adapters/NativeANNTurboModule.d.ts.map +1 -1
- package/lib/typescript/api/events/navigationEvents.d.ts +1 -1
- package/lib/typescript/api/events/navigationEvents.d.ts.map +1 -1
- package/lib/typescript/api/hooks/useNavigation.d.ts +1 -1
- package/lib/typescript/api/hooks/useNavigation.d.ts.map +1 -1
- package/lib/typescript/api/navigation.d.ts +4 -1
- package/lib/typescript/api/navigation.d.ts.map +1 -1
- package/lib/typescript/engine/commands.d.ts.map +1 -1
- package/lib/typescript/engine/options.d.ts +5 -2
- package/lib/typescript/engine/options.d.ts.map +1 -1
- package/lib/typescript/engine/places.d.ts +5 -2
- package/lib/typescript/engine/places.d.ts.map +1 -1
- package/lib/typescript/engine/props.d.ts +8 -1
- package/lib/typescript/engine/props.d.ts.map +1 -1
- package/lib/typescript/engine/rememberedOptions.d.ts.map +1 -1
- package/lib/typescript/engine/screens.d.ts.map +1 -1
- package/lib/typescript/interfaces/ComponentEvents.d.ts +2 -2
- package/lib/typescript/interfaces/ComponentEvents.d.ts.map +1 -1
- package/lib/typescript/interfaces/Events.d.ts +19 -10
- package/lib/typescript/interfaces/Events.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/animations.d.ts +15 -7
- package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/buttons.d.ts +13 -5
- package/lib/typescript/interfaces/options/buttons.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/modals.d.ts +18 -13
- package/lib/typescript/interfaces/options/modals.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/system.d.ts +6 -3
- package/lib/typescript/interfaces/options/system.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/topBar.d.ts +22 -9
- package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/ARCHITECTURE.md +18 -18
- package/src/adapters/NativeANNTurboModule.ts +5 -2
- package/src/api/events/navigationEvents.ts +1 -1
- package/src/api/hooks/useNavigation.ts +9 -4
- package/src/api/navigation.ts +4 -1
- package/src/engine/commands.ts +19 -7
- package/src/engine/layout.ts +16 -9
- package/src/engine/options.ts +39 -11
- package/src/engine/places.ts +56 -7
- package/src/engine/props.ts +30 -1
- package/src/engine/rememberedOptions.ts +4 -1
- package/src/engine/screens.tsx +23 -13
- package/src/interfaces/ComponentEvents.ts +2 -2
- package/src/interfaces/Events.ts +19 -10
- package/src/interfaces/contract/commands.json +101 -0
- package/src/interfaces/options/animations.ts +15 -7
- package/src/interfaces/options/buttons.ts +13 -5
- package/src/interfaces/options/modals.ts +18 -13
- package/src/interfaces/options/system.ts +6 -3
- package/src/interfaces/options/topBar.ts +22 -9
|
@@ -6,7 +6,6 @@ import android.animation.AnimatorSet
|
|
|
6
6
|
import android.animation.ObjectAnimator
|
|
7
7
|
import android.app.Activity
|
|
8
8
|
import android.graphics.Color
|
|
9
|
-
import android.graphics.drawable.Drawable
|
|
10
9
|
import android.view.View
|
|
11
10
|
import android.view.animation.LinearInterpolator
|
|
12
11
|
import android.view.animation.PathInterpolator
|
|
@@ -20,8 +19,8 @@ import dev.apexnative.navigation.options.params.LayoutDirection
|
|
|
20
19
|
import dev.apexnative.navigation.options.params.ThemeColour
|
|
21
20
|
import dev.apexnative.navigation.options.parsers.TypefaceLoader
|
|
22
21
|
import dev.apexnative.navigation.system.SystemUiOwner
|
|
23
|
-
import dev.apexnative.navigation.utils.
|
|
24
|
-
import dev.apexnative.navigation.utils.
|
|
22
|
+
import dev.apexnative.navigation.utils.ImageRequests
|
|
23
|
+
import dev.apexnative.navigation.utils.OptionImages
|
|
25
24
|
import dev.apexnative.navigation.utils.TestIds
|
|
26
25
|
import dev.apexnative.navigation.utils.UiUtils
|
|
27
26
|
import dev.apexnative.navigation.viewcontrollers.viewcontroller.Presenter
|
|
@@ -67,8 +66,9 @@ internal open class BottomTabsPresenter(activity: Activity) : Presenter(activity
|
|
|
67
66
|
|
|
68
67
|
private var layout: BottomTabsLayout? = null
|
|
69
68
|
private val typefaces = TypefaceLoader(activity)
|
|
70
|
-
|
|
71
|
-
private val
|
|
69
|
+
// The icons of the items by item and kind (selected or not): those of the cache at once, the others as below.
|
|
70
|
+
private val icons = ImageRequests<Pair<Int, Boolean>>(OptionImages.source(activity))
|
|
71
|
+
private val applied = AppliedTabCalls(icons::key)
|
|
72
72
|
private var motion: Animator? = null
|
|
73
73
|
private var anyIcon = false
|
|
74
74
|
private var testId: String? = null
|
|
@@ -100,25 +100,24 @@ internal open class BottomTabsPresenter(activity: Activity) : Presenter(activity
|
|
|
100
100
|
|
|
101
101
|
/**
|
|
102
102
|
* Runs [calls] on the bar's items; a call equal to the last of its kind for its item is skipped (an icon isn't
|
|
103
|
-
* loaded and set again as a screen appears)
|
|
103
|
+
* loaded and set again as a screen appears), except the badge and the font, an icon that didn't load and a `file:`
|
|
104
|
+
* icon whose file changed ([AppliedTabCalls]).
|
|
104
105
|
*/
|
|
105
106
|
open fun run(calls: List<TabItems.Call>) {
|
|
106
107
|
val bar = layout?.bar ?: return
|
|
107
108
|
for (call in calls) {
|
|
108
109
|
if (call is TabItems.Call.TestId && !itemTestIds) continue
|
|
109
|
-
val
|
|
110
|
-
|
|
111
|
-
applied[key] = call
|
|
112
|
-
run(bar, call)
|
|
110
|
+
val next = applied.next(call) ?: continue
|
|
111
|
+
run(bar, call, next)
|
|
113
112
|
}
|
|
114
113
|
}
|
|
115
114
|
|
|
116
|
-
private fun run(bar: BottomTabs, call: TabItems.Call) {
|
|
115
|
+
private fun run(bar: BottomTabs, call: TabItems.Call, next: AppliedTabCalls.Next) {
|
|
117
116
|
val index = call.index
|
|
118
117
|
when (call) {
|
|
119
118
|
is TabItems.Call.Title -> bar.setText(index, call.text)
|
|
120
|
-
is TabItems.Call.Icon ->
|
|
121
|
-
is TabItems.Call.SelectedIcon ->
|
|
119
|
+
is TabItems.Call.Icon -> icon(bar, call, call.uri, false, next)
|
|
120
|
+
is TabItems.Call.SelectedIcon -> icon(bar, call, call.uri, true, next)
|
|
122
121
|
is TabItems.Call.Colors -> {
|
|
123
122
|
bar.setIconActiveColor(index, call.iconActive)
|
|
124
123
|
bar.setIconInactiveColor(index, call.iconInactive)
|
|
@@ -344,17 +343,19 @@ internal open class BottomTabsPresenter(activity: Activity) : Presenter(activity
|
|
|
344
343
|
|
|
345
344
|
private fun colorOf(colour: ThemeColour): Int? = colour.get(activity, null)
|
|
346
345
|
|
|
347
|
-
// Icons
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
346
|
+
// Icons never throw: one that doesn't load leaves the item without it (without its selected icon, the item shows
|
|
347
|
+
// its icon when selected). An item's first icon loads at once, its tab's first frame shows it; a change of it on
|
|
348
|
+
// the bar that shows loads off the main thread when it can, and the item takes it when ready, unless a newer icon
|
|
349
|
+
// of the item came first.
|
|
350
|
+
// The key of a `file:` icon was built for the comparison: the request takes it, its file read once.
|
|
351
|
+
private fun icon(bar: BottomTabs, call: TabItems.Call, uri: String, selected: Boolean, next: AppliedTabCalls.Next) {
|
|
352
|
+
val index = call.index
|
|
353
|
+
val key = next.image ?: icons.key(uri)
|
|
354
|
+
icons.request(index to selected, key, mayWait = next.isChange && bar.isAttachedToWindow) { icon ->
|
|
355
|
+
if (icon == null) applied.failed(call)
|
|
356
|
+
val shown = layout?.bar ?: return@request
|
|
357
|
+
if (selected) shown.setSelectedIcon(index, icon) else shown.setIcon(index, icon)
|
|
358
|
+
}
|
|
358
359
|
}
|
|
359
360
|
|
|
360
361
|
private companion object {
|
|
@@ -6,6 +6,7 @@ import android.graphics.drawable.ColorDrawable
|
|
|
6
6
|
import android.graphics.drawable.Drawable
|
|
7
7
|
import android.graphics.drawable.LayerDrawable
|
|
8
8
|
import android.view.View
|
|
9
|
+
import com.google.android.material.color.MaterialColors
|
|
9
10
|
import dev.apexnative.navigation.options.DefaultOptions
|
|
10
11
|
import dev.apexnative.navigation.options.Options
|
|
11
12
|
import dev.apexnative.navigation.options.params.ThemeColour
|
|
@@ -15,9 +16,9 @@ import dev.apexnative.navigation.viewcontrollers.viewcontroller.Presenter
|
|
|
15
16
|
import dev.apexnative.navigation.viewcontrollers.viewcontroller.ViewController
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
|
-
* The presenter of a screen, React or native: its background — the screen's view takes `layout.backgroundColor
|
|
19
|
-
* out of the status bar's height where the status bar shows over
|
|
20
|
-
* `layout.componentBackgroundColor` over it —, its room for the bars, and its safe area again on `mergeOptions`
|
|
19
|
+
* The presenter of a screen, React or native: its background — the screen's view takes `layout.backgroundColor` (a
|
|
20
|
+
* React screen without it the theme's background), kept out of the status bar's height where the status bar shows over
|
|
21
|
+
* it, and the React content takes `layout.componentBackgroundColor` over it —, its room for the bars, and its safe area again on `mergeOptions`
|
|
21
22
|
* (`mergeOptions` step 4). It applies as the screen appears, on `mergeOptions` of what it covers, and again with new
|
|
22
23
|
* default options or after a light/dark switch from the resolved options it is given, which it never writes. Each
|
|
23
24
|
* appearance tells the activity's system UI owner, which follows the screen on top. Main thread only.
|
|
@@ -49,8 +50,8 @@ open class ComponentPresenter(activity: Activity) : Presenter(activity, DefaultO
|
|
|
49
50
|
applyBackground(controller, options)
|
|
50
51
|
|
|
51
52
|
/**
|
|
52
|
-
* The colors of [screen] from its resolved [options]: `layout.backgroundColor` on the screen's view,
|
|
53
|
-
*
|
|
53
|
+
* The colors of [screen] from its resolved [options]: `layout.backgroundColor` on the screen's view, without it the
|
|
54
|
+
* theme's background for a React screen ([defaultColorOf]); `layout.componentBackgroundColor` on its React content.
|
|
54
55
|
*/
|
|
55
56
|
fun applyBackground(screen: ViewController<*>, options: Options) {
|
|
56
57
|
val view = screen.peekView() ?: return
|
|
@@ -59,11 +60,11 @@ open class ComponentPresenter(activity: Activity) : Presenter(activity, DefaultO
|
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
private fun screenBackground(screen: ViewController<*>, options: Options): Drawable? {
|
|
62
|
-
val color = colorOf(options.layout.backgroundColor) ?: return null
|
|
63
|
+
val color = colorOf(options.layout.backgroundColor) ?: defaultColorOf(screen) ?: return null
|
|
63
64
|
val frame = frameOf(screen) ?: return ColorDrawable(color)
|
|
64
65
|
val band = ScreenSafeArea.backgroundBand(
|
|
65
66
|
statusBarHeight = frame.statusBarHeight,
|
|
66
|
-
hiddenOrDrawBehind = options.statusBar.isHiddenOrDrawBehind,
|
|
67
|
+
hiddenOrDrawBehind = options.statusBar.isHiddenOrDrawBehind(screen.isInOverlay),
|
|
67
68
|
inSideMenu = screen.isInSideMenu,
|
|
68
69
|
inSheet = screen.isInSheet,
|
|
69
70
|
inPair = frame.isInPair,
|
|
@@ -79,6 +80,15 @@ open class ComponentPresenter(activity: Activity) : Presenter(activity, DefaultO
|
|
|
79
80
|
private fun colorOf(colour: ThemeColour): Int? =
|
|
80
81
|
if (colour.hasValue()) colour.get(activity, Color.TRANSPARENT) ?: Color.TRANSPARENT else null
|
|
81
82
|
|
|
83
|
+
// A React screen is opaque without a color of its own, as iOS gives it the system background: the screen under it
|
|
84
|
+
// never shows through, also while a push slides it aside. The app theme's `android:colorBackground`, read now, so a
|
|
85
|
+
// light/dark switch takes the new mode's. A screen of a sheet shows the sheet's surface; a native screen paints its
|
|
86
|
+
// own view.
|
|
87
|
+
private fun defaultColorOf(screen: ViewController<*>): Int? {
|
|
88
|
+
if (screen !is ComponentViewController || screen.isInSheet) return null
|
|
89
|
+
return themeBackgroundOf(activity)
|
|
90
|
+
}
|
|
91
|
+
|
|
82
92
|
private fun frameOf(screen: ViewController<*>): ScreenFrame? = when (screen) {
|
|
83
93
|
is ComponentViewController -> screen.frame
|
|
84
94
|
is ExternalComponentViewController -> screen.frame
|
|
@@ -87,4 +97,15 @@ open class ComponentPresenter(activity: Activity) : Presenter(activity, DefaultO
|
|
|
87
97
|
|
|
88
98
|
// The view under the React content: the React root, or the container of a slot screen. A native screen has none.
|
|
89
99
|
private fun contentOf(screen: ViewController<*>): View? = (screen as? ComponentViewController)?.content?.asView()
|
|
100
|
+
|
|
101
|
+
internal companion object {
|
|
102
|
+
/**
|
|
103
|
+
* The background of a React screen without `layout.backgroundColor`: `android:colorBackground` of [activity]'s
|
|
104
|
+
* theme now. None for an activity without a theme (the unit tests' fakes).
|
|
105
|
+
*/
|
|
106
|
+
fun themeBackgroundOf(activity: Activity): Int? {
|
|
107
|
+
if (activity.theme == null) return null
|
|
108
|
+
return MaterialColors.getColor(activity, android.R.attr.colorBackground, Color.WHITE)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
90
111
|
}
|
|
@@ -4,7 +4,7 @@ import dev.apexnative.navigation.viewcontrollers.viewcontroller.ViewController
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Whether a screen's content rendered, which a command waits for before the screen comes in: a new root (`setRoot`),
|
|
7
|
-
* a push
|
|
7
|
+
* a push (unless `waitForRender: false`), the new top of a `setStackRoot`. A wait never listens to the readiness store
|
|
8
8
|
* itself: the content keeps the one listener of its id and tells every waiter. Main thread only.
|
|
9
9
|
*/
|
|
10
10
|
internal interface Readiness {
|
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/ScreenFrame.kt
CHANGED
|
@@ -143,7 +143,7 @@ internal class ScreenFrame(
|
|
|
143
143
|
val parent = node.parentController
|
|
144
144
|
val statusBar = ScreenSafeArea.statusBarRoom(
|
|
145
145
|
statusBarHeight = statusBarHeight,
|
|
146
|
-
hiddenOrDrawBehind = options.statusBar.isHiddenOrDrawBehind,
|
|
146
|
+
hiddenOrDrawBehind = options.statusBar.isHiddenOrDrawBehind(node.isInOverlay),
|
|
147
147
|
inSheet = node.isInSheet,
|
|
148
148
|
inSideMenu = node.isInSideMenu,
|
|
149
149
|
keptAround = parent?.keepsStatusBarRoomOf(node) == true,
|
package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalAnimations.kt
CHANGED
|
@@ -1,40 +1,38 @@
|
|
|
1
1
|
package dev.apexnative.navigation.viewcontrollers.modal
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import kotlin.math.cos
|
|
3
|
+
import dev.apexnative.navigation.views.animations.Curves
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
|
-
* The default animations of a modal on Android, as values:
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* The default animations of a modal over the whole screen on Android, as values: the system's motion of a window that
|
|
7
|
+
* opens over the one below it (`activity_translucent_open_enter` and `activity_translucent_close_exit` of the
|
|
8
|
+
* platform, which Android 16 plays for a translucent activity). The modal comes up from the bottom of the window by the
|
|
9
|
+
* window's height (in a split screen or a window, the window's, not the display's) in [SHOW_DURATION], slowing down at
|
|
10
|
+
* the end; it goes down in [DISMISS_DURATION], speeding up. It doesn't fade: the screen below is seen only around it,
|
|
11
|
+
* as under a translucent activity. A sheet moves only by the Material sheet's own motion. Pure arithmetic.
|
|
11
12
|
*/
|
|
12
13
|
internal object ModalAnimations {
|
|
13
|
-
/** How long the default show
|
|
14
|
-
const val
|
|
14
|
+
/** How long the default show lasts (ms): `activity_translucent_open_enter`. */
|
|
15
|
+
const val SHOW_DURATION = 300L
|
|
16
|
+
|
|
17
|
+
/** How long the default dismissal lasts (ms): `activity_translucent_close_exit`. */
|
|
18
|
+
const val DISMISS_DURATION = 250L
|
|
19
|
+
|
|
20
|
+
// `linear_out_slow_in` of the platform: the path (0, 0) (0, 0) (0.2, 1) (1, 1).
|
|
21
|
+
private val linearOutSlowIn = Curves.CubicBezier(0.0, 0.0, 0.2, 1.0)
|
|
15
22
|
|
|
16
23
|
/** How far a modal comes up and goes down: the window's height ([windowHeight]), or the display's while it is 0. */
|
|
17
24
|
fun distance(windowHeight: Int, displayHeight: Int): Float =
|
|
18
25
|
(if (windowHeight > 0) windowHeight else displayHeight).toFloat()
|
|
19
26
|
|
|
20
|
-
/** The curve of the show:
|
|
21
|
-
fun
|
|
27
|
+
/** The curve of the show: the platform's `linear_out_slow_in`, fast at first and slow at the end. */
|
|
28
|
+
fun linearOutSlowIn(progress: Float): Float = linearOutSlowIn.getInterpolation(progress)
|
|
22
29
|
|
|
23
|
-
/** The curve of the dismissal:
|
|
24
|
-
fun
|
|
30
|
+
/** The curve of the dismissal: the platform's `accelerate_quad`, p². */
|
|
31
|
+
fun accelerate(progress: Float): Float = progress.coerceIn(0f, 1f).let { it * it }
|
|
25
32
|
|
|
26
|
-
/** Where a modal is at [progress] (0 … 1, in time) of its show over [distance]: its translation
|
|
27
|
-
fun show(progress: Float, distance: Float):
|
|
28
|
-
val value = decelerate(progress)
|
|
29
|
-
return Frame(translationY = distance * (1f - value), alpha = value)
|
|
30
|
-
}
|
|
33
|
+
/** Where a modal is at [progress] (0 … 1, in time) of its show over [distance]: its translation. */
|
|
34
|
+
fun show(progress: Float, distance: Float): Float = distance * (1f - linearOutSlowIn(progress))
|
|
31
35
|
|
|
32
36
|
/** Where a modal is at [progress] of its dismissal over [distance], from where it rests. */
|
|
33
|
-
fun dismiss(progress: Float, distance: Float):
|
|
34
|
-
val value = accelerateDecelerate(progress)
|
|
35
|
-
return Frame(translationY = distance * value, alpha = 1f - value)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** A modal's place in its animation. */
|
|
39
|
-
data class Frame(val translationY: Float, val alpha: Float)
|
|
37
|
+
fun dismiss(progress: Float, distance: Float): Float = distance * accelerate(progress)
|
|
40
38
|
}
|
package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalCoordinator.kt
CHANGED
|
@@ -261,6 +261,54 @@ internal class ModalCoordinator(
|
|
|
261
261
|
}
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
+
/**
|
|
265
|
+
* A modal of a `setRoot`'s layout made with the root, before its turn: it joins the modals layer and renders
|
|
266
|
+
* unseen while the root waits and comes in, held — it doesn't show, and commands and back don't count it — until
|
|
267
|
+
* [release]; [animated] as in [showInChain]. A failure is logged, and nothing is made (null).
|
|
268
|
+
*/
|
|
269
|
+
fun hold(id: String, make: () -> ViewController<*>, animated: Boolean): ModalRecord? = try {
|
|
270
|
+
start(make, null, ModalRecord.Chain(animated) {}, held = true)
|
|
271
|
+
} catch (e: Throwable) {
|
|
272
|
+
val reason = ErrorTexts.reasonOf(e)
|
|
273
|
+
host.log("[ApexNavigation] setRoot: ${ErrorTexts.couldNot("show the screen '$id'", reason)}")
|
|
274
|
+
null
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* The turn of a [hold]'s modal in its `setRoot`'s chain: it shows as soon as it rendered, as [showInChain]'s
|
|
279
|
+
* would, with [done] once it showed or went; at once when it went meanwhile. The function returned stops it while
|
|
280
|
+
* it waits.
|
|
281
|
+
*/
|
|
282
|
+
fun release(record: ModalRecord, done: () -> Unit): () -> Unit {
|
|
283
|
+
if (!record.held || record.state != State.Waiting) {
|
|
284
|
+
done()
|
|
285
|
+
return {}
|
|
286
|
+
}
|
|
287
|
+
record.chain = ModalRecord.Chain(record.chain?.animated ?: true, done)
|
|
288
|
+
record.held = false
|
|
289
|
+
guarded(record) {
|
|
290
|
+
host.backChanged()
|
|
291
|
+
// Rendered while held: the targets of its elements are in the layer under it only now.
|
|
292
|
+
if (record.isReady && !record.targetsAwaited && awaitsTargets(record)) {
|
|
293
|
+
record.isReady = false
|
|
294
|
+
awaitTargets(record)
|
|
295
|
+
} else {
|
|
296
|
+
presentReady()
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return {
|
|
300
|
+
record.chain = null
|
|
301
|
+
if (record.state == State.Waiting) cancel(record, null, reports = false)
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** A [hold]'s modal whose `setRoot` ended before its turn: it goes without a word. */
|
|
306
|
+
fun drop(record: ModalRecord) {
|
|
307
|
+
if (!record.held) return
|
|
308
|
+
record.chain = null
|
|
309
|
+
if (record.state == State.Waiting) cancel(record, null, reports = false)
|
|
310
|
+
}
|
|
311
|
+
|
|
264
312
|
/**
|
|
265
313
|
* `dismissModal` after its shared steps: the record of [componentId], Android's rule of the last modal without a
|
|
266
314
|
* root, [options] merged into the modal's visible screen, the animation of the command's screen with its parents
|
|
@@ -279,6 +327,7 @@ internal class ModalCoordinator(
|
|
|
279
327
|
State.ClosedBelow -> return answer.resolve(record.rootId)
|
|
280
328
|
else -> Unit
|
|
281
329
|
}
|
|
330
|
+
if (record.held) return answer.reject(ErrorTexts.screenNotShown(componentId))
|
|
282
331
|
if (isLastWithoutRoot(record)) return answer.reject(ErrorTexts.modalWithoutRoot(componentId))
|
|
283
332
|
options?.let { mergeInto(record, it) }
|
|
284
333
|
val from = node?.takeUnless { it.isDestroyed } ?: record.root
|
|
@@ -293,7 +342,7 @@ internal class ModalCoordinator(
|
|
|
293
342
|
* before anything changes, with the id of the top modal's visible screen.
|
|
294
343
|
*/
|
|
295
344
|
fun dismissAll(options: Options?, unreadablePath: String?, answer: CommandAnswer) {
|
|
296
|
-
val members = records.filter { it.batch == null && it.state in BATCHED }
|
|
345
|
+
val members = records.filter { it.batch == null && !it.held && it.state in BATCHED }
|
|
297
346
|
if (members.isEmpty()) {
|
|
298
347
|
// Nothing else to take: the second one answers with the running batch (N6), else at once.
|
|
299
348
|
val running = batches.lastOrNull()
|
|
@@ -347,6 +396,12 @@ internal class ModalCoordinator(
|
|
|
347
396
|
return true
|
|
348
397
|
}
|
|
349
398
|
|
|
399
|
+
/**
|
|
400
|
+
* The root of the modal that back goes to first; none without one. A back gesture previews only a pop in it: its
|
|
401
|
+
* dismissal has a preview of its own nowhere yet.
|
|
402
|
+
*/
|
|
403
|
+
fun backTargetRoot(): ViewController<*>? = backTarget()?.root
|
|
404
|
+
|
|
350
405
|
/** The mirror of [handleBack], without acting; null without a modal to ask. */
|
|
351
406
|
fun canHandleBack(): Boolean? {
|
|
352
407
|
val record = backTarget() ?: return null
|
|
@@ -422,6 +477,8 @@ internal class ModalCoordinator(
|
|
|
422
477
|
}
|
|
423
478
|
batches.clear()
|
|
424
479
|
for (record in records.toList()) {
|
|
480
|
+
// A modal of the new root's layout, made with it, stays: it shows over the new root in its turn.
|
|
481
|
+
if (record.held) continue
|
|
425
482
|
if (record.state == State.Waiting) {
|
|
426
483
|
cancel(record, null, reports = false)
|
|
427
484
|
continue
|
|
@@ -432,7 +489,7 @@ internal class ModalCoordinator(
|
|
|
432
489
|
views.carry(record, old)
|
|
433
490
|
carried += record
|
|
434
491
|
}
|
|
435
|
-
records.
|
|
492
|
+
records.retainAll { it.held }
|
|
436
493
|
host.backChanged()
|
|
437
494
|
}
|
|
438
495
|
|
|
@@ -482,7 +539,12 @@ internal class ModalCoordinator(
|
|
|
482
539
|
|
|
483
540
|
// The show (3, 4).
|
|
484
541
|
|
|
485
|
-
private fun start(
|
|
542
|
+
private fun start(
|
|
543
|
+
make: () -> ViewController<*>,
|
|
544
|
+
answer: CommandAnswer?,
|
|
545
|
+
chain: ModalRecord.Chain?,
|
|
546
|
+
held: Boolean = false,
|
|
547
|
+
): ModalRecord {
|
|
486
548
|
val root = make()
|
|
487
549
|
val record: ModalRecord
|
|
488
550
|
val options: Options
|
|
@@ -492,6 +554,7 @@ internal class ModalCoordinator(
|
|
|
492
554
|
val sheet = ModalRecord.isSheet(options)
|
|
493
555
|
root.shownAsSheet = sheet
|
|
494
556
|
record = ModalRecord(root, sheet)
|
|
557
|
+
record.held = held
|
|
495
558
|
} catch (e: Throwable) {
|
|
496
559
|
root.destroy()
|
|
497
560
|
throw e
|
|
@@ -555,7 +618,7 @@ internal class ModalCoordinator(
|
|
|
555
618
|
private fun ready(record: ModalRecord) {
|
|
556
619
|
if (record.state != State.Waiting) return
|
|
557
620
|
record.wait = null
|
|
558
|
-
if (!record.targetsAwaited && awaitsTargets(record)) return awaitTargets(record)
|
|
621
|
+
if (!record.targetsAwaited && !record.held && awaitsTargets(record)) return awaitTargets(record)
|
|
559
622
|
record.cancelLimit()
|
|
560
623
|
record.isReady = true
|
|
561
624
|
presentReady()
|
|
@@ -607,7 +670,7 @@ internal class ModalCoordinator(
|
|
|
607
670
|
if (taking) return
|
|
608
671
|
for (record in records.toList()) {
|
|
609
672
|
if (record.state != State.Waiting) continue
|
|
610
|
-
if (!record.isReady) return
|
|
673
|
+
if (record.held || !record.isReady) return
|
|
611
674
|
present(record)
|
|
612
675
|
}
|
|
613
676
|
}
|
|
@@ -668,7 +731,7 @@ internal class ModalCoordinator(
|
|
|
668
731
|
// without the events not sent yet; what waits for it rejects, the layer it took out comes back, and the modals that
|
|
669
732
|
// wait go on.
|
|
670
733
|
private fun dismissFailed(record: ModalRecord, e: Throwable) {
|
|
671
|
-
val text = ErrorTexts.couldNot("dismiss the modal
|
|
734
|
+
val text = ErrorTexts.couldNot("dismiss the modal '${record.rootId}'", ErrorTexts.reasonOf(e))
|
|
672
735
|
val listed = record in records
|
|
673
736
|
val under = layerUnder(record)
|
|
674
737
|
runCatching { record.stopAnimation?.invoke() }
|
|
@@ -772,8 +835,8 @@ internal class ModalCoordinator(
|
|
|
772
835
|
State.Presenting -> record.waitingDismissals += dismissal
|
|
773
836
|
State.Shown -> if (isTop(record)) beginDismissal(record, dismissal) else closeBelow(record, dismissal)
|
|
774
837
|
State.Dismissing -> dismissal.answer?.let(record.answers::add)
|
|
775
|
-
|
|
776
|
-
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)
|
|
777
840
|
}
|
|
778
841
|
}
|
|
779
842
|
|
|
@@ -787,7 +850,7 @@ internal class ModalCoordinator(
|
|
|
787
850
|
record.state = State.Gone
|
|
788
851
|
records.remove(record)
|
|
789
852
|
views.remove(record)
|
|
790
|
-
val screen = AppearanceDispatcher.
|
|
853
|
+
val screen = AppearanceDispatcher.modalScreen(record.root)
|
|
791
854
|
record.showAnswer?.resolve(record.rootId)
|
|
792
855
|
record.showAnswer = null
|
|
793
856
|
record.chain?.let {
|
|
@@ -949,7 +1012,7 @@ internal class ModalCoordinator(
|
|
|
949
1012
|
val wasOut = isOut(revealed)
|
|
950
1013
|
val counted = members.filter { it.state != State.ClosedBelow && !it.reported }
|
|
951
1014
|
batch.count = counted.size
|
|
952
|
-
batch.screen = counted.lastOrNull()?.let { AppearanceDispatcher.
|
|
1015
|
+
batch.screen = counted.lastOrNull()?.let { AppearanceDispatcher.modalScreen(it.root) }
|
|
953
1016
|
// The top that leaves with its animation: one already leaving keeps its own.
|
|
954
1017
|
val top = members.lastOrNull { it.state in ANIMATED_IN_BATCH }
|
|
955
1018
|
batch.top = top
|
|
@@ -1156,13 +1219,16 @@ internal class ModalCoordinator(
|
|
|
1156
1219
|
|
|
1157
1220
|
// A record is the top when no record above it waits, comes in or shows; one that leaves doesn't count.
|
|
1158
1221
|
private fun isTop(record: ModalRecord): Boolean =
|
|
1159
|
-
records.drop(records.indexOf(record) + 1).none
|
|
1222
|
+
records.drop(records.indexOf(record) + 1).none(::countsAsTop)
|
|
1160
1223
|
|
|
1161
1224
|
// The top record, as the commands see it; none.
|
|
1162
|
-
private fun topRecord(): ModalRecord? = records.lastOrNull
|
|
1225
|
+
private fun topRecord(): ModalRecord? = records.lastOrNull(::countsAsTop)
|
|
1163
1226
|
|
|
1164
1227
|
// Back goes to the top modal that waits, comes in or shows.
|
|
1165
|
-
private fun backTarget(): ModalRecord? = records.lastOrNull
|
|
1228
|
+
private fun backTarget(): ModalRecord? = records.lastOrNull(::countsAsTop)
|
|
1229
|
+
|
|
1230
|
+
// A record that waits, comes in or shows; a held one doesn't count before its turn.
|
|
1231
|
+
private fun countsAsTop(record: ModalRecord): Boolean = !record.held && record.effective in TOP_STATES
|
|
1166
1232
|
|
|
1167
1233
|
// `hardwareBackButton.dismissModalOnPress: false` of the modal's options now.
|
|
1168
1234
|
private fun holdsBack(record: ModalRecord): Boolean =
|
package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalRecord.kt
CHANGED
|
@@ -17,7 +17,10 @@ import dev.apexnative.navigation.viewcontrollers.viewcontroller.ViewController
|
|
|
17
17
|
internal class ModalRecord(
|
|
18
18
|
/** The top node of the modal's layout. */
|
|
19
19
|
val root: ViewController<*>,
|
|
20
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* The modal shows as a Material sheet (`pageSheet`, `formSheet`, `popover`, or `modal.sheet` without a style),
|
|
22
|
+
* decided before its root's view is made.
|
|
23
|
+
*/
|
|
21
24
|
val isSheet: Boolean,
|
|
22
25
|
) : WaitingLayer {
|
|
23
26
|
/** Where the modal is. */
|
|
@@ -78,6 +81,12 @@ internal class ModalRecord(
|
|
|
78
81
|
/** Its wait ended: it shows once every older waiting modal showed or went (N1). */
|
|
79
82
|
var isReady = false
|
|
80
83
|
|
|
84
|
+
/**
|
|
85
|
+
* A modal of a `setRoot`'s layout made with the root, before its turn: it renders unseen while the root waits and
|
|
86
|
+
* comes in, and neither shows nor counts for commands or back until its step of the chain lets it go.
|
|
87
|
+
*/
|
|
88
|
+
var held = false
|
|
89
|
+
|
|
81
90
|
/** The wait for its screens while it waits. */
|
|
82
91
|
var wait: RenderWait? = null
|
|
83
92
|
|
|
@@ -160,14 +169,20 @@ internal class ModalRecord(
|
|
|
160
169
|
/** `modalPresentationStyle` values that keep the layer under in place. */
|
|
161
170
|
private val OVER_STYLES = setOf("overFullScreen", "overCurrentContext")
|
|
162
171
|
|
|
163
|
-
/**
|
|
164
|
-
|
|
172
|
+
/**
|
|
173
|
+
* The styles that always show as a sheet: the sheets of iOS, and the popover, which an iPhone shows as a sheet
|
|
174
|
+
* too.
|
|
175
|
+
*/
|
|
176
|
+
private val SHEET_STYLES = setOf("pageSheet", "formSheet", "popover")
|
|
165
177
|
|
|
166
178
|
/**
|
|
167
|
-
* Whether a modal with [options] (resolved, with the defaults) shows as a sheet:
|
|
168
|
-
*
|
|
179
|
+
* Whether a modal with [options] (resolved, with the defaults) shows as a sheet: its style is `pageSheet`,
|
|
180
|
+
* `formSheet` or `popover`, or it has none and any `modal.sheet` field is set.
|
|
169
181
|
*/
|
|
170
|
-
fun isSheet(options: Options): Boolean
|
|
182
|
+
fun isSheet(options: Options): Boolean {
|
|
183
|
+
val style = styleOf(options)
|
|
184
|
+
return style in SHEET_STYLES || (style == null && options.modal.sheet.hasValue)
|
|
185
|
+
}
|
|
171
186
|
|
|
172
187
|
/**
|
|
173
188
|
* Whether a modal covers the layer under it at the end of its show: full screen, unless it is a sheet or
|