@apexnative/navigation 1.0.0-alpha.6 → 1.0.0-alpha.7
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/ApexNavigation.podspec +2 -1
- package/CHANGELOG.md +7 -0
- package/android/CLAUDE.md +19 -10
- package/android/CORE.md +3 -2
- package/android/consumer-rules.pro +6 -0
- package/android/src/main/java/dev/apexnative/navigation/options/BottomTabOptions.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/options/ButtonOptions.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/options/LargeTitleOptions.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/options/LayoutAnimation.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/options/OptionKey.kt +18 -0
- package/android/src/main/java/dev/apexnative/navigation/options/OptionPaths.kt +91 -0
- package/android/src/main/java/dev/apexnative/navigation/options/SheetOptions.kt +7 -4
- package/android/src/main/java/dev/apexnative/navigation/options/TitleOptions.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/options/TopBarAppearanceOptions.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/options/TopBarOptions.kt +16 -7
- package/android/src/main/java/dev/apexnative/navigation/react/ContentHeights.kt +95 -0
- package/android/src/main/java/dev/apexnative/navigation/react/ContentMounts.kt +66 -0
- package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +4 -0
- package/android/src/main/java/dev/apexnative/navigation/react/OptionsCommands.kt +41 -3
- package/android/src/main/java/dev/apexnative/navigation/react/ReactView.kt +16 -1
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotScreenView.kt +27 -7
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotView.kt +12 -1
- package/android/src/main/java/dev/apexnative/navigation/utils/MaterialTheme.kt +5 -6
- package/android/src/main/java/dev/apexnative/navigation/utils/SystemAnimations.kt +36 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalSheet.kt +329 -55
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +26 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/SheetStops.kt +60 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuMotion.kt +8 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/BackPreview.kt +32 -4
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +46 -7
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackQueue.kt +15 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +7 -4
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabMotion.kt +142 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabPresenter.kt +215 -43
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/StackSearchPresenter.kt +25 -26
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/BarRowTransition.kt +3 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/ButtonPlaces.kt +11 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +36 -38
- package/android/src/main/java/dev/apexnative/navigation/views/element/ElementTransitions.kt +124 -181
- package/android/src/main/java/dev/apexnative/navigation/views/element/TargetsWait.kt +8 -1
- package/android/src/main/java/dev/apexnative/navigation/views/element/TransitionLayer.kt +37 -112
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ElementPictures.kt +56 -3
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/FlightGeometry.kt +170 -153
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMorph.kt +199 -0
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/SharedAnimatorFactory.kt +55 -45
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/{TextChangeAnimator.kt → TextMorph.kt} +79 -129
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewMorph.kt +108 -0
- package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/FabCopy.kt +85 -0
- package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/FabMenu.kt +13 -0
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TitleBar.kt +5 -24
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TopBar.kt +51 -34
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarButtonComponentCell.kt +6 -6
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarButtonView.kt +113 -0
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarMenu.kt +219 -0
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonBar.kt +121 -175
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonMenus.kt +34 -64
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonRow.kt +216 -0
- package/android/src/main/jni/ANNContentHeights.cpp +82 -0
- package/android/src/main/res/drawable/apex_navigation_ic_arrow_right.xml +14 -0
- package/android/src/main/res/layout/apex_navigation_menu_header.xml +19 -0
- package/android/src/main/res/layout/apex_navigation_menu_item.xml +68 -0
- package/android/src/main/res/values/styles.xml +1 -1
- package/android/src/main/res/values-night/styles.xml +1 -1
- package/cli/doctor/checks/app.js +16 -1
- package/cli/project.js +4 -0
- package/cli/setup/index.js +5 -0
- package/cli/setup/steps/metro.js +46 -0
- package/docs-md/architecture/native-screens.md +4 -0
- package/docs-md/architecture/theme-and-context.md +131 -12
- package/docs-md/architecture/transitions.md +43 -4
- package/docs-md/flows/bottom-sheets.md +4 -2
- package/docs-md/fundamentals/screens-and-props.md +9 -1
- package/docs-md/guides/cli.md +3 -1
- package/docs-md/help/troubleshooting.md +1 -1
- package/docs-md/index.json +29 -1
- package/docs-md/screen-ui/back-and-gestures.md +3 -1
- package/docs-md/screen-ui/bar-buttons.md +1 -1
- package/docs-md/screen-ui/toolbar-and-fab.md +2 -0
- package/docs-md/start/installation.md +16 -1
- package/ios/Animations/ANNFrameDriver.h +55 -15
- package/ios/Animations/ANNFrameDriver.mm +214 -60
- package/ios/Animations/ANNTransitionAnimator.h +26 -5
- package/ios/Animations/ANNTransitionAnimator.mm +49 -3
- package/ios/Animations/ANNTransitionPlan.h +2 -2
- package/ios/Animations/ANNTransitionPlan.mm +10 -0
- package/ios/Animations/Elements/ANNElementFinder.h +2 -2
- package/ios/Animations/Elements/ANNElementFinder.mm +2 -2
- package/ios/Animations/Elements/ANNElementTransition.h +3 -3
- package/ios/Animations/Elements/ANNElementTransition.mm +2 -2
- package/ios/Animations/Elements/ANNSharedElementFlight.h +3 -3
- package/ios/Animations/Elements/ANNSharedElementFlight.mm +181 -43
- package/ios/Animations/Interpolators/ANNViewTracks.h +6 -5
- package/ios/Animations/Interpolators/ANNViewTracks.mm +51 -21
- package/ios/CLAUDE.md +15 -7
- package/ios/CORE.md +1 -1
- package/ios/Core/ANNAppDelegate.mm +0 -2
- package/ios/Core/ANNDeepLinkQueue.h +6 -5
- package/ios/Core/ANNDeepLinkQueue.mm +22 -10
- package/ios/Core/ANNSceneDelegate.mm +2 -2
- package/ios/Core/ApexNavigation.mm +6 -2
- package/ios/Layouts/Root/ANNRootCoordinator.mm +45 -15
- package/ios/Layouts/Screen/ANNComponentViewController.mm +4 -1
- package/ios/Layouts/Stack/ANNStackAnimatingDelegate.h +11 -2
- package/ios/Layouts/Stack/ANNStackAnimatingDelegate.mm +14 -7
- package/ios/Layouts/Stack/ANNStackController.h +8 -0
- package/ios/Layouts/Stack/ANNStackController.mm +153 -11
- package/ios/Layouts/Stack/ANNSwipePopInteraction.h +26 -0
- package/ios/Layouts/Stack/ANNSwipePopInteraction.mm +185 -0
- package/ios/Layouts/Stack/InteractivePopGestureDelegate.mm +28 -4
- package/ios/Options/ANNAnimationOptions.h +2 -1
- package/ios/Options/ANNAnimationOptions.mm +1 -1
- package/ios/Options/ANNOptions.h +7 -0
- package/ios/Options/ANNOptions.mm +45 -0
- package/ios/Options/ANNOptionsReader.mm +2 -4
- package/ios/Options/ANNStackPopAnimationOptions.h +13 -0
- package/ios/Options/ANNStackPopAnimationOptions.mm +11 -0
- package/ios/Options/Params/ColorParser.h +4 -3
- package/ios/Options/Params/ColorParser.mm +22 -4
- package/ios/React/ANNSurfaceHostingView.h +8 -0
- package/ios/React/ANNSurfaceHostingView.mm +28 -1
- package/ios/React/Slots/ANNSlotScreenView.mm +8 -2
- package/ios/TurboModules/ANNTurboModule.mm +46 -0
- package/ios/cpp/react/renderer/components/ApexNavigationSpec/ANNSlotState.cpp +20 -0
- package/ios/cpp/react/renderer/components/ApexNavigationSpec/ANNSlotState.h +5 -14
- package/lib/module/ARCHITECTURE.md +9 -3
- package/lib/module/adapters/NativeANNTurboModule.js.map +1 -1
- package/lib/module/api/app/AppRegistry.js +14 -2
- package/lib/module/api/app/AppRegistry.js.map +1 -1
- package/lib/module/api/app/createNavigation.js +188 -66
- package/lib/module/api/app/createNavigation.js.map +1 -1
- package/lib/module/api/events/navigationEvents.js +6 -2
- package/lib/module/api/events/navigationEvents.js.map +1 -1
- package/lib/module/api/linking/linking.js +11 -0
- package/lib/module/api/linking/linking.js.map +1 -1
- package/lib/module/api/screens/defineScreen.js +45 -3
- package/lib/module/api/screens/defineScreen.js.map +1 -1
- package/lib/module/api/screens/nativeScreens.js +31 -6
- package/lib/module/api/screens/nativeScreens.js.map +1 -1
- package/lib/module/api/screens/registerScreens.js +172 -35
- package/lib/module/api/screens/registerScreens.js.map +1 -1
- package/lib/module/api/theme/theme.js +5 -0
- package/lib/module/api/theme/theme.js.map +1 -1
- package/lib/module/engine/commands.js +21 -1
- package/lib/module/engine/commands.js.map +1 -1
- package/lib/module/engine/layout.js +7 -2
- package/lib/module/engine/layout.js.map +1 -1
- package/lib/module/engine/nativeScreens.js +9 -0
- package/lib/module/engine/nativeScreens.js.map +1 -1
- package/lib/module/engine/options.js +5 -4
- package/lib/module/engine/options.js.map +1 -1
- package/lib/module/engine/optionsChange.js +102 -0
- package/lib/module/engine/optionsChange.js.map +1 -0
- package/lib/module/engine/refresh.js +85 -0
- package/lib/module/engine/refresh.js.map +1 -0
- package/lib/module/engine/rememberedOptions.js +60 -0
- package/lib/module/engine/rememberedOptions.js.map +1 -1
- package/lib/module/engine/screens.js +26 -0
- package/lib/module/engine/screens.js.map +1 -1
- package/lib/typescript/adapters/NativeANNTurboModule.d.ts +7 -0
- package/lib/typescript/adapters/NativeANNTurboModule.d.ts.map +1 -1
- package/lib/typescript/api/app/AppRegistry.d.ts.map +1 -1
- package/lib/typescript/api/app/createNavigation.d.ts +5 -0
- package/lib/typescript/api/app/createNavigation.d.ts.map +1 -1
- package/lib/typescript/api/events/navigationEvents.d.ts +2 -2
- package/lib/typescript/api/events/navigationEvents.d.ts.map +1 -1
- package/lib/typescript/api/index.d.ts +1 -1
- package/lib/typescript/api/index.d.ts.map +1 -1
- package/lib/typescript/api/linking/linking.d.ts +8 -0
- package/lib/typescript/api/linking/linking.d.ts.map +1 -1
- package/lib/typescript/api/screens/defineScreen.d.ts +23 -4
- package/lib/typescript/api/screens/defineScreen.d.ts.map +1 -1
- package/lib/typescript/api/screens/nativeScreens.d.ts +8 -1
- package/lib/typescript/api/screens/nativeScreens.d.ts.map +1 -1
- package/lib/typescript/api/screens/registerScreens.d.ts.map +1 -1
- package/lib/typescript/api/theme/theme.d.ts +2 -0
- package/lib/typescript/api/theme/theme.d.ts.map +1 -1
- package/lib/typescript/engine/commands.d.ts +5 -0
- package/lib/typescript/engine/commands.d.ts.map +1 -1
- package/lib/typescript/engine/layout.d.ts.map +1 -1
- package/lib/typescript/engine/nativeScreens.d.ts +5 -0
- package/lib/typescript/engine/nativeScreens.d.ts.map +1 -1
- package/lib/typescript/engine/optionsChange.d.ts +28 -0
- package/lib/typescript/engine/optionsChange.d.ts.map +1 -0
- package/lib/typescript/engine/refresh.d.ts +35 -0
- package/lib/typescript/engine/refresh.d.ts.map +1 -0
- package/lib/typescript/engine/rememberedOptions.d.ts +13 -0
- package/lib/typescript/engine/rememberedOptions.d.ts.map +1 -1
- package/lib/typescript/engine/screens.d.ts +7 -0
- package/lib/typescript/engine/screens.d.ts.map +1 -1
- package/lib/typescript/interfaces/ComponentEvents.d.ts +2 -2
- package/lib/typescript/interfaces/options/animations.d.ts +40 -15
- package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/bottomTabs.d.ts +2 -2
- package/lib/typescript/interfaces/options/buttons.d.ts +2 -2
- package/lib/typescript/interfaces/options/modals.d.ts +6 -7
- package/lib/typescript/interfaces/options/modals.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/stack.d.ts +8 -3
- package/lib/typescript/interfaces/options/stack.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/topBar.d.ts +14 -0
- package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
- package/metro/acceptPlugin.js +57 -0
- package/metro/babelTransformer.js +45 -0
- package/metro/index.js +36 -0
- package/package.json +3 -1
- package/src/ARCHITECTURE.md +9 -3
- package/src/adapters/NativeANNTurboModule.ts +7 -0
- package/src/api/app/AppRegistry.ts +16 -2
- package/src/api/app/createNavigation.ts +198 -66
- package/src/api/events/navigationEvents.ts +9 -3
- package/src/api/index.ts +1 -0
- package/src/api/linking/linking.ts +22 -1
- package/src/api/screens/defineScreen.ts +49 -5
- package/src/api/screens/nativeScreens.ts +40 -4
- package/src/api/screens/registerScreens.tsx +170 -42
- package/src/api/theme/theme.ts +5 -0
- package/src/engine/commands.ts +21 -0
- package/src/engine/layout.ts +11 -2
- package/src/engine/nativeScreens.ts +10 -1
- package/src/engine/options.ts +6 -8
- package/src/engine/optionsChange.ts +107 -0
- package/src/engine/refresh.ts +97 -0
- package/src/engine/rememberedOptions.ts +70 -0
- package/src/engine/screens.tsx +24 -0
- package/src/interfaces/ComponentEvents.ts +2 -2
- package/src/interfaces/options/animations.ts +40 -15
- package/src/interfaces/options/bottomTabs.ts +2 -2
- package/src/interfaces/options/buttons.ts +2 -2
- package/src/interfaces/options/modals.ts +6 -7
- package/src/interfaces/options/stack.ts +8 -3
- package/src/interfaces/options/topBar.ts +14 -0
- package/android/src/main/java/dev/apexnative/navigation/views/element/ElementStub.kt +0 -31
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMatrixAnimator.kt +0 -134
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PlainViewAnimator.kt +0 -78
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PropertyAnimatorCreator.kt +0 -33
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ReactImageAnimator.kt +0 -57
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewChangeAnimator.kt +0 -146
|
@@ -3,10 +3,68 @@ package dev.apexnative.navigation.viewcontrollers.modal
|
|
|
3
3
|
import dev.apexnative.navigation.options.SheetDetent
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Where a
|
|
7
|
-
*
|
|
6
|
+
* Where a sheet rests: the stops it keeps of its detents, how the Material behavior stands at them, and where a shown
|
|
7
|
+
* sheet goes at new options, as on iOS: the sheet keeps the stop it stands at (the user's last drag included) until a
|
|
8
|
+
* `selectedDetent` arrives in a `mergeOptions`, or until its stop is no longer among the detents.
|
|
8
9
|
*/
|
|
9
10
|
internal object SheetStops {
|
|
11
|
+
/** The Material behavior has three resting states: collapsed, half expanded and expanded. */
|
|
12
|
+
const val MAX_STOPS = 3
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The stops of the sheet, from the lowest: [detents] by their height ([heightOf]), one of each height (the first
|
|
16
|
+
* the options give), and at most [MAX_STOPS] of them: the lowest, the highest, and between them [selected] when
|
|
17
|
+
* it stands there, else the lowest of the rest.
|
|
18
|
+
*/
|
|
19
|
+
fun stops(detents: List<SheetDetent>, heightOf: (SheetDetent) -> Int, selected: SheetDetent?): List<SheetDetent> {
|
|
20
|
+
val sorted = detents.sortedBy(heightOf).distinctBy(heightOf)
|
|
21
|
+
if (sorted.size <= MAX_STOPS) return sorted
|
|
22
|
+
val between = sorted.subList(1, sorted.size - 1)
|
|
23
|
+
val middle = selected?.takeIf { it in between } ?: between.first()
|
|
24
|
+
return listOf(sorted.first(), middle, sorted.last())
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* How the Material behavior stands at the stops: [fitToContents] with one or two (collapsed and expanded, the
|
|
29
|
+
* sheet as tall as the highest, [maxHeight]), else half expanded between them at [halfExpandedRatio] of the
|
|
30
|
+
* layout, and expanded [expandedOffset] below its top. [skipCollapsed] with one stop: the sheet has only that.
|
|
31
|
+
* The heights are in pixels, as is the rest.
|
|
32
|
+
*/
|
|
33
|
+
data class Material(
|
|
34
|
+
val fitToContents: Boolean,
|
|
35
|
+
val skipCollapsed: Boolean,
|
|
36
|
+
val peekHeight: Int,
|
|
37
|
+
val halfExpandedRatio: Float?,
|
|
38
|
+
val expandedOffset: Int,
|
|
39
|
+
val maxHeight: Int,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The behavior's settings for stops [heights] tall (from the lowest, each higher than the one before) in a layout
|
|
44
|
+
* [parentHeight] tall.
|
|
45
|
+
*/
|
|
46
|
+
fun material(heights: List<Int>, parentHeight: Int): Material {
|
|
47
|
+
val lowest = heights.first()
|
|
48
|
+
val highest = heights.last()
|
|
49
|
+
val three = heights.size == MAX_STOPS && parentHeight > 0
|
|
50
|
+
return Material(
|
|
51
|
+
fitToContents = !three,
|
|
52
|
+
skipCollapsed = heights.size == 1,
|
|
53
|
+
peekHeight = lowest,
|
|
54
|
+
halfExpandedRatio = if (three) heights[1].toFloat() / parentHeight else null,
|
|
55
|
+
expandedOffset = (parentHeight - highest).coerceAtLeast(0),
|
|
56
|
+
maxHeight = highest,
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The heights of stops [heights] tall (from the lowest, each higher) lifted by [lift] pixels, the part of the
|
|
62
|
+
* keyboard over the navigation bar: the sheet stands on the keyboard as it stood on the bottom. At most [full]
|
|
63
|
+
* each, and each still higher than the one below, so the behavior keeps all of its states.
|
|
64
|
+
*/
|
|
65
|
+
fun lifted(heights: List<Int>, lift: Int, full: Int): List<Int> =
|
|
66
|
+
heights.mapIndexed { i, height -> (height + lift).coerceAtMost(full - (heights.lastIndex - i)) }
|
|
67
|
+
|
|
10
68
|
/**
|
|
11
69
|
* The stop to move to, or null to stay: [arrived] (the `selectedDetent` the merge names) when it is among
|
|
12
70
|
* [detents]; else, when [settled] (the stop the sheet rests at) is no longer one of them, [selected] (the resolved
|
package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuMotion.kt
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package dev.apexnative.navigation.viewcontrollers.sidemenu
|
|
2
2
|
|
|
3
3
|
import dev.apexnative.navigation.react.events.AppearanceDispatcher
|
|
4
|
+
import dev.apexnative.navigation.utils.SystemAnimations
|
|
4
5
|
import dev.apexnative.navigation.viewcontrollers.viewcontroller.ViewController
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -26,6 +27,8 @@ import dev.apexnative.navigation.viewcontrollers.viewcontroller.ViewController
|
|
|
26
27
|
internal class SideMenuMotion(
|
|
27
28
|
private val drawer: () -> Drawer,
|
|
28
29
|
private val host: Host,
|
|
30
|
+
/** The system's animations are off ([SystemAnimations]): a command or back moves the drawer at once. */
|
|
31
|
+
private val animationsOff: () -> Boolean = { SystemAnimations.areOff() },
|
|
29
32
|
) {
|
|
30
33
|
/** A side of the side menu: physical, `left` stays on the left in RTL too. */
|
|
31
34
|
enum class Side(
|
|
@@ -242,11 +245,14 @@ internal class SideMenuMotion(
|
|
|
242
245
|
drawer.lock(side, locked = !isOnScreen(side) && !host.isEnabled(side))
|
|
243
246
|
}
|
|
244
247
|
|
|
245
|
-
// The drawer opens or closes [side]; one that doesn't call back has its result recorded here.
|
|
248
|
+
// The drawer opens or closes [side]; one that doesn't call back has its result recorded here. With the system's
|
|
249
|
+
// animations off it jumps, unless it moves: the drag and the settle aren't animators, and it can't jump while it
|
|
250
|
+
// settles.
|
|
246
251
|
private fun move(side: Side, open: Boolean, animate: Boolean) {
|
|
247
252
|
val drawer = drawer()
|
|
253
|
+
val animated = animate && (moving || !animationsOff())
|
|
248
254
|
val silent = !drawer.callsBack
|
|
249
|
-
if (open) drawer.open(side,
|
|
255
|
+
if (open) drawer.open(side, animated) else drawer.close(side, animated)
|
|
250
256
|
if (silent) record(side, open)
|
|
251
257
|
}
|
|
252
258
|
|
package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/BackPreview.kt
CHANGED
|
@@ -2,6 +2,7 @@ package dev.apexnative.navigation.viewcontrollers.stack
|
|
|
2
2
|
|
|
3
3
|
import android.animation.Animator
|
|
4
4
|
import android.animation.AnimatorListenerAdapter
|
|
5
|
+
import android.animation.StateListAnimator
|
|
5
6
|
import android.animation.ValueAnimator
|
|
6
7
|
import android.content.res.Configuration
|
|
7
8
|
import android.graphics.Bitmap
|
|
@@ -29,7 +30,7 @@ import dev.apexnative.navigation.views.stack.StackLayout
|
|
|
29
30
|
* The preview of a pop while the user's back gesture runs (predictive back), as the system shows the activity under
|
|
30
31
|
* the one that closes: the screen on top with its bar, a picture taken as the gesture starts, shrinks toward the
|
|
31
32
|
* 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
|
+
* its own bar and FAB, smaller and dimmed. Released, the pop plays its end ([commit]); cancelled, everything goes back
|
|
33
34
|
* ([cancel]). The values are those of the system's cross-activity back animation (WMShell
|
|
34
35
|
* `DefaultCrossActivityBackAnimation` of Android 15 and 16, the smallest scale as Android 17 shows it), also on Android
|
|
35
36
|
* 14, whose own preview differs. Main thread only.
|
|
@@ -46,8 +47,13 @@ internal class BackPreview(
|
|
|
46
47
|
* corners, while the tab bar stays, as Gmail shows it.
|
|
47
48
|
*/
|
|
48
49
|
private val bottomRoom: Int,
|
|
49
|
-
/**
|
|
50
|
+
/**
|
|
51
|
+
* The picture is there: the stack shows the screen under the top, with its bar and its FAB, before the preview
|
|
52
|
+
* draws.
|
|
53
|
+
*/
|
|
50
54
|
private val onReveal: () -> Unit,
|
|
55
|
+
/** The view of the FAB the stack shows now, which goes with the screen under the top; none. */
|
|
56
|
+
private val fab: () -> View? = { null },
|
|
51
57
|
) {
|
|
52
58
|
private val density = layout.resources.displayMetrics.density
|
|
53
59
|
private val bar = layout.topBar
|
|
@@ -58,6 +64,11 @@ internal class BackPreview(
|
|
|
58
64
|
private var animator: Animator? = null
|
|
59
65
|
private var ended = false
|
|
60
66
|
private var topAlpha = 1f
|
|
67
|
+
// The FAB that shows with the screen under the top, drawn flat under the dim and the picture, its own Z and the
|
|
68
|
+
// animator of its Z by its state.
|
|
69
|
+
private var flatFab: View? = null
|
|
70
|
+
private var fabZ = 0f
|
|
71
|
+
private var fabZByState: StateListAnimator? = null
|
|
61
72
|
// What puts the stack as it was after a cancel: once, as the preview ends.
|
|
62
73
|
private var onCancelled: (() -> Unit)? = null
|
|
63
74
|
// Forward is to the end of the line: right to left, the screens move the other way, as the stack's pop does.
|
|
@@ -175,10 +186,17 @@ internal class BackPreview(
|
|
|
175
186
|
card = null
|
|
176
187
|
(scrim.parent as? ViewGroup)?.removeView(scrim)
|
|
177
188
|
top.alpha = topAlpha
|
|
189
|
+
flatFab?.let {
|
|
190
|
+
flatFab = null
|
|
191
|
+
it.translationZ = fabZ
|
|
192
|
+
it.stateListAnimator = fabZByState
|
|
193
|
+
fabZByState = null
|
|
194
|
+
}
|
|
178
195
|
entering(1f, 0f)
|
|
179
196
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
180
197
|
target.resetPivot()
|
|
181
198
|
bar.resetPivot()
|
|
199
|
+
fab()?.resetPivot()
|
|
182
200
|
}
|
|
183
201
|
onCancelled?.let {
|
|
184
202
|
onCancelled = null
|
|
@@ -190,6 +208,16 @@ internal class BackPreview(
|
|
|
190
208
|
// the top's place, cut to rounded corners.
|
|
191
209
|
private fun show(picture: Bitmap) {
|
|
192
210
|
onReveal()
|
|
211
|
+
// A child with a Z draws over those without, whatever their order: the FAB, raised by its elevation, would lie
|
|
212
|
+
// over the dim and the picture. Its state's animator, which Material's FAB sets on the frame after it joins the
|
|
213
|
+
// window (and on a press, focus or hover), would raise it again: it stays off while the preview draws.
|
|
214
|
+
fab()?.takeIf { it.z > 0f }?.let {
|
|
215
|
+
flatFab = it
|
|
216
|
+
fabZ = it.translationZ
|
|
217
|
+
fabZByState = it.stateListAnimator
|
|
218
|
+
it.stateListAnimator = null
|
|
219
|
+
it.translationZ = -it.elevation
|
|
220
|
+
}
|
|
193
221
|
val dark = layout.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
|
|
194
222
|
Configuration.UI_MODE_NIGHT_YES
|
|
195
223
|
scrim.setBackgroundColor(Color.BLACK)
|
|
@@ -235,9 +263,9 @@ internal class BackPreview(
|
|
|
235
263
|
entering(scale, -SystemTransition.DISTANCE_DP * density * forward)
|
|
236
264
|
}
|
|
237
265
|
|
|
238
|
-
// The screen under the top and its
|
|
266
|
+
// The screen under the top, its bar and its FAB at [scale] around the stack's center, [x] along the line.
|
|
239
267
|
private fun entering(scale: Float, x: Float) {
|
|
240
|
-
for (view in
|
|
268
|
+
for (view in listOfNotNull(target, bar, fab())) {
|
|
241
269
|
view.pivotX = layout.width / 2f - view.left
|
|
242
270
|
view.pivotY = areaHeight / 2f - view.top
|
|
243
271
|
view.scaleX = scale
|
package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt
CHANGED
|
@@ -31,6 +31,7 @@ import dev.apexnative.navigation.viewcontrollers.parent.OptionsScreen
|
|
|
31
31
|
import dev.apexnative.navigation.viewcontrollers.parent.ParentController
|
|
32
32
|
import dev.apexnative.navigation.viewcontrollers.parent.ReturnsScreenFromColumn
|
|
33
33
|
import dev.apexnative.navigation.viewcontrollers.parent.nodeWith
|
|
34
|
+
import dev.apexnative.navigation.viewcontrollers.stack.fab.FabMotion
|
|
34
35
|
import dev.apexnative.navigation.viewcontrollers.stack.fab.FabPresenter
|
|
35
36
|
import dev.apexnative.navigation.viewcontrollers.stack.topbar.LargeTitleCollapse
|
|
36
37
|
import dev.apexnative.navigation.viewcontrollers.stack.topbar.TopBarPresenter
|
|
@@ -207,6 +208,17 @@ class StackController(
|
|
|
207
208
|
screen.changeOptions { it.topBar.buttons.back.setVisible() }
|
|
208
209
|
}
|
|
209
210
|
|
|
211
|
+
/**
|
|
212
|
+
* `clearOptions` took options out of the own options of [node], an entry of this stack or the options screen of
|
|
213
|
+
* one: the back arrow's rule ([join]) writes its part of them again, so the first screen keeps its arrow hidden
|
|
214
|
+
* and another shows it unless its options say otherwise.
|
|
215
|
+
*/
|
|
216
|
+
internal fun ownOptionsCleared(node: ViewController<*>) {
|
|
217
|
+
val entries = queue.entries
|
|
218
|
+
val entry = entries.firstOrNull { it === node || optionsScreen(it) === node } ?: return
|
|
219
|
+
backRule(entry, first = entry === entries.first())
|
|
220
|
+
}
|
|
221
|
+
|
|
210
222
|
/** The key of [entry]; none without one. */
|
|
211
223
|
internal fun keyOf(entry: ViewController<*>): String? = keys[entry]
|
|
212
224
|
|
|
@@ -307,6 +319,9 @@ class StackController(
|
|
|
307
319
|
// The back gesture over this stack whose preview hasn't ended yet; none.
|
|
308
320
|
private var backGesture: BackGesture? = null
|
|
309
321
|
|
|
322
|
+
// A merge of `fab` into the top waits for the end of a back gesture's preview.
|
|
323
|
+
private var fabMergeWaits = false
|
|
324
|
+
|
|
310
325
|
/**
|
|
311
326
|
* A back gesture over the top of this stack ([startBackGesture]): its progress, its cancel, and its release, which
|
|
312
327
|
* pops the top through the queue with the preview's end. A command accepted meanwhile, or the stack's destroy,
|
|
@@ -326,6 +341,10 @@ class StackController(
|
|
|
326
341
|
private var revealed = false
|
|
327
342
|
private var aborted = false
|
|
328
343
|
|
|
344
|
+
/** The preview shows the screen under the top, with its bar and its FAB. */
|
|
345
|
+
val isRevealed: Boolean
|
|
346
|
+
get() = revealed && !aborted
|
|
347
|
+
|
|
329
348
|
/** The gesture moved: [progress] 0 … 1, the finger at [touchY]. */
|
|
330
349
|
fun progress(progress: Float, touchY: Float) {
|
|
331
350
|
if (!aborted) preview.progress(progress, touchY)
|
|
@@ -348,6 +367,9 @@ class StackController(
|
|
|
348
367
|
return true
|
|
349
368
|
}
|
|
350
369
|
if (backGesture === this) backGesture = null
|
|
370
|
+
// The top's FAB, set aside, leaves with it; a merge into it no longer matters.
|
|
371
|
+
fab.dropAside()
|
|
372
|
+
fabMergeWaits = false
|
|
351
373
|
queue.cut(target, null, id, "go back in the stack '$id'", null, gesture = true)
|
|
352
374
|
return true
|
|
353
375
|
}
|
|
@@ -360,7 +382,8 @@ class StackController(
|
|
|
360
382
|
restore()
|
|
361
383
|
}
|
|
362
384
|
|
|
363
|
-
// The preview's picture is there: the screen under the top shows under it, and the bar
|
|
385
|
+
// The preview's picture is there: the screen under the top shows under it, and the bar and the FAB show that
|
|
386
|
+
// screen, at once.
|
|
364
387
|
internal fun reveal() {
|
|
365
388
|
val view = target.peekView() ?: return
|
|
366
389
|
targetWasPlaced = view.parent === peekView() && view.visibility == View.VISIBLE
|
|
@@ -370,6 +393,8 @@ class StackController(
|
|
|
370
393
|
views.reveal(target, top)
|
|
371
394
|
view.alpha = 1f
|
|
372
395
|
topBar.apply(target)
|
|
396
|
+
fab.setAside()
|
|
397
|
+
showFab(FabMotion.Move.AtOnce, target)
|
|
373
398
|
}
|
|
374
399
|
|
|
375
400
|
private fun restore() {
|
|
@@ -386,9 +411,14 @@ class StackController(
|
|
|
386
411
|
views.detach(target)
|
|
387
412
|
}
|
|
388
413
|
}
|
|
389
|
-
// The bar
|
|
414
|
+
// The bar and the FAB show the top again, the bar's components appeared again.
|
|
390
415
|
val shown = queue.currentChild
|
|
391
416
|
topBar.apply(shown)
|
|
417
|
+
fab.bringBack()
|
|
418
|
+
if (fabMergeWaits) {
|
|
419
|
+
fabMergeWaits = false
|
|
420
|
+
mergeFab()
|
|
421
|
+
}
|
|
392
422
|
shown?.let {
|
|
393
423
|
topBar.screenWillAppear(optionsScreen(it))
|
|
394
424
|
topBar.screenDidAppear(optionsScreen(it))
|
|
@@ -462,7 +492,8 @@ class StackController(
|
|
|
462
492
|
ContainerBackground.apply(this, options)
|
|
463
493
|
updateBar()
|
|
464
494
|
takeKeptMerge(child)?.let(::applyTopMerge)
|
|
465
|
-
|
|
495
|
+
// A back gesture's preview shows the FAB of the screen under the top.
|
|
496
|
+
if (queue.isAtRest && backGesture?.isRevealed != true) showFab()
|
|
466
497
|
performOnParentController { it.applyChildOptions(withoutStackGroups(options), this) }
|
|
467
498
|
}
|
|
468
499
|
|
|
@@ -670,15 +701,20 @@ class StackController(
|
|
|
670
701
|
topBar.apply(queue.currentChild)
|
|
671
702
|
}
|
|
672
703
|
|
|
673
|
-
// The FAB follows the
|
|
674
|
-
|
|
704
|
+
// The FAB follows [top] (the current child) from the start of a transition by [move], with the screens; at rest, a
|
|
705
|
+
// FAB that comes by Material's swap.
|
|
706
|
+
private fun showFab(move: FabMotion.Move = FabMotion.Move.Swap, top: ViewController<*>? = queue.currentChild) {
|
|
675
707
|
val layout = peekView() ?: return
|
|
676
|
-
val top = queue.currentChild
|
|
677
708
|
val screen = top?.let(::optionsScreen)
|
|
678
|
-
fab.showFor(layout, screen, top?.let(::resolvedOf)?.fab ?: FabOptions(), scrollOf(screen))
|
|
709
|
+
fab.showFor(layout, screen, top?.let(::resolvedOf)?.fab ?: FabOptions(), scrollOf(screen), move)
|
|
679
710
|
}
|
|
680
711
|
|
|
681
712
|
private fun mergeFab() {
|
|
713
|
+
// A back gesture's preview shows the FAB of the screen under the top: the top's merge waits for a cancel.
|
|
714
|
+
if (backGesture?.isRevealed == true) {
|
|
715
|
+
fabMergeWaits = true
|
|
716
|
+
return
|
|
717
|
+
}
|
|
682
718
|
val layout = peekView() ?: return
|
|
683
719
|
val top = queue.currentChild ?: return
|
|
684
720
|
val screen = optionsScreen(top)
|
|
@@ -727,6 +763,9 @@ class StackController(
|
|
|
727
763
|
parentController?.tabBarTransition(this@StackController, move, side)
|
|
728
764
|
}
|
|
729
765
|
|
|
766
|
+
// The FAB of the new top arrives with its screen, the old one leaves with its own.
|
|
767
|
+
override fun fabTransition(move: FabMotion.Move) = showFab(move)
|
|
768
|
+
|
|
730
769
|
override fun operationEnded(topSettled: Boolean) {
|
|
731
770
|
BackCallbackState.update()
|
|
732
771
|
if (topSettled) parentController?.tabBarSettled(this@StackController)
|
|
@@ -21,6 +21,7 @@ import dev.apexnative.navigation.viewcontrollers.stack.StackDisplay.Custom
|
|
|
21
21
|
import dev.apexnative.navigation.viewcontrollers.stack.StackDisplay.Elements
|
|
22
22
|
import dev.apexnative.navigation.viewcontrollers.stack.StackDisplay.Fade
|
|
23
23
|
import dev.apexnative.navigation.viewcontrollers.stack.StackDisplay.Motion
|
|
24
|
+
import dev.apexnative.navigation.viewcontrollers.stack.fab.FabMotion
|
|
24
25
|
import dev.apexnative.navigation.viewcontrollers.viewcontroller.ViewController
|
|
25
26
|
import dev.apexnative.navigation.views.animations.AnimationRules
|
|
26
27
|
import java.util.WeakHashMap
|
|
@@ -58,7 +59,7 @@ private typealias Node = ViewController<*>
|
|
|
58
59
|
* made in the commit of that mount (its `mergeOptions`), so the transition starts with the options of that commit —
|
|
59
60
|
* content mounted already goes that way too. A push or a `setStackRoot` with elements waits for its targets after
|
|
60
61
|
* that, a wait like the others, within what is left of its limit without `waitForRender`: a cut or a replace during it
|
|
61
|
-
* interrupts the push, which
|
|
62
|
+
* interrupts the push, which starts no element.
|
|
62
63
|
*
|
|
63
64
|
* The transition is the screens' own when the options say so (`AnimationRules.stackIsOwn`): the display plays each
|
|
64
65
|
* screen's side ([StackDisplay.animateCustom]) with the same owners as the default transitions; the bar's row then
|
|
@@ -103,6 +104,12 @@ internal class StackQueue(
|
|
|
103
104
|
*/
|
|
104
105
|
fun tabBarTransition(move: TabBarMotion.Move, side: BottomTabsEnterExitAnimationOptions?) = Unit
|
|
105
106
|
|
|
107
|
+
/**
|
|
108
|
+
* The current child is about to change, right after [barTransition]: [move] is how the FAB goes, the new top's
|
|
109
|
+
* arriving with its screen and the old one's leaving with its own from the start of the transition.
|
|
110
|
+
*/
|
|
111
|
+
fun fabTransition(move: FabMotion.Move) = Unit
|
|
112
|
+
|
|
106
113
|
/** [node] was accepted to show as the top (a push, the new top of a replace). */
|
|
107
114
|
fun willShow(node: Node) = Unit
|
|
108
115
|
|
|
@@ -546,6 +553,7 @@ internal class StackQueue(
|
|
|
546
553
|
val ended = takeOver(listOfNotNull(covered, node), op)
|
|
547
554
|
host.tabBarTransition(TabBarMotion.Move.AtOnce, null)
|
|
548
555
|
host.barTransition(null, BarVisibility.AT_ONCE)
|
|
556
|
+
host.fabTransition(FabMotion.Move.AtOnce)
|
|
549
557
|
host.currentChildChanged()
|
|
550
558
|
finishTakenOver(ended)
|
|
551
559
|
if (display.hasView && covered != null) display.detach(covered)
|
|
@@ -785,6 +793,7 @@ internal class StackQueue(
|
|
|
785
793
|
val ended = takeOver(old + top, op)
|
|
786
794
|
host.tabBarTransition(TabBarMotion.Move.AtOnce, null)
|
|
787
795
|
host.barTransition(null, BarVisibility.AT_ONCE)
|
|
796
|
+
host.fabTransition(FabMotion.Move.AtOnce)
|
|
788
797
|
host.currentChildChanged()
|
|
789
798
|
finishTakenOver(ended)
|
|
790
799
|
op.done = true
|
|
@@ -1124,6 +1133,7 @@ internal class StackQueue(
|
|
|
1124
1133
|
reject(op, text)
|
|
1125
1134
|
host.tabBarTransition(TabBarMotion.Move.AtOnce, null)
|
|
1126
1135
|
host.barTransition(null, BarVisibility.AT_ONCE)
|
|
1136
|
+
host.fabTransition(FabMotion.Move.AtOnce)
|
|
1127
1137
|
host.currentChildChanged()
|
|
1128
1138
|
settled()
|
|
1129
1139
|
drain()
|
|
@@ -1174,7 +1184,7 @@ internal class StackQueue(
|
|
|
1174
1184
|
// The bars with a change of the current child. The row goes with the default transition; with an animation of its
|
|
1175
1185
|
// own the top bar goes with the screens ([travel]). The tab bar rides with the system's [system] move, slides with an
|
|
1176
1186
|
// animation of its own or a side of its own, and is where the new top wants it at once without animation; with
|
|
1177
|
-
// elements every bar changes at once.
|
|
1187
|
+
// elements every bar changes at once. The FAB goes with its screen, by Material's swap with an animation of its own.
|
|
1178
1188
|
private fun tellBars(
|
|
1179
1189
|
animated: Boolean,
|
|
1180
1190
|
own: Boolean,
|
|
@@ -1183,7 +1193,8 @@ internal class StackQueue(
|
|
|
1183
1193
|
system: TabBarMotion.Move,
|
|
1184
1194
|
row: BarMotion,
|
|
1185
1195
|
travel: BarTravel,
|
|
1186
|
-
// The top bar
|
|
1196
|
+
// The top bar and the FAB show the new top already (a back gesture's preview): they change at once, the tab bar
|
|
1197
|
+
// still moves.
|
|
1187
1198
|
barAtOnce: Boolean = false,
|
|
1188
1199
|
) {
|
|
1189
1200
|
val tabs = animation.bottomTabs
|
|
@@ -1200,6 +1211,7 @@ internal class StackQueue(
|
|
|
1200
1211
|
barVisibility(barMoves && !elements, animation.topBar),
|
|
1201
1212
|
travel.takeIf { barMoves && own && !elements },
|
|
1202
1213
|
)
|
|
1214
|
+
host.fabTransition(FabMotion.moveOf(animated, own, row, shown = barAtOnce))
|
|
1203
1215
|
}
|
|
1204
1216
|
|
|
1205
1217
|
private fun barVisibility(animated: Boolean, side: TopBarEnterExitAnimationOptions): BarVisibility =
|
|
@@ -150,8 +150,9 @@ internal class StackViews(private val stack: StackController) : StackDisplay {
|
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
// The elements
|
|
154
|
-
// without a duration takes the screen's fade: its own single `alpha`,
|
|
153
|
+
// The elements play in their screens and the pairs' copies fly in a layer over them, all done as the screen's
|
|
154
|
+
// animator ends or stops. A shared element without a duration takes the screen's fade: its own single `alpha`,
|
|
155
|
+
// else 300 ms.
|
|
155
156
|
override fun animateCustom(screen: ViewController<*>, custom: StackDisplay.Custom, done: () -> Unit) {
|
|
156
157
|
stop(screen)
|
|
157
158
|
val view = screen.peekView() ?: return done()
|
|
@@ -199,7 +200,7 @@ internal class StackViews(private val stack: StackController) : StackDisplay {
|
|
|
199
200
|
|
|
200
201
|
/**
|
|
201
202
|
* Starts the preview of a back gesture over [top], [target] under it (`BackPreview`): none without the stack's view
|
|
202
|
-
* or a window. [onReveal] shows [target]
|
|
203
|
+
* or a window. [onReveal] shows [target] with its bar and its FAB as the preview's picture is there.
|
|
203
204
|
*/
|
|
204
205
|
fun startBackPreview(
|
|
205
206
|
top: ViewController<*>,
|
|
@@ -214,7 +215,9 @@ internal class StackViews(private val stack: StackController) : StackDisplay {
|
|
|
214
215
|
val window = stack.activity.window ?: return null
|
|
215
216
|
val topView = top.peekView() ?: return null
|
|
216
217
|
val targetView = target.peekView() ?: return null
|
|
217
|
-
val started = BackPreview(layout, topView, targetView, fromLeft, touchY, bottomRoom, onReveal)
|
|
218
|
+
val started = BackPreview(layout, topView, targetView, fromLeft, touchY, bottomRoom, onReveal) {
|
|
219
|
+
stack.fab.shownView
|
|
220
|
+
}
|
|
218
221
|
preview = started
|
|
219
222
|
previewScreens = top to target
|
|
220
223
|
started.start(window)
|
package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabMotion.kt
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
package dev.apexnative.navigation.viewcontrollers.stack.fab
|
|
2
|
+
|
|
3
|
+
import dev.apexnative.navigation.viewcontrollers.stack.StackDisplay.BarMotion
|
|
4
|
+
import dev.apexnative.navigation.views.animations.FadeThrough
|
|
5
|
+
import dev.apexnative.navigation.views.animations.SystemTransition
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* How a stack's FAB goes with a change of its screen on top, as the values alone: the new top's FAB arrives with its
|
|
9
|
+
* screen and a copy of the old one leaves with the screen it belonged to, from the start of the transition. A FAB with
|
|
10
|
+
* the same id stays where it is. The stack plays them on its views (`FabPresenter`).
|
|
11
|
+
*/
|
|
12
|
+
internal object FabMotion {
|
|
13
|
+
/** How the stack's transition goes, as its queue tells it (`StackQueue.Host.fabTransition`). */
|
|
14
|
+
enum class Move {
|
|
15
|
+
/** The system's push: each FAB rides with its screen along the line, as the tab bar does. */
|
|
16
|
+
WithPush,
|
|
17
|
+
|
|
18
|
+
/** The system's pop: the same, the other way. */
|
|
19
|
+
WithPop,
|
|
20
|
+
|
|
21
|
+
/** The fade through of a `setStackRoot`: the old one fades out first, the new one fades in and grows. */
|
|
22
|
+
FadeThrough,
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A transition of the screens' own (`content`, elements): Material's swap at its start, the old one shrinking
|
|
26
|
+
* away, the new one growing from 60 %.
|
|
27
|
+
*/
|
|
28
|
+
Swap,
|
|
29
|
+
|
|
30
|
+
/** No animation: the new top's FAB is there at once. */
|
|
31
|
+
AtOnce,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The curve a ride's move and scale take; its alpha changes on a straight line. */
|
|
35
|
+
enum class Curve { System, FadeThrough, Material }
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* A FAB's ride: along the x axis in dp, mirrored right to left; its scale on [curve] over [duration]; its alpha after
|
|
39
|
+
* a hold of [alphaDelay] over [alphaDuration], in ms.
|
|
40
|
+
*/
|
|
41
|
+
data class Ride(
|
|
42
|
+
val fromX: Float,
|
|
43
|
+
val toX: Float,
|
|
44
|
+
val fromAlpha: Float,
|
|
45
|
+
val toAlpha: Float,
|
|
46
|
+
val fromScale: Float,
|
|
47
|
+
val toScale: Float,
|
|
48
|
+
val alphaDelay: Long,
|
|
49
|
+
val alphaDuration: Long,
|
|
50
|
+
val duration: Long,
|
|
51
|
+
val curve: Curve,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
/** How long Material's swap takes, as the FAB shrinks or grows. */
|
|
55
|
+
const val SWAP_MILLIS = 200L
|
|
56
|
+
|
|
57
|
+
/** Where Material's swap grows the new FAB from. */
|
|
58
|
+
const val GROW_FROM = 0.6f
|
|
59
|
+
|
|
60
|
+
private const val FADE_THROUGH_SCALE = 0.92f
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The move of the FAB with a change of the current child: none when the change comes at once or the new top shows
|
|
64
|
+
* already ([shown]: a back gesture's preview showed it); Material's swap with an animation of the screens' own
|
|
65
|
+
* ([own]); else the default transition's [row], as the bar's row goes.
|
|
66
|
+
*/
|
|
67
|
+
fun moveOf(animated: Boolean, own: Boolean, row: BarMotion, shown: Boolean): Move = when {
|
|
68
|
+
!animated || shown -> Move.AtOnce
|
|
69
|
+
own -> Move.Swap
|
|
70
|
+
row == BarMotion.Push -> Move.WithPush
|
|
71
|
+
row == BarMotion.Pop -> Move.WithPop
|
|
72
|
+
else -> Move.FadeThrough
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The new top's FAB comes with its screen: a push brings it from +96 dp (−96 dp on a pop), transparent for the hold
|
|
77
|
+
* of the screen's fade, then fading in; the fade through fades it in after the old one went and grows it from 92 %;
|
|
78
|
+
* the swap grows it from 60 %. None at once.
|
|
79
|
+
*/
|
|
80
|
+
fun arrival(move: Move, rtl: Boolean): Ride? {
|
|
81
|
+
val distance = SystemTransition.DISTANCE_DP * if (rtl) -1 else 1
|
|
82
|
+
return when (move) {
|
|
83
|
+
Move.WithPush, Move.WithPop -> Ride(
|
|
84
|
+
fromX = if (move == Move.WithPush) distance else -distance,
|
|
85
|
+
toX = 0f,
|
|
86
|
+
fromAlpha = 0f,
|
|
87
|
+
toAlpha = 1f,
|
|
88
|
+
fromScale = 1f,
|
|
89
|
+
toScale = 1f,
|
|
90
|
+
alphaDelay = fadeDelay(move),
|
|
91
|
+
alphaDuration = SystemTransition.FADE_DURATION,
|
|
92
|
+
duration = SystemTransition.DURATION,
|
|
93
|
+
curve = Curve.System,
|
|
94
|
+
)
|
|
95
|
+
Move.FadeThrough -> Ride(
|
|
96
|
+
fromX = 0f,
|
|
97
|
+
toX = 0f,
|
|
98
|
+
fromAlpha = 0f,
|
|
99
|
+
toAlpha = 1f,
|
|
100
|
+
fromScale = FADE_THROUGH_SCALE,
|
|
101
|
+
toScale = 1f,
|
|
102
|
+
alphaDelay = fadeThroughOut(),
|
|
103
|
+
alphaDuration = FadeThrough.DURATION - fadeThroughOut(),
|
|
104
|
+
duration = FadeThrough.DURATION,
|
|
105
|
+
curve = Curve.FadeThrough,
|
|
106
|
+
)
|
|
107
|
+
Move.Swap -> Ride(0f, 0f, 1f, 1f, GROW_FROM, 1f, 0L, 0L, SWAP_MILLIS, Curve.Material)
|
|
108
|
+
Move.AtOnce -> null
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The old top's FAB leaves with its screen, from where it is: a push takes it to −96 dp with the covered screen and
|
|
114
|
+
* fades it out as the new screen fades in over it; a pop takes it to +96 dp, fading with the leaving screen; the
|
|
115
|
+
* fade through fades it out in the first 35 %; the swap shrinks it to nothing. None at once.
|
|
116
|
+
*/
|
|
117
|
+
fun departure(move: Move, rtl: Boolean): Ride? {
|
|
118
|
+
val distance = SystemTransition.DISTANCE_DP * if (rtl) -1 else 1
|
|
119
|
+
return when (move) {
|
|
120
|
+
Move.WithPush, Move.WithPop -> Ride(
|
|
121
|
+
fromX = 0f,
|
|
122
|
+
toX = if (move == Move.WithPush) -distance else distance,
|
|
123
|
+
fromAlpha = 1f,
|
|
124
|
+
toAlpha = 0f,
|
|
125
|
+
fromScale = 1f,
|
|
126
|
+
toScale = 1f,
|
|
127
|
+
alphaDelay = fadeDelay(move),
|
|
128
|
+
alphaDuration = SystemTransition.FADE_DURATION,
|
|
129
|
+
duration = SystemTransition.DURATION,
|
|
130
|
+
curve = Curve.System,
|
|
131
|
+
)
|
|
132
|
+
Move.FadeThrough -> Ride(0f, 0f, 1f, 0f, 1f, 1f, 0L, fadeThroughOut(), fadeThroughOut(), Curve.FadeThrough)
|
|
133
|
+
Move.Swap -> Ride(0f, 0f, 1f, 1f, 1f, 0f, 0L, 0L, SWAP_MILLIS, Curve.Material)
|
|
134
|
+
Move.AtOnce -> null
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private fun fadeDelay(move: Move): Long =
|
|
139
|
+
if (move == Move.WithPush) SystemTransition.PUSH_FADE_DELAY else SystemTransition.POP_FADE_DELAY
|
|
140
|
+
|
|
141
|
+
private fun fadeThroughOut(): Long = (FadeThrough.DURATION * FadeThrough.OUT).toLong()
|
|
142
|
+
}
|