@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
|
@@ -15,6 +15,7 @@ import dev.apexnative.navigation.options.SplitViewGroupOptions
|
|
|
15
15
|
import dev.apexnative.navigation.options.SplitViewOptions
|
|
16
16
|
import dev.apexnative.navigation.react.CommandListener
|
|
17
17
|
import dev.apexnative.navigation.react.CommandListenerAdapter
|
|
18
|
+
import dev.apexnative.navigation.react.ErrorTexts
|
|
18
19
|
import dev.apexnative.navigation.react.events.EventEmitter
|
|
19
20
|
import dev.apexnative.navigation.system.SystemUiOwner
|
|
20
21
|
import dev.apexnative.navigation.utils.TestIds
|
|
@@ -24,10 +25,11 @@ import dev.apexnative.navigation.viewcontrollers.child.ChildControllersRegistry
|
|
|
24
25
|
import dev.apexnative.navigation.viewcontrollers.navigator.BackCallbackState
|
|
25
26
|
import dev.apexnative.navigation.viewcontrollers.navigator.Navigator
|
|
26
27
|
import dev.apexnative.navigation.viewcontrollers.parent.ChildOffsetFromRest
|
|
28
|
+
import dev.apexnative.navigation.viewcontrollers.parent.OptionsScreen
|
|
27
29
|
import dev.apexnative.navigation.viewcontrollers.parent.ParentController
|
|
28
30
|
import dev.apexnative.navigation.viewcontrollers.parent.ReturnsScreenFromColumn
|
|
29
|
-
import dev.apexnative.navigation.viewcontrollers.parent.ShowsColumns
|
|
30
31
|
import dev.apexnative.navigation.viewcontrollers.parent.ShownChildren
|
|
32
|
+
import dev.apexnative.navigation.viewcontrollers.parent.ShowsColumns
|
|
31
33
|
import dev.apexnative.navigation.viewcontrollers.stack.StackController
|
|
32
34
|
import dev.apexnative.navigation.viewcontrollers.viewcontroller.Presenter
|
|
33
35
|
import dev.apexnative.navigation.viewcontrollers.viewcontroller.ViewController
|
|
@@ -45,8 +47,9 @@ class SplitViewController(
|
|
|
45
47
|
presenter: Presenter,
|
|
46
48
|
initialOptions: Options,
|
|
47
49
|
private var split: SplitViewOptions,
|
|
48
|
-
|
|
49
|
-
private
|
|
50
|
+
// The panes' content; `showInColumn` may give a pane a new one.
|
|
51
|
+
private var primary: ViewController<*>,
|
|
52
|
+
private var secondary: ViewController<*>,
|
|
50
53
|
private val compact: ViewController<*>?,
|
|
51
54
|
private val eventEmitter: EventEmitter,
|
|
52
55
|
) : ParentController<SplitViewLayout>(activity, childRegistry, id, presenter, initialOptions),
|
|
@@ -366,6 +369,19 @@ class SplitViewController(
|
|
|
366
369
|
return can || super.canHandleBack()
|
|
367
370
|
}
|
|
368
371
|
|
|
372
|
+
// As handleBack: the pane whose back goes first decides; an open sliding pane that would close previews nothing.
|
|
373
|
+
override fun stackPoppedByBack(): StackController? {
|
|
374
|
+
if (!isViewCreated) return super.stackPoppedByBack()
|
|
375
|
+
val layout = getView()
|
|
376
|
+
return when {
|
|
377
|
+
layout.isCompact -> compact?.stackPoppedByBack()
|
|
378
|
+
isSliding && !layout.panes.isOpen -> primary.stackPoppedByBack()
|
|
379
|
+
isSliding -> if (secondary.canHandleBack()) secondary.stackPoppedByBack() else null
|
|
380
|
+
secondary.canHandleBack() -> secondary.stackPoppedByBack()
|
|
381
|
+
else -> primary.stackPoppedByBack()
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
369
385
|
/**
|
|
370
386
|
* How far the pane of [child] is from where it rests: the secondary pane sliding over the primary one rests over
|
|
371
387
|
* it, closed or on its way. Zero for the other children.
|
|
@@ -408,27 +424,67 @@ class SplitViewController(
|
|
|
408
424
|
}
|
|
409
425
|
|
|
410
426
|
/**
|
|
411
|
-
* Shows [screen] in
|
|
412
|
-
*
|
|
427
|
+
* Shows [screen] in [column] (`primary` or `secondary`; Android has no other) in place of what the pane shows, and
|
|
428
|
+
* brings the pane into view. A stack pane's screens are replaced by it, unless it is a container
|
|
429
|
+
* (![goesInStack]): `columnIsStack`. Any other pane is taken by it, and its content goes. A column the split view
|
|
430
|
+
* doesn't have: `noSuchColumn`. False with the compact layout on screen: the screen goes over [from], the screen
|
|
431
|
+
* the command comes from.
|
|
413
432
|
*/
|
|
414
|
-
override fun showScreen(
|
|
433
|
+
override fun showScreen(
|
|
434
|
+
screen: ViewController<*>,
|
|
435
|
+
column: String,
|
|
436
|
+
goesInStack: Boolean,
|
|
437
|
+
from: ViewController<*>,
|
|
438
|
+
listener: CommandListener,
|
|
439
|
+
): Boolean {
|
|
415
440
|
if (isViewCreated && getView().isCompact) return false
|
|
416
|
-
val
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
441
|
+
val pane = when (column) {
|
|
442
|
+
PRIMARY -> primary
|
|
443
|
+
SECONDARY -> secondary
|
|
444
|
+
else -> null
|
|
420
445
|
}
|
|
421
|
-
|
|
422
|
-
|
|
446
|
+
when {
|
|
447
|
+
pane == null -> listener.onError(ErrorTexts.noSuchColumn(from.id, column))
|
|
448
|
+
pane is StackController && !goesInStack -> listener.onError(ErrorTexts.columnIsStack(from.id, column))
|
|
449
|
+
pane is StackController -> pane.setRoot(listOf(screen), false, object : CommandListenerAdapter() {
|
|
450
|
+
override fun onSuccess(childId: String) {
|
|
451
|
+
updateBackButton()
|
|
452
|
+
bringIntoView(column) { listener.onSuccess(childId) }
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
override fun onError(message: String) = listener.onError(message)
|
|
456
|
+
})
|
|
457
|
+
else -> {
|
|
458
|
+
replacePane(pane, screen)
|
|
423
459
|
updateBackButton()
|
|
424
|
-
|
|
460
|
+
val shownId = ((screen as? OptionsScreen)?.optionsScreen ?: screen).id
|
|
461
|
+
bringIntoView(column) { listener.onSuccess(shownId) }
|
|
425
462
|
}
|
|
426
|
-
|
|
427
|
-
override fun onError(message: String) = listener.onError(message)
|
|
428
|
-
})
|
|
463
|
+
}
|
|
429
464
|
return true
|
|
430
465
|
}
|
|
431
466
|
|
|
467
|
+
// [next] takes the pane of [old], a screen alone or a container, as iOS puts a screen in place of a column's: its
|
|
468
|
+
// view goes where the old one's was, the children shown are told as a move of the panes tells them (DidDisappear
|
|
469
|
+
// of what left, DidAppear of what came), and [old] goes with its screens.
|
|
470
|
+
private fun replacePane(old: ViewController<*>, next: ViewController<*>) {
|
|
471
|
+
next.setParentController(this)
|
|
472
|
+
if (old === primary) primary = next else secondary = next
|
|
473
|
+
peekView()?.let { layout ->
|
|
474
|
+
layout.panes.replacePane(old.getView(), next.getView())
|
|
475
|
+
applyTestIds(primary.getView(), secondary.getView())
|
|
476
|
+
hideCoveredPane()
|
|
477
|
+
shownChildren.moved()
|
|
478
|
+
tellShownChanged()
|
|
479
|
+
}
|
|
480
|
+
old.destroy()
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// The pane of [column] comes into view; [done] once the panes stopped.
|
|
484
|
+
private fun bringIntoView(column: String, done: Runnable) {
|
|
485
|
+
if (column == PRIMARY) setColumnShown(PRIMARY, true, done) else showSecondary(done)
|
|
486
|
+
}
|
|
487
|
+
|
|
432
488
|
private fun showSecondary(done: Runnable) {
|
|
433
489
|
if (!isSliding || getView().panes.isOpen) {
|
|
434
490
|
done.run()
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
package dev.apexnative.navigation.viewcontrollers.stack
|
|
2
|
+
|
|
3
|
+
import android.animation.Animator
|
|
4
|
+
import android.animation.AnimatorListenerAdapter
|
|
5
|
+
import android.animation.ValueAnimator
|
|
6
|
+
import android.content.res.Configuration
|
|
7
|
+
import android.graphics.Bitmap
|
|
8
|
+
import android.graphics.Canvas
|
|
9
|
+
import android.graphics.Color
|
|
10
|
+
import android.graphics.Outline
|
|
11
|
+
import android.graphics.Rect
|
|
12
|
+
import android.os.Build
|
|
13
|
+
import android.os.Handler
|
|
14
|
+
import android.os.Looper
|
|
15
|
+
import android.view.PixelCopy
|
|
16
|
+
import android.view.View
|
|
17
|
+
import android.view.ViewGroup
|
|
18
|
+
import android.view.ViewOutlineProvider
|
|
19
|
+
import android.view.Window
|
|
20
|
+
import android.view.animation.DecelerateInterpolator
|
|
21
|
+
import android.view.animation.LinearInterpolator
|
|
22
|
+
import android.view.animation.PathInterpolator
|
|
23
|
+
import android.widget.ImageView
|
|
24
|
+
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
25
|
+
import dev.apexnative.navigation.views.animations.SystemTransition
|
|
26
|
+
import dev.apexnative.navigation.views.stack.StackLayout
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The preview of a pop while the user's back gesture runs (predictive back), as the system shows the activity under
|
|
30
|
+
* the one that closes: the screen on top with its bar, a picture taken as the gesture starts, shrinks toward the
|
|
31
|
+
* gesture's side with rounded corners and follows the finger up and down; the screen under it shows behind it with
|
|
32
|
+
* its own bar, smaller and dimmed. Released, the pop plays its end ([commit]); cancelled, everything goes back
|
|
33
|
+
* ([cancel]). The values are those of the system's cross-activity back animation (WMShell
|
|
34
|
+
* `DefaultCrossActivityBackAnimation` of Android 15 and 16, the smallest scale as Android 17 shows it), also on Android
|
|
35
|
+
* 14, whose own preview differs. Main thread only.
|
|
36
|
+
*/
|
|
37
|
+
internal class BackPreview(
|
|
38
|
+
private val layout: StackLayout,
|
|
39
|
+
private val top: View,
|
|
40
|
+
private val target: View,
|
|
41
|
+
/** The gesture started at the left edge: the screen moves right; from the right edge it stays centered. */
|
|
42
|
+
private val fromLeft: Boolean,
|
|
43
|
+
private val startY: Float,
|
|
44
|
+
/**
|
|
45
|
+
* The room the tabs take at the bottom of the top screen: the card is the stack above them, rounded on all four
|
|
46
|
+
* corners, while the tab bar stays, as Gmail shows it.
|
|
47
|
+
*/
|
|
48
|
+
private val bottomRoom: Int,
|
|
49
|
+
/** The picture is there: the stack shows the screen under the top, with its bar, before the preview draws. */
|
|
50
|
+
private val onReveal: () -> Unit,
|
|
51
|
+
) {
|
|
52
|
+
private val density = layout.resources.displayMetrics.density
|
|
53
|
+
private val bar = layout.topBar
|
|
54
|
+
private val scrim = View(layout.context)
|
|
55
|
+
private var card: ImageView? = null
|
|
56
|
+
private var progress = 0f
|
|
57
|
+
private var touchY = startY
|
|
58
|
+
private var animator: Animator? = null
|
|
59
|
+
private var ended = false
|
|
60
|
+
private var topAlpha = 1f
|
|
61
|
+
// What puts the stack as it was after a cancel: once, as the preview ends.
|
|
62
|
+
private var onCancelled: (() -> Unit)? = null
|
|
63
|
+
// Forward is to the end of the line: right to left, the screens move the other way, as the stack's pop does.
|
|
64
|
+
private val forward = if (layout.layoutDirection == View.LAYOUT_DIRECTION_RTL) -1f else 1f
|
|
65
|
+
|
|
66
|
+
/** The picture of the top is there and the screen under it shows: the preview draws. */
|
|
67
|
+
val isReady: Boolean
|
|
68
|
+
get() = card != null && !ended
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Takes the picture of the stack as it shows now, drawn from its views (what lies over the stack, an overlay or a
|
|
72
|
+
* tab bar drawn over it, stays out of it); with a hardware bitmap in it, copied from the window a frame later. The
|
|
73
|
+
* preview draws once it is there.
|
|
74
|
+
*/
|
|
75
|
+
fun start(window: Window) {
|
|
76
|
+
val width = layout.width
|
|
77
|
+
val height = areaHeight
|
|
78
|
+
if (width <= 0 || height <= 0) return
|
|
79
|
+
val picture = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
|
80
|
+
try {
|
|
81
|
+
layout.draw(Canvas(picture))
|
|
82
|
+
show(picture)
|
|
83
|
+
return
|
|
84
|
+
} catch (e: IllegalArgumentException) {
|
|
85
|
+
picture.eraseColor(Color.TRANSPARENT)
|
|
86
|
+
}
|
|
87
|
+
val place = IntArray(2)
|
|
88
|
+
layout.getLocationInWindow(place)
|
|
89
|
+
val area = Rect(place[0], place[1], place[0] + width, place[1] + height)
|
|
90
|
+
PixelCopy.request(window, area, picture, { result ->
|
|
91
|
+
if (result == PixelCopy.SUCCESS && !ended) show(picture)
|
|
92
|
+
}, Handler(Looper.getMainLooper()))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** The gesture moved: [value] is its progress, 0 … 1, and [y] the finger's height in the window. */
|
|
96
|
+
fun progress(value: Float, y: Float) {
|
|
97
|
+
progress = value
|
|
98
|
+
touchY = y
|
|
99
|
+
if (isReady) drawGesture(GESTURE_CURVE.getInterpolation(value))
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The gesture was cancelled: the preview springs back, then [restore] puts the stack as it was; at once when the
|
|
104
|
+
* preview ends earlier (a new gesture, a command).
|
|
105
|
+
*/
|
|
106
|
+
fun cancel(restore: () -> Unit) {
|
|
107
|
+
onCancelled = restore
|
|
108
|
+
if (!isReady) return endAtOnce()
|
|
109
|
+
val from = progress
|
|
110
|
+
animator?.cancel()
|
|
111
|
+
animator = ValueAnimator.ofFloat(from, 0f).apply {
|
|
112
|
+
duration = CANCEL_MILLIS
|
|
113
|
+
interpolator = DecelerateInterpolator()
|
|
114
|
+
addUpdateListener { drawGesture(GESTURE_CURVE.getInterpolation(it.animatedValue as Float)) }
|
|
115
|
+
addListener(object : AnimatorListenerAdapter() {
|
|
116
|
+
override fun onAnimationEnd(animation: Animator) {
|
|
117
|
+
if (animator === animation) endAtOnce()
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
start()
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* The gesture was released and the pop runs: the top goes back to its size while it moves away by 96 dp and fades
|
|
126
|
+
* in the first fifth, the screen under it grows to its place, the dim fades; [done] once at the end, after the
|
|
127
|
+
* preview took its views away.
|
|
128
|
+
*/
|
|
129
|
+
fun commit(done: () -> Unit) {
|
|
130
|
+
val card = card ?: return done()
|
|
131
|
+
val distance = SystemTransition.DISTANCE_DP * density
|
|
132
|
+
val q = GESTURE_CURVE.getInterpolation(progress)
|
|
133
|
+
val scale = scaleAt(q)
|
|
134
|
+
val cardX = card.translationX
|
|
135
|
+
val cardY = card.translationY
|
|
136
|
+
val dim = scrim.alpha
|
|
137
|
+
animator?.cancel()
|
|
138
|
+
animator = ValueAnimator.ofFloat(0f, 1f).apply {
|
|
139
|
+
duration = SystemTransition.DURATION
|
|
140
|
+
interpolator = LinearInterpolator()
|
|
141
|
+
addUpdateListener {
|
|
142
|
+
val t = it.animatedValue as Float
|
|
143
|
+
val e = SystemTransition.CURVE.getInterpolation(t)
|
|
144
|
+
card.scaleX = lerp(scale, 1f, e)
|
|
145
|
+
card.scaleY = card.scaleX
|
|
146
|
+
card.translationX = lerp(cardX, distance * forward, e)
|
|
147
|
+
card.translationY = lerp(cardY, 0f, e)
|
|
148
|
+
card.alpha = (1f - 5f * t).coerceAtLeast(0f)
|
|
149
|
+
entering(lerp(scale, 1f, e), lerp(-distance * forward, 0f, e))
|
|
150
|
+
scrim.alpha = lerp(dim, 0f, t)
|
|
151
|
+
}
|
|
152
|
+
addListener(object : AnimatorListenerAdapter() {
|
|
153
|
+
override fun onAnimationEnd(animation: Animator) {
|
|
154
|
+
if (animator !== animation) return
|
|
155
|
+
endAtOnce()
|
|
156
|
+
done()
|
|
157
|
+
}
|
|
158
|
+
})
|
|
159
|
+
start()
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The preview ends where it is: its views go, the screens and the bar take their own values again, and a cancel's
|
|
165
|
+
* restore runs.
|
|
166
|
+
*/
|
|
167
|
+
fun endAtOnce() {
|
|
168
|
+
if (ended) return
|
|
169
|
+
ended = true
|
|
170
|
+
animator?.let {
|
|
171
|
+
animator = null
|
|
172
|
+
it.cancel()
|
|
173
|
+
}
|
|
174
|
+
card?.let { (it.parent as? ViewGroup)?.removeView(it) }
|
|
175
|
+
card = null
|
|
176
|
+
(scrim.parent as? ViewGroup)?.removeView(scrim)
|
|
177
|
+
top.alpha = topAlpha
|
|
178
|
+
entering(1f, 0f)
|
|
179
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
180
|
+
target.resetPivot()
|
|
181
|
+
bar.resetPivot()
|
|
182
|
+
}
|
|
183
|
+
onCancelled?.let {
|
|
184
|
+
onCancelled = null
|
|
185
|
+
it()
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// The picture came: the screen under the top shows with its bar, dimmed, and the picture lies over everything in
|
|
190
|
+
// the top's place, cut to rounded corners.
|
|
191
|
+
private fun show(picture: Bitmap) {
|
|
192
|
+
onReveal()
|
|
193
|
+
val dark = layout.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
|
|
194
|
+
Configuration.UI_MODE_NIGHT_YES
|
|
195
|
+
scrim.setBackgroundColor(Color.BLACK)
|
|
196
|
+
scrim.alpha = if (dark) SCRIM_DARK else SCRIM_LIGHT
|
|
197
|
+
scrim.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
|
|
198
|
+
layout.addOverAll(scrim, fill())
|
|
199
|
+
val radius = CORNER_DP * density
|
|
200
|
+
val area = CoordinatorLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, areaHeight)
|
|
201
|
+
card = ImageView(layout.context).apply {
|
|
202
|
+
setImageBitmap(picture)
|
|
203
|
+
scaleType = ImageView.ScaleType.FIT_XY
|
|
204
|
+
importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
|
|
205
|
+
outlineProvider = object : ViewOutlineProvider() {
|
|
206
|
+
override fun getOutline(view: View, outline: Outline) =
|
|
207
|
+
outline.setRoundRect(0, 0, view.width, view.height, radius)
|
|
208
|
+
}
|
|
209
|
+
clipToOutline = true
|
|
210
|
+
layout.addOverAll(this, area)
|
|
211
|
+
}
|
|
212
|
+
// Transparent, not invisible: a surface in it (a video, a map) stays, and so does the focus.
|
|
213
|
+
topAlpha = top.alpha
|
|
214
|
+
top.alpha = 0f
|
|
215
|
+
drawGesture(GESTURE_CURVE.getInterpolation(progress))
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// The values of the gesture at [q], its progress on the system's curve.
|
|
219
|
+
private fun drawGesture(q: Float) {
|
|
220
|
+
val card = card ?: return
|
|
221
|
+
val width = layout.width.toFloat()
|
|
222
|
+
val height = areaHeight.toFloat()
|
|
223
|
+
val margin = MARGIN_DP * density
|
|
224
|
+
val scale = scaleAt(q)
|
|
225
|
+
card.scaleX = scale
|
|
226
|
+
card.scaleY = scale
|
|
227
|
+
// From the left edge the screen's right side ends 8 dp from the window's right edge; from the right edge it stays
|
|
228
|
+
// centered.
|
|
229
|
+
card.translationX = if (fromLeft) (width - width * scale) / 2f - margin * q else 0f
|
|
230
|
+
// Up and down with the finger, never closer than 8 dp to the window's edges.
|
|
231
|
+
val moved = touchY - startY
|
|
232
|
+
val ratio = FOLLOW_CURVE.getInterpolation((kotlin.math.abs(moved) / (height / 2f)).coerceAtMost(1f))
|
|
233
|
+
card.translationY = ((height - height * scale) / 2f - margin).coerceAtLeast(0f) * ratio *
|
|
234
|
+
Math.signum(moved)
|
|
235
|
+
entering(scale, -SystemTransition.DISTANCE_DP * density * forward)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// The screen under the top and its bar at [scale] around the stack's center, [x] along the line.
|
|
239
|
+
private fun entering(scale: Float, x: Float) {
|
|
240
|
+
for (view in listOf(target, bar)) {
|
|
241
|
+
view.pivotX = layout.width / 2f - view.left
|
|
242
|
+
view.pivotY = areaHeight / 2f - view.top
|
|
243
|
+
view.scaleX = scale
|
|
244
|
+
view.scaleY = scale
|
|
245
|
+
view.translationX = x
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// The stack above the tabs' room: what the card shows and what the screens shrink around.
|
|
250
|
+
private val areaHeight: Int
|
|
251
|
+
get() = (layout.height - bottomRoom).coerceAtLeast(0)
|
|
252
|
+
|
|
253
|
+
private fun scaleAt(q: Float): Float = 1f - (1f - minScale()) * q
|
|
254
|
+
|
|
255
|
+
private fun fill() = CoordinatorLayout.LayoutParams(
|
|
256
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
257
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
private fun lerp(from: Float, to: Float, fraction: Float): Float = from + (to - from) * fraction
|
|
261
|
+
|
|
262
|
+
private companion object {
|
|
263
|
+
/** The system's curve of the back gesture's progress (`BackGestureInterpolator`). */
|
|
264
|
+
val GESTURE_CURVE = PathInterpolator(0.1f, 0.1f, 0f, 1f)
|
|
265
|
+
|
|
266
|
+
/** How the screen follows the finger up and down. */
|
|
267
|
+
val FOLLOW_CURVE = DecelerateInterpolator()
|
|
268
|
+
|
|
269
|
+
/** The room the shrunk screen keeps from the window's edges. */
|
|
270
|
+
const val MARGIN_DP = 8f
|
|
271
|
+
|
|
272
|
+
/** The corners of the shrunk screen, as Android 17 draws them on a Pixel (6.5 dp at its smallest). */
|
|
273
|
+
const val CORNER_DP = 7.5f
|
|
274
|
+
|
|
275
|
+
const val SCRIM_LIGHT = 0.2f
|
|
276
|
+
const val SCRIM_DARK = 0.8f
|
|
277
|
+
|
|
278
|
+
/** The cancel springs back about as fast as the system's (`BackProgressAnimator`'s spring, no bounce). */
|
|
279
|
+
const val CANCEL_MILLIS = 350L
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* The smallest scale of the screens: 0.9 in WMShell of Android 15 and 16 (and here on Android 14); Android 17
|
|
283
|
+
* shrinks the window further (0.86, measured on a Pixel 6).
|
|
284
|
+
*/
|
|
285
|
+
fun minScale(): Float = if (android.os.Build.VERSION.SDK_INT >= 37) 0.86f else 0.9f
|
|
286
|
+
}
|
|
287
|
+
}
|
package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackCommands.kt
CHANGED
|
@@ -105,33 +105,42 @@ internal class StackCommands(
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
/**
|
|
108
|
-
* `showInColumn` (Android): [layout] in
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
108
|
+
* `showInColumn` (Android): [layout] in [column] of the split view of the screen [componentId], or over that screen
|
|
109
|
+
* in its stack. Before any node: the screen is there, else `the screen '<id>' is not shown`; the layout has no id
|
|
110
|
+
* that shows already; out of a split view, the push's check of the layout (a container, which the stack doesn't
|
|
111
|
+
* take); a screen prepared with the id goes. The screen is made at once, a native screen's view too. The split view
|
|
112
|
+
* takes it — in place of a stack column's screens or of a column's content — with the answer once its pane
|
|
113
|
+
* stopped, or rejects (a column it doesn't have, a container for a stack column); with the compact layout on
|
|
114
|
+
* screen, or without a split view, it is pushed over [componentId] in its stack, with the push's checks, answer and
|
|
115
|
+
* errors; neither: `the screen '<id>' is neither in a split view nor in a stack`. Every rejection after the screen
|
|
116
|
+
* was made destroys it. Two calls in a row each make their screen the column's content in turn, and each answers
|
|
117
|
+
* its own id.
|
|
116
118
|
*/
|
|
117
|
-
fun showInColumn(componentId: String, layout: LayoutNode, answer: CommandAnswer, what: String) {
|
|
119
|
+
fun showInColumn(componentId: String, column: String, layout: LayoutNode, answer: CommandAnswer, what: String) {
|
|
118
120
|
val source = find(componentId)?.takeIf { !prepared.isPrepared(it) }
|
|
119
121
|
?: return answer.reject(ErrorTexts.screenNotShown(componentId))
|
|
120
122
|
val shown = LayoutChecks.firstShownId(listOf(layout), ::isShown) { false }
|
|
121
123
|
if (shown != null) return answer.reject(ErrorTexts.alreadyShown(shown))
|
|
122
124
|
val columns = nodeWith<ShowsColumns>(source)
|
|
123
125
|
val stack = source as? StackController ?: source.findParentStack()
|
|
124
|
-
|
|
125
|
-
|
|
126
|
+
// Out of a split view it can only be a push: the stack's check comes before any node.
|
|
127
|
+
if (columns == null && stack != null) {
|
|
128
|
+
LayoutChecks.checkStackChild(layout, stack.id)?.let { return answer.reject(it) }
|
|
129
|
+
}
|
|
126
130
|
if (!factory.canMake(layout)) return answer.reject(ErrorTexts.notDoneYet("showInColumn"))
|
|
127
131
|
prepared.discard(layout.id)
|
|
128
132
|
val node = make(layout)
|
|
129
133
|
try {
|
|
130
|
-
|
|
134
|
+
val goesInStack = LayoutChecks.isStackChild(layout)
|
|
135
|
+
if (columns?.showScreen(node, column, goesInStack, source, destroyingOnError(node, answer)) == true) return
|
|
131
136
|
if (stack == null) {
|
|
132
137
|
node.destroy()
|
|
133
138
|
return answer.reject(ErrorTexts.neitherSplitViewNorStack(componentId))
|
|
134
139
|
}
|
|
140
|
+
LayoutChecks.checkStackChild(layout, stack.id)?.let {
|
|
141
|
+
node.destroy()
|
|
142
|
+
return answer.reject(it)
|
|
143
|
+
}
|
|
135
144
|
pushMade(stack, source, node, layout, answer, what)
|
|
136
145
|
} catch (e: Throwable) {
|
|
137
146
|
node.destroy()
|
|
@@ -225,8 +234,9 @@ internal class StackCommands(
|
|
|
225
234
|
fun setStackRoot(componentId: String, layouts: List<LayoutNode>, answer: CommandAnswer, what: String) {
|
|
226
235
|
if (layouts.isEmpty()) return answer.reject(ErrorTexts.noScreensForStack(componentId))
|
|
227
236
|
val stack = stackOf(componentId, answer) ?: return
|
|
228
|
-
// An id of this stack may come again: the new screen replaces it (`setStackRoot` step 3).
|
|
229
|
-
|
|
237
|
+
// An id of an entry of this stack may come again: the new screen replaces it (`setStackRoot` step 3). The
|
|
238
|
+
// stack's own id, and a screen an accepted cut or replace took away, show elsewhere or still go: as on iOS.
|
|
239
|
+
val shown = LayoutChecks.firstShownId(layouts, ::isShown) { isInEntryOf(stack, it.id) }
|
|
230
240
|
if (shown != null) return answer.reject(ErrorTexts.alreadyShown(shown))
|
|
231
241
|
for (layout in layouts) LayoutChecks.checkStackChild(layout, stack.id)?.let { return answer.reject(it) }
|
|
232
242
|
if (!layouts.all(factory::canMake)) return answer.reject(ErrorTexts.notDoneYet("setStackRoot"))
|
|
@@ -357,10 +367,10 @@ internal class StackCommands(
|
|
|
357
367
|
// A node of the registry has [id], and it isn't only prepared for a push (`push` step 1, `preparePush`).
|
|
358
368
|
private fun isShown(id: String): Boolean = registry.find(id)?.let { !prepared.isPrepared(it) } == true
|
|
359
369
|
|
|
360
|
-
// The node with [id] is [stack]
|
|
361
|
-
private fun
|
|
362
|
-
val node = find(id) ?: return false
|
|
363
|
-
return node
|
|
370
|
+
// The node with [id] is in an entry of [stack] as JavaScript sees it (`StackQueue.entries`), not the stack itself.
|
|
371
|
+
private fun isInEntryOf(stack: StackController, id: String): Boolean {
|
|
372
|
+
val node = find(id)?.takeIf { it !== stack } ?: return false
|
|
373
|
+
return stack.entryOf(node)?.let { it in stack.queue.entries } == true
|
|
364
374
|
}
|
|
365
375
|
|
|
366
376
|
// A node of [layout], with its view made now when it has a native screen: its creator's failure rejects the
|