@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
package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabPresenter.kt
CHANGED
|
@@ -172,19 +172,20 @@ internal class FabPresenter(
|
|
|
172
172
|
alignStart = options.alignHorizontally.get("right") == "left"
|
|
173
173
|
val source = source
|
|
174
174
|
fab?.let { fab ->
|
|
175
|
-
applyButton(fab, options)
|
|
175
|
+
applyButton(fab, options, mayWait = fab.drawable != null || options.visible.isFalse)
|
|
176
176
|
TestIds.apply(fab, options.testID.takeIf { it.hasValue() }?.get())
|
|
177
177
|
val id = shownId.orEmpty()
|
|
178
178
|
fab.setOnClickListener { press(source, id) }
|
|
179
179
|
}
|
|
180
180
|
menu?.let { menu ->
|
|
181
|
-
applyButton(menu.main, options)
|
|
181
|
+
applyButton(menu.main, options, mayWait = menu.main.drawable != null || options.visible.isFalse)
|
|
182
182
|
TestIds.apply(menu.main, options.testID.takeIf { it.hasValue() }?.get())
|
|
183
183
|
menu.scrimColor = options.scrimColor.get(activity, null)
|
|
184
184
|
menu.setActions(options.actions.orEmpty().map { action ->
|
|
185
185
|
val actionId = action.id.get("")
|
|
186
186
|
Fab(activity, actionId).also { button ->
|
|
187
|
-
|
|
187
|
+
// The actions show only while the menu is open.
|
|
188
|
+
applyButton(button, action, mayWait = !menu.isOpen)
|
|
188
189
|
TestIds.apply(button, action.testID.takeIf { it.hasValue() }?.get())
|
|
189
190
|
button.setOnClickListener {
|
|
190
191
|
press(source, actionId)
|
|
@@ -205,9 +206,10 @@ internal class FabPresenter(
|
|
|
205
206
|
place()
|
|
206
207
|
}
|
|
207
208
|
|
|
208
|
-
|
|
209
|
+
// [mayWait]: the button can take its icon when it is ready (it shows one, or it stays hidden), see [Fab.applyIcon].
|
|
210
|
+
private fun applyButton(button: Fab, options: FabOptions, mayWait: Boolean) {
|
|
209
211
|
val icon = options.icon.takeIf { it.hasValue() }?.get()?.ifEmpty { null }
|
|
210
|
-
if (icon != null) button.applyIcon(icon, options.iconColor)
|
|
212
|
+
if (icon != null) button.applyIcon(icon, options.iconColor, mayWait)
|
|
211
213
|
applyColors(button, options)
|
|
212
214
|
button.setMini(options.size.get("regular") == "mini")
|
|
213
215
|
}
|
|
@@ -14,7 +14,6 @@ import androidx.core.view.WindowCompat
|
|
|
14
14
|
import androidx.core.view.WindowInsetsCompat
|
|
15
15
|
import com.facebook.react.ReactApplication
|
|
16
16
|
import dev.apexnative.navigation.options.SearchBarOptions
|
|
17
|
-
import dev.apexnative.navigation.options.params.Text
|
|
18
17
|
import dev.apexnative.navigation.options.params.ThemeColour
|
|
19
18
|
import dev.apexnative.navigation.react.events.EventEmitter
|
|
20
19
|
import dev.apexnative.navigation.utils.dp
|
|
@@ -148,10 +147,11 @@ class StackSearchPresenter(
|
|
|
148
147
|
|
|
149
148
|
private fun searchOf(screen: ViewController<*>): ScreenSearch =
|
|
150
149
|
searches.getOrPut(screen.view) {
|
|
151
|
-
// The search of a pair of screens is its primary's: its events name that screen, which keeps the scope
|
|
150
|
+
// The search of a pair of screens is its primary's: its events name that screen, which keeps the scope in
|
|
151
|
+
// its user layer, where a merge that sends the same options again doesn't take it away.
|
|
152
152
|
val owner = (screen as? OptionsScreen)?.optionsScreen ?: screen
|
|
153
153
|
ScreenSearch(this, owner.id, styleContext(), loadIcon) { scope ->
|
|
154
|
-
owner.
|
|
154
|
+
owner.changeChoices { it.pickScope(scope) }
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
157
|
|
package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/MenuChecks.kt
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
package dev.apexnative.navigation.viewcontrollers.stack.topbar
|
|
2
2
|
|
|
3
|
-
import dev.apexnative.navigation.options.ButtonMenuOptions
|
|
4
|
-
import dev.apexnative.navigation.options.ButtonOptions
|
|
5
3
|
import dev.apexnative.navigation.options.Options
|
|
6
|
-
import dev.apexnative.navigation.options.
|
|
4
|
+
import dev.apexnative.navigation.options.UserChoices
|
|
7
5
|
import dev.apexnative.navigation.viewcontrollers.viewcontroller.ViewController
|
|
8
6
|
|
|
9
7
|
/**
|
|
10
|
-
* The check of a single-selection menu the user picked: written
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
8
|
+
* The check of a single-selection menu the user picked: written into the screen's user layer
|
|
9
|
+
* ([ViewController.changeChoices]), never into its own options nor into a menu other screens share (the stack's, the
|
|
10
|
+
* defaults'). It goes over the menu as the screen's options resolve, by the button's id and the item's id, and stays
|
|
11
|
+
* until the app changes that menu's checks or takes the item away. Main thread only.
|
|
14
12
|
*/
|
|
15
13
|
internal object MenuChecks {
|
|
16
14
|
/**
|
|
@@ -19,54 +17,13 @@ internal object MenuChecks {
|
|
|
19
17
|
*/
|
|
20
18
|
fun write(screen: ViewController<*>, resolved: Options, buttonId: String?, itemId: String) {
|
|
21
19
|
val topBar = resolved.topBar
|
|
22
|
-
if (buttonId == null) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return
|
|
31
|
-
}
|
|
32
|
-
val left = topBar.buttons.left?.any { it.id.get(null) == buttonId } == true
|
|
33
|
-
val list = (if (left) topBar.buttons.left else topBar.buttons.right) ?: return
|
|
34
|
-
val menu = list.firstOrNull { it.id.get(null) == buttonId }?.menu ?: return
|
|
35
|
-
if (!checks(menu, itemId)) return
|
|
36
|
-
screen.changeOptions { own ->
|
|
37
|
-
val ownList = (if (left) own.topBar.buttons.left else own.topBar.buttons.right)
|
|
38
|
-
?: list.map(ButtonOptions::copyWithMenu)
|
|
39
|
-
val button = ownList.firstOrNull { it.id.get(null) == buttonId } ?: return@changeOptions
|
|
40
|
-
val copy = (button.menu ?: menu).copyMenu()
|
|
41
|
-
check(copy, itemId)
|
|
42
|
-
button.menu = copy
|
|
43
|
-
if (left) own.topBar.buttons.left = ownList else own.topBar.buttons.right = ownList
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** Whether picking [itemId] moves a check: its level has `singleSelection`. */
|
|
48
|
-
fun checks(menu: ButtonMenuOptions, itemId: String): Boolean = levelOf(menu.items, menu.singleSelection, itemId) == true
|
|
49
|
-
|
|
50
|
-
/** Moves the check of [itemId]'s level to it, in [menu] itself. */
|
|
51
|
-
fun check(menu: ButtonMenuOptions, itemId: String) {
|
|
52
|
-
mark(menu.items, menu.singleSelection, itemId)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Whether the level holding [itemId] has single selection; null when [itemId] isn't in [elements].
|
|
56
|
-
private fun levelOf(elements: List<ButtonMenuOptions.Element>, single: Boolean, itemId: String): Boolean? {
|
|
57
|
-
if (elements.any { it is ButtonMenuOptions.Item && it.id == itemId }) return single
|
|
58
|
-
for (element in elements) {
|
|
59
|
-
if (element !is ButtonMenuOptions.Submenu) continue
|
|
60
|
-
levelOf(element.items, element.singleSelection, itemId)?.let { return it }
|
|
61
|
-
}
|
|
62
|
-
return null
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
private fun mark(elements: List<ButtonMenuOptions.Element>, single: Boolean, itemId: String): Boolean {
|
|
66
|
-
if (elements.any { it is ButtonMenuOptions.Item && it.id == itemId }) {
|
|
67
|
-
if (single) elements.forEach { if (it is ButtonMenuOptions.Item) it.checked = it.id == itemId }
|
|
68
|
-
return true
|
|
69
|
-
}
|
|
70
|
-
return elements.any { it is ButtonMenuOptions.Submenu && mark(it.items, it.singleSelection, itemId) }
|
|
20
|
+
val menu = if (buttonId == null) {
|
|
21
|
+
topBar.overflowMenu
|
|
22
|
+
} else {
|
|
23
|
+
(topBar.buttons.left.orEmpty() + topBar.buttons.right.orEmpty()).firstOrNull { it.id.get(null) == buttonId }
|
|
24
|
+
?.menu
|
|
25
|
+
} ?: return
|
|
26
|
+
if (!UserChoices.checks(menu, itemId)) return
|
|
27
|
+
screen.changeChoices { it.check(buttonId, menu, itemId) }
|
|
71
28
|
}
|
|
72
29
|
}
|
|
@@ -49,7 +49,9 @@ import dev.apexnative.navigation.viewcontrollers.stack.search.StackSearchPresent
|
|
|
49
49
|
import dev.apexnative.navigation.viewcontrollers.viewcontroller.IReactView
|
|
50
50
|
import dev.apexnative.navigation.viewcontrollers.viewcontroller.ScrollEventListener
|
|
51
51
|
import dev.apexnative.navigation.viewcontrollers.viewcontroller.ViewController
|
|
52
|
+
import dev.apexnative.navigation.views.animations.AnimationRules
|
|
52
53
|
import dev.apexnative.navigation.views.animations.SideAnimator
|
|
54
|
+
import dev.apexnative.navigation.views.animations.FadeThrough
|
|
53
55
|
import dev.apexnative.navigation.views.animations.SystemTransition
|
|
54
56
|
import dev.apexnative.navigation.views.component.ComponentLayout
|
|
55
57
|
import dev.apexnative.navigation.views.stack.StackLayout
|
|
@@ -124,8 +126,13 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
|
|
|
124
126
|
private var largeTitleShown = false
|
|
125
127
|
private var followed: ScrollEventListener? = null
|
|
126
128
|
private var rowTarget: RowTarget? = null
|
|
129
|
+
// The large title opening with a push from a screen without one; none at rest.
|
|
130
|
+
private var largeTitleOpening: ValueAnimator? = null
|
|
127
131
|
// The picture of the part under the row in a leaving screen's overlay: the screen and the picture.
|
|
128
132
|
private var underRow: Pair<ViewController<*>, Drawable>? = null
|
|
133
|
+
// The bar goes with the screens of a transition of the stack's own, and the screen that leaves with its picture.
|
|
134
|
+
private var travelling = false
|
|
135
|
+
private var travelLeaving: ViewController<*>? = null
|
|
129
136
|
|
|
130
137
|
private enum class Part { Look, Title, Left, Right, Back, LargeTitle, Visible, TestId }
|
|
131
138
|
|
|
@@ -161,12 +168,16 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
|
|
|
161
168
|
* The stack's current child changes to [incoming] ([motion]: the default transition that brings it, none at once):
|
|
162
169
|
* the row's picture is taken before the bar takes the new screen's options. A push from a large title to a screen
|
|
163
170
|
* without one makes the bar lower at once: the part under the row rides with the leaving screen as a picture in its
|
|
164
|
-
* overlay, and the row's picture holds the row only. So does a pop
|
|
171
|
+
* overlay, and the row's picture holds the row only. So does a pop or a fade through (`setStackRoot`) to a screen
|
|
172
|
+
* without a large title or with its large title collapsed: a picture as tall as the large title would hold the bar
|
|
173
|
+
* at its height to the end. With a transition of the stack's own ([travel]) the bar goes with the screens instead
|
|
174
|
+
* ([beginTravel]).
|
|
165
175
|
*/
|
|
166
176
|
fun beginRowTransition(
|
|
167
177
|
motion: BarMotion?,
|
|
168
178
|
incoming: ViewController<*>?,
|
|
169
179
|
visibility: StackDisplay.BarVisibility = StackDisplay.BarVisibility.AT_ONCE,
|
|
180
|
+
travel: StackDisplay.BarTravel? = null,
|
|
170
181
|
) {
|
|
171
182
|
this.motion = motion
|
|
172
183
|
this.visibilityChange = visibility
|
|
@@ -179,14 +190,68 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
|
|
|
179
190
|
rowTarget?.withLargeTitle = !lowers
|
|
180
191
|
if (lowers) pictureUnderRow(bar, leaving)
|
|
181
192
|
rowTransition?.begin(motion)
|
|
193
|
+
endTravel()
|
|
194
|
+
if (travel != null && bar != null && incoming != null) beginTravel(bar, leaving, incoming, travel, visibility)
|
|
182
195
|
}
|
|
183
196
|
|
|
184
197
|
/** The transition that brought the current child ended: the row at rest, the picture under it gone. */
|
|
185
198
|
fun endRowTransition() {
|
|
199
|
+
// A large title still opening is open now.
|
|
200
|
+
largeTitleOpening?.let {
|
|
201
|
+
largeTitleOpening = null
|
|
202
|
+
it.end()
|
|
203
|
+
}
|
|
186
204
|
motion = null
|
|
187
205
|
visibilityChange = StackDisplay.BarVisibility.AT_ONCE
|
|
188
206
|
rowTransition?.end()
|
|
189
207
|
dropPictureUnderRow()
|
|
208
|
+
endTravel()
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* The bar goes with the screens of a transition of the stack's own, as each screen's bar would: a picture of the
|
|
213
|
+
* bar as it is goes with [leaving], and the bar, which takes [incoming]'s options next, is drawn flat as [incoming]
|
|
214
|
+
* moves — under [leaving] on a pop ([StackDisplay.BarTravel.Back]) (`StackLayout.barTravels`). No picture when the
|
|
215
|
+
* bar doesn't show. Nothing travels when the new top hides the bar by a side of its own
|
|
216
|
+
* (`animations.*.topBar.exit`), which plays instead, or when the bar can't be drawn in software (a `HARDWARE`
|
|
217
|
+
* bitmap in it): the bar takes the options in its place.
|
|
218
|
+
*/
|
|
219
|
+
private fun beginTravel(
|
|
220
|
+
bar: TopBar,
|
|
221
|
+
leaving: ViewController<*>?,
|
|
222
|
+
incoming: ViewController<*>,
|
|
223
|
+
direction: StackDisplay.BarTravel,
|
|
224
|
+
visibility: StackDisplay.BarVisibility,
|
|
225
|
+
) {
|
|
226
|
+
val layout = layout ?: return
|
|
227
|
+
val screen = incoming.peekView()?.takeIf(layout::holds) ?: return
|
|
228
|
+
val leavingView = leaving?.peekView()?.takeIf(layout::holds)
|
|
229
|
+
// The new top hides the bar by a side of its own: the side plays on the bar in its place instead.
|
|
230
|
+
if (host.resolvedOf(incoming).topBar.visible.isFalse && AnimationRules.barSideGiven(visibility.side?.exit)) return
|
|
231
|
+
val shows = bar.visibility == View.VISIBLE && this.visibility?.isHidden != true
|
|
232
|
+
val picture = if (shows && leavingView != null) pictureOfBar(bar) ?: return else null
|
|
233
|
+
travelling = true
|
|
234
|
+
travelLeaving = leaving.takeIf { picture != null }
|
|
235
|
+
// Flat: with an elevation the renderer would draw the bar over every screen and out of its alpha layer.
|
|
236
|
+
bar.isFlat = true
|
|
237
|
+
layout.barTravels(screen, leavingView, picture, direction == StackDisplay.BarTravel.Back)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// The bar's picture where the bar is in the stack, its moves counted.
|
|
241
|
+
private fun pictureOfBar(bar: TopBar): Drawable? {
|
|
242
|
+
val bitmap = bar.picture() ?: return null
|
|
243
|
+
val left = (bar.left + bar.translationX).roundToInt()
|
|
244
|
+
val top = (bar.top + bar.translationY).roundToInt()
|
|
245
|
+
return BitmapDrawable(bar.resources, bitmap).apply { setBounds(left, top, left + bitmap.width, top + bitmap.height) }
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// The bar's travel ends: the picture goes, the bar is drawn in its place again with its shadow.
|
|
249
|
+
private fun endTravel() {
|
|
250
|
+
if (!travelling) return
|
|
251
|
+
travelling = false
|
|
252
|
+
travelLeaving = null
|
|
253
|
+
bar?.isFlat = false
|
|
254
|
+
layout?.endBarTravel()
|
|
190
255
|
}
|
|
191
256
|
|
|
192
257
|
/**
|
|
@@ -205,8 +270,9 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
|
|
|
205
270
|
keys.clear()
|
|
206
271
|
}
|
|
207
272
|
shownEntry = entry
|
|
208
|
-
// The screen that left with the picture under the row shows again: the picture goes.
|
|
273
|
+
// The screen that left with the picture under the row, or of the bar, shows again: the picture goes.
|
|
209
274
|
if (underRow?.first === entry) dropPictureUnderRow()
|
|
275
|
+
if (travelLeaving === entry) endTravel()
|
|
210
276
|
val options = host.resolvedOf(entry)
|
|
211
277
|
applyAll(bar, entry, screen, options, merge = false)
|
|
212
278
|
}
|
|
@@ -334,7 +400,9 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
|
|
|
334
400
|
/** The stack is destroyed: every component stops. */
|
|
335
401
|
fun destroy() {
|
|
336
402
|
rowTransition?.end()
|
|
403
|
+
endTravel()
|
|
337
404
|
largeTitleFollower.stop()
|
|
405
|
+
stopLargeTitleOpening()
|
|
338
406
|
collapse?.disableCollapse()
|
|
339
407
|
components.destroyAll()
|
|
340
408
|
cells.clear()
|
|
@@ -370,6 +438,9 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
|
|
|
370
438
|
)
|
|
371
439
|
}
|
|
372
440
|
|
|
441
|
+
/** Whether the bar shows [entry]: its parts are applied, so its place in the layout is [entry]'s. */
|
|
442
|
+
fun shows(entry: ViewController<*>): Boolean = entry === shownEntry
|
|
443
|
+
|
|
373
444
|
/** The height of the search under the row of [entry] when it is the screen the bar shows; 0 otherwise. */
|
|
374
445
|
fun searchHeightOf(entry: ViewController<*>): Int =
|
|
375
446
|
if (entry === shownEntry) bar?.searchAreaHeight ?: 0 else 0
|
|
@@ -633,11 +704,13 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
|
|
|
633
704
|
}
|
|
634
705
|
|
|
635
706
|
// Whether [incoming] shows less of the bar under the row than the large title now: a push to a screen without a
|
|
636
|
-
// large title, or a pop
|
|
707
|
+
// large title, or a pop or a fade through to one without a large title or with its large title collapsed. The lower
|
|
708
|
+
// bar would cut the row's picture.
|
|
637
709
|
private fun lowersBar(motion: BarMotion?, incoming: ViewController<*>, options: Options): Boolean = when (motion) {
|
|
638
710
|
BarMotion.Push -> !largeTitleShownFor(incoming, options)
|
|
639
|
-
BarMotion.Pop
|
|
640
|
-
|
|
711
|
+
BarMotion.Pop, BarMotion.FadeThrough ->
|
|
712
|
+
!largeTitleShownFor(incoming, options) || collapseOf(incoming, options) > 0f
|
|
713
|
+
null -> false
|
|
641
714
|
}
|
|
642
715
|
|
|
643
716
|
// How far [entry]'s large title is collapsed as it shows: by the offset its list had last, unless it doesn't follow
|
|
@@ -675,6 +748,11 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
|
|
|
675
748
|
large.fontSize.get(null), fontKey(large.font), large.collapseOnScroll.get(null), testId).joinToString()
|
|
676
749
|
if (keys[Part.LargeTitle] == key) return
|
|
677
750
|
keys[Part.LargeTitle] = key
|
|
751
|
+
// The default push or fade through from a screen without a large title opens it with the screens, on a bar that
|
|
752
|
+
// shows at rest: the bar's show and its hide on scroll move the same translation.
|
|
753
|
+
val opening = motion.takeIf { it == BarMotion.Push || it == BarMotion.FadeThrough }
|
|
754
|
+
val opens = shown && !largeTitleShown && bar.visibility == View.VISIBLE && bar.translationY == 0f
|
|
755
|
+
stopLargeTitleOpening()
|
|
678
756
|
largeTitleShown = shown
|
|
679
757
|
bar.setLargeTitleShown(shown)
|
|
680
758
|
largeTitleFollower.stop()
|
|
@@ -692,9 +770,39 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
|
|
|
692
770
|
// same frame: back to the screen, after light or dark changed, after a merge.
|
|
693
771
|
if (large.collapseOnScroll.isFalse) scrollOf(entry)?.forgetOffset()
|
|
694
772
|
largeTitleFollower.collapse(collapseOf(entry, options))
|
|
773
|
+
if (opens) opening?.let { openLargeTitle(bar, collapseOf(entry, options), it) }
|
|
695
774
|
if (!large.collapseOnScroll.isFalse) scrollOf(entry)?.let(largeTitleFollower::follow)
|
|
696
775
|
}
|
|
697
776
|
|
|
777
|
+
// The bar grows with the screens' [motion], on its curve and in its time, from the row's height to the large title
|
|
778
|
+
// as the screen's list left it: the large title comes out from under the row as it does when the list scrolls
|
|
779
|
+
// back. No frame shows the tall bar before the screens move.
|
|
780
|
+
private fun openLargeTitle(bar: TopBar, offset: Float, motion: BarMotion) {
|
|
781
|
+
val range = UiUtils.dpToPx(bar.context, LargeTitleView.HEIGHT)
|
|
782
|
+
val to = LargeTitleCollapse.shift(offset, range)
|
|
783
|
+
if (to >= range) return
|
|
784
|
+
bar.setLargeTitleCollapse(range.toInt())
|
|
785
|
+
largeTitleOpening = ValueAnimator.ofFloat(range, to).apply {
|
|
786
|
+
duration = if (motion == BarMotion.FadeThrough) FadeThrough.DURATION else SystemTransition.DURATION
|
|
787
|
+
interpolator = if (motion == BarMotion.FadeThrough) FadeThrough.CURVE else SystemTransition.CURVE
|
|
788
|
+
addUpdateListener { bar.setLargeTitleCollapse((it.animatedValue as Float).toInt()) }
|
|
789
|
+
addListener(object : AnimatorListenerAdapter() {
|
|
790
|
+
override fun onAnimationEnd(animation: Animator) {
|
|
791
|
+
if (largeTitleOpening === animation) largeTitleOpening = null
|
|
792
|
+
}
|
|
793
|
+
})
|
|
794
|
+
start()
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// Stopped where it is: what applies next sets the large title.
|
|
799
|
+
private fun stopLargeTitleOpening() {
|
|
800
|
+
val opening = largeTitleOpening ?: return
|
|
801
|
+
largeTitleOpening = null
|
|
802
|
+
opening.removeAllUpdateListeners()
|
|
803
|
+
opening.cancel()
|
|
804
|
+
}
|
|
805
|
+
|
|
698
806
|
// Shown or hidden: with the transition by its side, or by `animate` in a merge.
|
|
699
807
|
private fun applyVisibility(options: Options, merge: Boolean) {
|
|
700
808
|
val visible = !options.topBar.visible.isFalse
|
|
@@ -702,7 +810,10 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
|
|
|
702
810
|
if (keys[Part.Visible] == key) return
|
|
703
811
|
keys[Part.Visible] = key
|
|
704
812
|
val change = visibilityChange
|
|
705
|
-
|
|
813
|
+
// While the bar goes with the screens it shows or hides at once, unless its side of its own plays.
|
|
814
|
+
val travels = !merge && travelling &&
|
|
815
|
+
!AnimationRules.barSideGiven(if (visible) change.side?.enter else change.side?.exit)
|
|
816
|
+
val animated = !options.topBar.animate.isFalse && (merge || change.animated) && !travels
|
|
706
817
|
visibility?.apply(visible, animated, if (merge) null else change.side)
|
|
707
818
|
}
|
|
708
819
|
|
|
@@ -897,8 +1008,16 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
|
|
|
897
1008
|
button.priority.get(null),
|
|
898
1009
|
).joinToString("|")
|
|
899
1010
|
|
|
900
|
-
|
|
901
|
-
|
|
1011
|
+
// What the overflow menu shows, its submenus and checks with it: a check the app moves, or one of the user layer
|
|
1012
|
+
// that leaves, fills the menu again.
|
|
1013
|
+
private fun menuKey(menu: ButtonMenuOptions): String = elementsKey(menu.items) + menu.testID
|
|
1014
|
+
|
|
1015
|
+
private fun elementsKey(elements: List<ButtonMenuOptions.Element>): String = elements.joinToString("|") {
|
|
1016
|
+
"${it.title}:${it.icon}:${it.testID}" + when (it) {
|
|
1017
|
+
is ButtonMenuOptions.Item -> ":${it.checked}:${it.disabled}"
|
|
1018
|
+
is ButtonMenuOptions.Submenu -> "[${elementsKey(it.items)}]"
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
902
1021
|
|
|
903
1022
|
private fun fontKey(font: FontOptions): String =
|
|
904
1023
|
listOf(font.fontFamily, font.fontStyle, font.fontWeight).joinToString { it.get(null).toString() }
|
|
@@ -960,8 +1079,11 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
|
|
|
960
1079
|
if (large) View.IMPORTANT_FOR_ACCESSIBILITY_NO else View.IMPORTANT_FOR_ACCESSIBILITY_YES
|
|
961
1080
|
}
|
|
962
1081
|
|
|
1082
|
+
// The list scrolled while the large title opens: it follows the list from there.
|
|
963
1083
|
override fun onScrollOffset(offsetY: Float) {
|
|
964
|
-
if (largeTitleShown)
|
|
1084
|
+
if (!largeTitleShown) return
|
|
1085
|
+
stopLargeTitleOpening()
|
|
1086
|
+
collapse(offsetY)
|
|
965
1087
|
}
|
|
966
1088
|
|
|
967
1089
|
override fun onScrollUp(nextTranslation: Float) = Unit
|
|
@@ -6,6 +6,7 @@ import android.view.ViewGroup
|
|
|
6
6
|
import dev.apexnative.navigation.options.DefaultOptions
|
|
7
7
|
import dev.apexnative.navigation.options.Options
|
|
8
8
|
import dev.apexnative.navigation.options.OptionsCache
|
|
9
|
+
import dev.apexnative.navigation.options.UserChoices
|
|
9
10
|
import dev.apexnative.navigation.options.params.Bool
|
|
10
11
|
import dev.apexnative.navigation.options.params.NullBool
|
|
11
12
|
import dev.apexnative.navigation.react.CommandListener
|
|
@@ -30,11 +31,11 @@ abstract class ViewController<V : ViewGroup>(
|
|
|
30
31
|
var initialOptions: Options,
|
|
31
32
|
) {
|
|
32
33
|
/**
|
|
33
|
-
* The node's own options: those it was made with, with every `mergeOptions`
|
|
34
|
-
* Writing them is one of the
|
|
35
|
-
* options, keeps the object (a stripped copy when it has a one-shot option: the
|
|
36
|
-
* makes every resolved result stale. Don't change the object in place:
|
|
37
|
-
* changed copy.
|
|
34
|
+
* The node's own options: those it was made with, with every `mergeOptions` over them; what the user chose in the
|
|
35
|
+
* native UI is not here but in [choices]. Writing them is one of the places the options change ([OptionsCache]):
|
|
36
|
+
* the setter takes away the one-shot options, keeps the object (a stripped copy when it has a one-shot option: the
|
|
37
|
+
* caller's object never changes), and makes every resolved result stale. Don't change the object in place:
|
|
38
|
+
* [changeOptions] or [mergeOptions] write a changed copy.
|
|
38
39
|
*/
|
|
39
40
|
var options: Options = initialOptions.copy().also(Options::clearOneShotOptions)
|
|
40
41
|
set(value) {
|
|
@@ -42,6 +43,10 @@ abstract class ViewController<V : ViewGroup>(
|
|
|
42
43
|
OptionsCache.optionsChanged()
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
// The user layer: what the user chose in the native UI (a menu's check, a search scope), over the node's own
|
|
47
|
+
// options as they resolve ([changeChoices]).
|
|
48
|
+
private val choices = UserChoices()
|
|
49
|
+
|
|
45
50
|
/**
|
|
46
51
|
* The JavaScript the node was made for (`ReloadCoordinator`): the events of the node and its screens reach only
|
|
47
52
|
* that one.
|
|
@@ -94,6 +99,19 @@ abstract class ViewController<V : ViewGroup>(
|
|
|
94
99
|
open val isInSheet: Boolean
|
|
95
100
|
get() = shownAsSheet || parent?.isInSheet == true
|
|
96
101
|
|
|
102
|
+
/**
|
|
103
|
+
* The node is the root of an overlay: the overlays set it before the node's view is made, so its screens are drawn
|
|
104
|
+
* under the status bar from their first layout (`StatusBarOptions.isHiddenOrDrawBehind`).
|
|
105
|
+
*/
|
|
106
|
+
internal var shownAsOverlay = false
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The node is in an overlay: it covers the whole window, and is drawn under the status bar by default. It holds from
|
|
110
|
+
* the start of the show, while `OverlayCoordinator.isInOverlay` answers only for the overlays it lists.
|
|
111
|
+
*/
|
|
112
|
+
open val isInOverlay: Boolean
|
|
113
|
+
get() = shownAsOverlay || parent?.isInOverlay == true
|
|
114
|
+
|
|
97
115
|
/**
|
|
98
116
|
* The node is in a side of a side menu (its left or right menu, not its center): the side goes the whole height of
|
|
99
117
|
* the window, and the node keeps no room for the status bar. The layout around answers for it
|
|
@@ -143,9 +161,9 @@ abstract class ViewController<V : ViewGroup>(
|
|
|
143
161
|
}
|
|
144
162
|
|
|
145
163
|
/**
|
|
146
|
-
* The node's options over those of the layouts around it, without the default options
|
|
147
|
-
* then the node's own. A new object at each call, which the caller may
|
|
148
|
-
* what it shows with: that is [resolveShownOptions].
|
|
164
|
+
* The node's options over those of the layouts around it, without the default options and without the user layer
|
|
165
|
+
* ([changeChoices]): the parents' resolution, then the node's own. A new object at each call, which the caller may
|
|
166
|
+
* change. For a layout of nodes this is not what it shows with: that is [resolveShownOptions].
|
|
149
167
|
*/
|
|
150
168
|
open fun resolveCurrentOptions(): Options {
|
|
151
169
|
val resolved = parent?.resolveChildOptions(this) ?: Options()
|
|
@@ -153,21 +171,21 @@ abstract class ViewController<V : ViewGroup>(
|
|
|
153
171
|
}
|
|
154
172
|
|
|
155
173
|
/**
|
|
156
|
-
* The node's options over those of the layouts around it and [defaultOptions]
|
|
157
|
-
* default options ([DefaultOptions.current]) the result is
|
|
158
|
-
* ([OptionsCache]) and the layouts around the node and its current
|
|
159
|
-
* changes it copies it first. Main thread only.
|
|
174
|
+
* The node's options over those of the layouts around it and [defaultOptions], with the user layer over them
|
|
175
|
+
* ([changeChoices]): what it shows with. With the app's default options ([DefaultOptions.current]) the result is
|
|
176
|
+
* kept and given again while no options changed ([OptionsCache]) and the layouts around the node and its current
|
|
177
|
+
* children are the same: it is read-only, whoever changes it copies it first. Main thread only.
|
|
160
178
|
*
|
|
161
179
|
* For a layout of nodes (a stack, tabs, a side menu) it is the layout's own options over those around it, without
|
|
162
180
|
* any child's: not what the layout shows with, which [resolveShownOptions] gives.
|
|
163
181
|
*/
|
|
164
182
|
open fun resolveCurrentOptions(defaultOptions: Options): Options {
|
|
165
|
-
if (defaultOptions !== DefaultOptions.current) return
|
|
183
|
+
if (defaultOptions !== DefaultOptions.current) return withChoices(defaultOptions)
|
|
166
184
|
val generation = OptionsCache.generation
|
|
167
185
|
val along = resolvedAlong()
|
|
168
186
|
val kept = resolved
|
|
169
187
|
if (kept != null && resolvedGeneration == generation && sameNodes(along, resolvedAlong)) return kept
|
|
170
|
-
val result =
|
|
188
|
+
val result = withChoices(defaultOptions)
|
|
171
189
|
resolved = result
|
|
172
190
|
resolvedGeneration = generation
|
|
173
191
|
resolvedAlong = along.map(::WeakReference)
|
|
@@ -183,6 +201,10 @@ abstract class ViewController<V : ViewGroup>(
|
|
|
183
201
|
*/
|
|
184
202
|
open fun resolveShownOptions(defaultOptions: Options): Options = resolveCurrentOptions(defaultOptions)
|
|
185
203
|
|
|
204
|
+
// The resolution in its order: the defaults, the parents, the node's own options, then the user layer over them.
|
|
205
|
+
private fun withChoices(defaultOptions: Options): Options =
|
|
206
|
+
choices.applyTo(resolveCurrentOptions().withDefaultOptions(defaultOptions))
|
|
207
|
+
|
|
186
208
|
// The layouts around this node, nearest first, then its current children down the visible chain.
|
|
187
209
|
private fun resolvedAlong(): List<ViewController<*>> {
|
|
188
210
|
val nodes = ArrayList<ViewController<*>>()
|
|
@@ -203,9 +225,9 @@ abstract class ViewController<V : ViewGroup>(
|
|
|
203
225
|
nodes.size == kept.size && nodes.indices.all { nodes[it] === kept[it].get() }
|
|
204
226
|
|
|
205
227
|
/**
|
|
206
|
-
* Changes the node's own options: [change] gets a copy, which is stored ([options]).
|
|
207
|
-
*
|
|
208
|
-
*
|
|
228
|
+
* Changes the node's own options: [change] gets a copy, which is stored ([options]). It is for the library's own
|
|
229
|
+
* bookkeeping of a merge (a side color written into the node's buttons, the back button's visibility); what the
|
|
230
|
+
* user chose goes through [changeChoices].
|
|
209
231
|
*/
|
|
210
232
|
fun changeOptions(change: (Options) -> Unit) {
|
|
211
233
|
val copy = options.copy()
|
|
@@ -213,14 +235,40 @@ abstract class ViewController<V : ViewGroup>(
|
|
|
213
235
|
options = copy
|
|
214
236
|
}
|
|
215
237
|
|
|
238
|
+
/**
|
|
239
|
+
* Changes the node's user layer: what the user chose in the native UI (a single-selection menu's check, a search
|
|
240
|
+
* scope). It goes over the node's own options as they resolve, by the ids of the buttons, items and scopes, and
|
|
241
|
+
* stays through an unrelated `mergeOptions`, the same values sent again and `setDefaultOptions`; a merge that
|
|
242
|
+
* changes the chosen field takes it away ([mergeOwnOptions]). Every resolved result is stale after it
|
|
243
|
+
* ([OptionsCache]).
|
|
244
|
+
*/
|
|
245
|
+
internal fun changeChoices(change: (UserChoices) -> Unit) {
|
|
246
|
+
change(choices)
|
|
247
|
+
OptionsCache.optionsChanged()
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* [delta] of a `mergeOptions` goes over the node's own options ([options]); a choice of the user layer whose field
|
|
252
|
+
* the merge changes, or whose button, item or scope it took away, leaves, so the app's value shows
|
|
253
|
+
* (`UserChoices.yieldTo`). Every `mergeOptions` into a node writes its own options this way.
|
|
254
|
+
*/
|
|
255
|
+
protected fun mergeOwnOptions(delta: Options) {
|
|
256
|
+
val own = options
|
|
257
|
+
val before = if (choices.isEmpty) null else resolveCurrentOptions().withDefaultOptions(DefaultOptions.current)
|
|
258
|
+
options = own.copy().mergeWith(delta)
|
|
259
|
+
before ?: return
|
|
260
|
+
val after = resolveCurrentOptions().withDefaultOptions(DefaultOptions.current)
|
|
261
|
+
if (choices.yieldTo(delta, own, before, after)) OptionsCache.optionsChanged()
|
|
262
|
+
}
|
|
263
|
+
|
|
216
264
|
/**
|
|
217
265
|
* `mergeOptions` with the node's id (`mergeOptions`): [options] go over its own and stay there (one-shot options
|
|
218
|
-
* not kept); what shows applies them ([applyMergedOptions], when the view shows; otherwise as it
|
|
219
|
-
* layouts around it apply their part (`ParentController.mergeChildOptions`, each passes it on).
|
|
220
|
-
* carry the one-shot options to them. Containers that hand the merge to a child override it.
|
|
266
|
+
* not kept, [mergeOwnOptions]); what shows applies them ([applyMergedOptions], when the view shows; otherwise as it
|
|
267
|
+
* appears); the layouts around it apply their part (`ParentController.mergeChildOptions`, each passes it on).
|
|
268
|
+
* [options] still carry the one-shot options to them. Containers that hand the merge to a child override it.
|
|
221
269
|
*/
|
|
222
270
|
open fun mergeOptions(options: Options) {
|
|
223
|
-
|
|
271
|
+
mergeOwnOptions(options)
|
|
224
272
|
if (isViewShown) applyMergedOptions(options)
|
|
225
273
|
performOnParentController { it.mergeChildOptions(options, this) }
|
|
226
274
|
}
|
|
@@ -265,6 +313,12 @@ abstract class ViewController<V : ViewGroup>(
|
|
|
265
313
|
/** Whether [handleBack] would go back. */
|
|
266
314
|
open fun canHandleBack(): Boolean = false
|
|
267
315
|
|
|
316
|
+
/**
|
|
317
|
+
* The stack whose top [handleBack] would take away with the default pop, which a back gesture previews; none when
|
|
318
|
+
* back would do anything else (hold, close a menu or a pane, a pop of its own) or nothing.
|
|
319
|
+
*/
|
|
320
|
+
internal open fun stackPoppedByBack(): StackController? = null
|
|
321
|
+
|
|
268
322
|
/** Starts the node's content (a native screen's view, a React screen's surface). */
|
|
269
323
|
open fun start() = Unit
|
|
270
324
|
|
|
@@ -96,6 +96,9 @@ internal object Curves {
|
|
|
96
96
|
* linear between them — the values of AndroidX's `FastOutSlowInInterpolator`, computed here.
|
|
97
97
|
*/
|
|
98
98
|
object FastOutSlowIn : Curve() {
|
|
99
|
+
// Before the table, which is made from it.
|
|
100
|
+
private val curve = CubicBezier(0.4, 0.0, 0.2, 1.0)
|
|
101
|
+
|
|
99
102
|
/** The 201 values. */
|
|
100
103
|
val table: DoubleArray = DoubleArray(TABLE_STEPS + 1) {
|
|
101
104
|
round(bezierAt(it.toDouble() / TABLE_STEPS) * DECIMALS) / DECIMALS
|
|
@@ -109,7 +112,25 @@ internal object Curves {
|
|
|
109
112
|
}
|
|
110
113
|
|
|
111
114
|
/** y of the Bézier at its x = [x], exactly (to 1e-12). */
|
|
112
|
-
fun bezierAt(x: Double): Double
|
|
115
|
+
fun bezierAt(x: Double): Double = curve.yAt(x)
|
|
116
|
+
|
|
117
|
+
private const val DECIMALS = 1e4
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* A cubic Bézier from (0, 0) to (1, 1) with the control points ([x1], [y1]) and ([x2], [y2]), as the framework's
|
|
122
|
+
* `PathInterpolator` draws it, computed exactly: the system's curves without a framework class.
|
|
123
|
+
*/
|
|
124
|
+
class CubicBezier(
|
|
125
|
+
private val x1: Double,
|
|
126
|
+
private val y1: Double,
|
|
127
|
+
private val x2: Double,
|
|
128
|
+
private val y2: Double,
|
|
129
|
+
) : Curve() {
|
|
130
|
+
override fun at(p: Double): Double = yAt(p)
|
|
131
|
+
|
|
132
|
+
/** y of the curve at its x = [x], exactly (to 1e-12). */
|
|
133
|
+
fun yAt(x: Double): Double {
|
|
113
134
|
if (x <= 0.0) return 0.0
|
|
114
135
|
if (x >= 1.0) return 1.0
|
|
115
136
|
// x(t) only grows with t: a bisection finds t, then y(t).
|
|
@@ -118,23 +139,20 @@ internal object Curves {
|
|
|
118
139
|
var t = x
|
|
119
140
|
repeat(BISECTION_STEPS) {
|
|
120
141
|
t = (low + high) / 2
|
|
121
|
-
if (
|
|
142
|
+
if (coordinate(t, x1, x2) < x) low = t else high = t
|
|
122
143
|
}
|
|
123
|
-
return
|
|
144
|
+
return coordinate(t, y1, y2)
|
|
124
145
|
}
|
|
125
146
|
|
|
126
|
-
// One coordinate of the curve
|
|
127
|
-
private fun
|
|
147
|
+
// One coordinate of the curve with the control points' coordinates [c1], [c2].
|
|
148
|
+
private fun coordinate(t: Double, c1: Double, c2: Double): Double {
|
|
128
149
|
val u = 1 - t
|
|
129
150
|
return 3 * u * u * t * c1 + 3 * u * t * t * c2 + t * t * t
|
|
130
151
|
}
|
|
131
152
|
|
|
132
|
-
private
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
private const val Y2 = 1.0
|
|
136
|
-
private const val BISECTION_STEPS = 60
|
|
137
|
-
private const val DECIMALS = 1e4
|
|
153
|
+
private companion object {
|
|
154
|
+
const val BISECTION_STEPS = 60
|
|
155
|
+
}
|
|
138
156
|
}
|
|
139
157
|
|
|
140
158
|
/**
|