@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
|
@@ -4,6 +4,7 @@ import android.annotation.SuppressLint
|
|
|
4
4
|
import android.content.Context
|
|
5
5
|
import android.graphics.Color
|
|
6
6
|
import android.graphics.Outline
|
|
7
|
+
import android.os.SystemClock
|
|
7
8
|
import android.view.GestureDetector
|
|
8
9
|
import android.view.Gravity
|
|
9
10
|
import android.view.MotionEvent
|
|
@@ -18,6 +19,7 @@ import android.widget.ScrollView
|
|
|
18
19
|
import androidx.activity.BackEventCompat
|
|
19
20
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
20
21
|
import androidx.core.graphics.Insets
|
|
22
|
+
import androidx.core.view.OneShotPreDrawListener
|
|
21
23
|
import androidx.core.view.ViewCompat
|
|
22
24
|
import androidx.core.view.WindowInsetsCompat
|
|
23
25
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
@@ -25,23 +27,33 @@ import com.google.android.material.bottomsheet.BottomSheetDragHandleView
|
|
|
25
27
|
import dev.apexnative.navigation.options.SheetDetent
|
|
26
28
|
import dev.apexnative.navigation.options.SheetOptions
|
|
27
29
|
import dev.apexnative.navigation.utils.MaterialTheme
|
|
30
|
+
import dev.apexnative.navigation.utils.SystemAnimations
|
|
28
31
|
import dev.apexnative.navigation.utils.TestIds
|
|
29
32
|
import dev.apexnative.navigation.utils.dp
|
|
33
|
+
import dev.apexnative.navigation.viewcontrollers.viewcontroller.IReactView
|
|
30
34
|
import kotlin.math.roundToInt
|
|
31
35
|
|
|
32
36
|
/**
|
|
33
37
|
* A modal shown as a Material bottom sheet (`pageSheet`, `formSheet`, `popover`, or `modal.sheet` without a style): the
|
|
34
38
|
* modal's view in a container with a [BottomSheetBehavior], in the modals' CoordinatorLayout, over a scrim. It stops at
|
|
35
39
|
* the detents of its options — `medium` at half the height, `large` at the full height under the status bar, a number
|
|
36
|
-
* at that height in dp
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
+
* at that height in dp, `content` at the height of the shown screen's content — and the user drags it between them; at
|
|
41
|
+
* three of them at most, the Material behavior's resting states ([SheetStops]). The content is measured without a
|
|
42
|
+
* height limit, as on iOS ([IReactView.contentSize]), again after each of its mounts, and the stops follow it. In a
|
|
43
|
+
* window wider than [MAX_WIDTH_DP] it stands in the middle at that width. While the keyboard shows, the sheet stands
|
|
44
|
+
* on it at every stop, as on iOS, unless its screen keeps its size for it. A drag down past the smallest one closes
|
|
45
|
+
* it, unless `swipeToDismiss` is false: then the sheet stays, and the try is told. The screen under it stays in place.
|
|
46
|
+
* On Android 14+ the back gesture previews its hide ([SheetBack]): the Material behavior shrinks it with the progress
|
|
47
|
+
* and slides it down at the release.
|
|
40
48
|
*/
|
|
41
49
|
internal class ModalSheet(
|
|
42
50
|
private val layout: CoordinatorLayout,
|
|
43
51
|
private val content: View,
|
|
44
52
|
private val events: Events,
|
|
53
|
+
/** The content of the screen the modal shows now, which the `content` detent measures; none without one. */
|
|
54
|
+
private val shownContent: () -> IReactView?,
|
|
55
|
+
/** The screen the modal shows now moves its content out of the keyboard's way (`layout.adjustResize`). */
|
|
56
|
+
private val adjustsForKeyboard: () -> Boolean,
|
|
45
57
|
) : SheetBack {
|
|
46
58
|
interface Events {
|
|
47
59
|
/** The user dragged the sheet to another detent. */
|
|
@@ -63,6 +75,19 @@ internal class ModalSheet(
|
|
|
63
75
|
private var options = SheetOptions()
|
|
64
76
|
private var swipeToDismiss = true
|
|
65
77
|
private var detents: List<SheetDetent> = listOf(SheetDetent.Large)
|
|
78
|
+
// The detents the sheet rests at, from the lowest (`SheetStops.stops`): the behavior's states.
|
|
79
|
+
private var stops: List<SheetDetent> = listOf(SheetDetent.Large)
|
|
80
|
+
// The heights the behavior has for the stops, in pixels: lifted by the keyboard (`SheetStops.lifted`).
|
|
81
|
+
private var stopHeights: List<Int> = emptyList()
|
|
82
|
+
// The part of the keyboard over the navigation bar, in pixels; 0 without the keyboard.
|
|
83
|
+
private var keyboard = 0
|
|
84
|
+
// The stops' heights changed while the sheet moved (a drag, a slide): it takes them once it rests, and goes to the
|
|
85
|
+
// `selectedDetent` a merge named meanwhile ([pendingArrived]).
|
|
86
|
+
private var restopPending = false
|
|
87
|
+
// The sheet showed while its `content` detent had no height yet: once it has one, the sheet goes where the show
|
|
88
|
+
// would have taken it ([showTarget]), unless the user moved it first.
|
|
89
|
+
private var awaiting = false
|
|
90
|
+
private var pendingArrived: SheetDetent? = null
|
|
66
91
|
private var selected: SheetDetent? = null
|
|
67
92
|
private var topGap = 0
|
|
68
93
|
private var dragged = false
|
|
@@ -94,7 +119,31 @@ internal class ModalSheet(
|
|
|
94
119
|
// A list inside the sheet scrolls with it, as the Material behavior expects: up at half height the sheet goes up
|
|
95
120
|
// first, down at the top of the list the sheet comes down. The behavior follows views that scroll nested.
|
|
96
121
|
private val scrollViews = ViewTreeObserver.OnGlobalLayoutListener {
|
|
97
|
-
|
|
122
|
+
turnScrollViews()
|
|
123
|
+
// The bars above the content may have taken their height.
|
|
124
|
+
if (followed != null) contentChanged()
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// The shown screen's content that the `content` detent follows, its height without a height limit (pixels, none
|
|
128
|
+
// before it is measured), and the height of the detent the behavior has now.
|
|
129
|
+
private var followed: IReactView? = null
|
|
130
|
+
// The shown screen's content whose mounts turn the lists in it to scroll nested ([hearContent]).
|
|
131
|
+
private var heard: IReactView? = null
|
|
132
|
+
private var contentBody: Int? = null
|
|
133
|
+
private var appliedContentHeight: Int? = null
|
|
134
|
+
// When the content was measured last after a mount, and whether a measure waits ([contentMounted]).
|
|
135
|
+
private var lastMeasure = 0L
|
|
136
|
+
private var measureWaits = false
|
|
137
|
+
private val measureLater = Runnable {
|
|
138
|
+
measureWaits = false
|
|
139
|
+
lastMeasure = SystemClock.uptimeMillis()
|
|
140
|
+
measureContent()
|
|
141
|
+
contentChanged()
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// The stops stand in pixels: they follow the layout's height (a rotation, a resize).
|
|
145
|
+
private val layoutHeight = View.OnLayoutChangeListener { _, _, top, _, bottom, _, oldTop, _, oldBottom ->
|
|
146
|
+
if (bottom - top != oldBottom - oldTop) container.post { restop() }
|
|
98
147
|
}
|
|
99
148
|
|
|
100
149
|
/** The sheet is down, out of sight: the modal can go at once. */
|
|
@@ -113,6 +162,19 @@ internal class ModalSheet(
|
|
|
113
162
|
}
|
|
114
163
|
})
|
|
115
164
|
scrim.setOnTouchListener { _, event -> taps.onTouchEvent(event) }
|
|
165
|
+
// The keyboard covers the bottom of the window: the sheet stands on it, by the keyboard's part over the
|
|
166
|
+
// navigation bar (the detents count that bar already), unless the screen in it keeps its size for the keyboard
|
|
167
|
+
// (`layout.adjustResize: false`). The scrim, under the sheet, hears the window's insets.
|
|
168
|
+
ViewCompat.setOnApplyWindowInsetsListener(scrim) { _, insets ->
|
|
169
|
+
val ime = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
|
|
170
|
+
val bar = insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom
|
|
171
|
+
val lift = (ime - bar).coerceAtLeast(0)
|
|
172
|
+
if (lift != keyboard) {
|
|
173
|
+
keyboard = lift
|
|
174
|
+
restop()
|
|
175
|
+
}
|
|
176
|
+
insets
|
|
177
|
+
}
|
|
116
178
|
// The sheet keeps a touch that lands on it and no view in it takes (its bar, an empty part), as Material's
|
|
117
179
|
// BottomSheetDialog does: else the touch falls to the scrim under it, and a tap there closes a dimmed sheet;
|
|
118
180
|
// past an undimmed scrim it reaches the behavior, which takes the sheet into its drag state at the touch's
|
|
@@ -151,8 +213,11 @@ internal class ModalSheet(
|
|
|
151
213
|
container.viewTreeObserver.addOnGlobalLayoutListener(scrollViews)
|
|
152
214
|
}
|
|
153
215
|
|
|
154
|
-
/**
|
|
155
|
-
|
|
216
|
+
/**
|
|
217
|
+
* Slides the sheet up to its selected detent; [onShown] once it is there. Not [animated], or with the system's
|
|
218
|
+
* animations off ([SystemAnimations]), it stands there at once.
|
|
219
|
+
*/
|
|
220
|
+
fun show(sheetOptions: SheetOptions, swipeToDismiss: Boolean, animated: Boolean, onShown: () -> Unit) {
|
|
156
221
|
topGap = ViewCompat.getRootWindowInsets(layout)?.getInsets(WindowInsetsCompat.Type.systemBars())?.top ?: 0
|
|
157
222
|
layout.addView(scrim, CoordinatorLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT))
|
|
158
223
|
// As tall as its largest detent (maxHeight), not moved down by a margin: the behavior counts its states from
|
|
@@ -163,42 +228,48 @@ internal class ModalSheet(
|
|
|
163
228
|
params.gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
|
|
164
229
|
params.behavior = behavior
|
|
165
230
|
layout.addView(container, params)
|
|
231
|
+
layout.addOnLayoutChangeListener(layoutHeight)
|
|
166
232
|
shown = onShown
|
|
167
233
|
update(sheetOptions, swipeToDismiss, merge = null)
|
|
168
234
|
selected = sheetOptions.selectedDetent?.takeIf { it in detents }
|
|
235
|
+
awaiting = SheetDetent.Content in detents && contentHeight() == null
|
|
236
|
+
if (!animated || SystemAnimations.areOff(context)) {
|
|
237
|
+
// The behavior lays the sheet out at the state it has before its first layout, with no slide, and tells
|
|
238
|
+
// no state then: the sheet stands at its detent once laid out.
|
|
239
|
+
behavior.state = stateOf(showTarget())
|
|
240
|
+
container.post { if (closing == null) onState(behavior.state) }
|
|
241
|
+
return
|
|
242
|
+
}
|
|
169
243
|
// Laid out hidden first, so it slides up from the bottom.
|
|
170
|
-
container.post { if (closing == null) behavior.state = stateOf(
|
|
244
|
+
container.post { if (closing == null) behavior.state = stateOf(showTarget()) }
|
|
171
245
|
}
|
|
172
246
|
|
|
247
|
+
// The stop the show goes to: the selected one when it is a stop already, else the lowest.
|
|
248
|
+
private fun showTarget(): SheetDetent = selected?.takeIf { it in stops } ?: stops.first()
|
|
249
|
+
|
|
173
250
|
/**
|
|
174
251
|
* New resolved options: the detents and the look. The sheet moves only as [SheetStops] says, after a `mergeOptions`
|
|
175
252
|
* of [merge]; without one (the shown screen changed, the show) it stays where it is.
|
|
176
253
|
*/
|
|
177
254
|
fun update(sheetOptions: SheetOptions, swipeToDismiss: Boolean, merge: SheetOptions?) {
|
|
255
|
+
// The stop it rests at, by the stops it has now: the new ones may give its state another detent.
|
|
256
|
+
val settled = detentOf(behavior.state)
|
|
178
257
|
options = sheetOptions
|
|
179
258
|
this.swipeToDismiss = swipeToDismiss
|
|
180
|
-
detents = sheetOptions.detents?.takeIf { it.isNotEmpty() }
|
|
259
|
+
detents = sheetOptions.detents?.takeIf { it.isNotEmpty() } ?: listOf(SheetDetent.Large)
|
|
181
260
|
grabber.visibility = if (sheetOptions.grabber.isTrue) View.VISIBLE else View.GONE
|
|
182
261
|
behavior.dragAnywhere = sheetOptions.expandsOnScroll.get(true)
|
|
183
262
|
container.invalidateOutline()
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
if (settled != null) behavior.isHideable = swipeToDismiss && settled == detents.first()
|
|
188
|
-
val target = SheetStops.target(settled, detents, merge.selectedDetent, sheetOptions.selectedDetent)
|
|
189
|
-
// A merge that moves the sheet or keeps it from going down ends a preview of back on it.
|
|
190
|
-
if (target != null || !behavior.isHideable) stopBackPreview()
|
|
191
|
-
if (target != null) {
|
|
192
|
-
selected = target
|
|
193
|
-
behavior.state = stateOf(target)
|
|
194
|
-
}
|
|
195
|
-
updateScrim()
|
|
263
|
+
hearContent()
|
|
264
|
+
followContent()
|
|
265
|
+
restop(settled, merge?.selectedDetent)
|
|
196
266
|
}
|
|
197
267
|
|
|
198
|
-
/** Slides the sheet down; [onClosed] once it is out of sight. */
|
|
199
|
-
fun close(onClosed: () -> Unit) {
|
|
268
|
+
/** Slides the sheet down; [onClosed] once it is out of sight. Not [animated], or with animations off, at once. */
|
|
269
|
+
fun close(animated: Boolean, onClosed: () -> Unit) {
|
|
200
270
|
closing = onClosed
|
|
201
|
-
if (isClosed) {
|
|
271
|
+
if (isClosed || !animated || SystemAnimations.areOff(context)) {
|
|
272
|
+
stopBackPreview()
|
|
202
273
|
finishClosing()
|
|
203
274
|
return
|
|
204
275
|
}
|
|
@@ -216,6 +287,12 @@ internal class ModalSheet(
|
|
|
216
287
|
stopBackPreview()
|
|
217
288
|
stopBackFrames()
|
|
218
289
|
container.viewTreeObserver.removeOnGlobalLayoutListener(scrollViews)
|
|
290
|
+
layout.removeOnLayoutChangeListener(layoutHeight)
|
|
291
|
+
followed?.setOnContentMounted(CONTENT_KEY, null)
|
|
292
|
+
followed = null
|
|
293
|
+
container.removeCallbacks(measureLater)
|
|
294
|
+
heard?.setOnContentMounted(SCROLL_KEY, null)
|
|
295
|
+
heard = null
|
|
219
296
|
container.removeView(content)
|
|
220
297
|
layout.removeView(container)
|
|
221
298
|
layout.removeView(scrim)
|
|
@@ -296,52 +373,208 @@ internal class ModalSheet(
|
|
|
296
373
|
events.onCloseAttempted()
|
|
297
374
|
return
|
|
298
375
|
}
|
|
376
|
+
// With animations off the modal goes at once, and its close takes the sheet away with no slide.
|
|
377
|
+
if (SystemAnimations.areOff(context)) {
|
|
378
|
+
if (closing != null) return
|
|
379
|
+
closing = {}
|
|
380
|
+
return events.onClosedByUser()
|
|
381
|
+
}
|
|
299
382
|
behavior.isHideable = true
|
|
300
383
|
behavior.state = BottomSheetBehavior.STATE_HIDDEN
|
|
301
384
|
}
|
|
302
385
|
|
|
303
|
-
//
|
|
304
|
-
//
|
|
305
|
-
//
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
val
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
386
|
+
// The stops of the detents and how the Material behavior rests at them (`SheetStops.material`): at one or two it
|
|
387
|
+
// fits the sheet to its highest stop, at three it stops half expanded between them. The detent the options select
|
|
388
|
+
// stays the middle one when there are more.
|
|
389
|
+
// A new height of the sheet (`maxHeight`), of its collapsed state, or a change of the mode is laid out: the
|
|
390
|
+
// behavior measures the sheet and counts a fitted sheet's top in its layout. It says whether a layout comes.
|
|
391
|
+
private fun configure(): Boolean {
|
|
392
|
+
appliedContentHeight = contentHeight()
|
|
393
|
+
// A `content` detent not measured yet has no height: it stays out of the stops until it has one, or it would
|
|
394
|
+
// count as the full height and take the place of `large`.
|
|
395
|
+
val measured = detents.filter { it != SheetDetent.Content || contentHeight() != null }.ifEmpty { detents }
|
|
396
|
+
stops = SheetStops.stops(measured, ::heightOf, options.selectedDetent)
|
|
397
|
+
val lift = if (adjustsForKeyboard()) keyboard else 0
|
|
398
|
+
stopHeights = SheetStops.lifted(stops.map(::heightOf), lift, fullHeight())
|
|
399
|
+
val material = SheetStops.material(stopHeights, parentHeight())
|
|
400
|
+
val relayout = behavior.maxHeight != material.maxHeight || behavior.peekHeight != material.peekHeight ||
|
|
401
|
+
behavior.isFitToContents != material.fitToContents
|
|
402
|
+
behavior.isFitToContents = material.fitToContents
|
|
403
|
+
material.halfExpandedRatio?.takeIf { it > 0f && it < 1f }?.let { behavior.halfExpandedRatio = it }
|
|
404
|
+
behavior.expandedOffset = material.expandedOffset
|
|
405
|
+
behavior.maxHeight = material.maxHeight
|
|
406
|
+
behavior.skipCollapsed = material.skipCollapsed
|
|
407
|
+
behavior.peekHeight = material.peekHeight
|
|
408
|
+
if (relayout) container.requestLayout()
|
|
409
|
+
return relayout
|
|
317
410
|
}
|
|
318
411
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
412
|
+
/**
|
|
413
|
+
* The stops or their heights changed (the options, the layout, the content, the keyboard): the behavior takes them,
|
|
414
|
+
* and a sheet at rest goes to [arrived] (a `selectedDetent` a merge names) when it is a stop, else stays at the
|
|
415
|
+
* stop it rested at ([settled], by the stops before the change) where it stands now, else goes to the selected one
|
|
416
|
+
* or the lowest (`SheetStops.target`). A sheet on the move takes them once it rests.
|
|
417
|
+
*/
|
|
418
|
+
private fun restop(settled: SheetDetent? = detentOf(behavior.state), arrived: SheetDetent? = null) {
|
|
419
|
+
if (container.parent == null) return
|
|
420
|
+
val relayout = configure()
|
|
421
|
+
updateScrim()
|
|
422
|
+
if (closing != null || backFinishing) return
|
|
423
|
+
if (isMoving()) {
|
|
424
|
+
restopPending = true
|
|
425
|
+
if (arrived != null) pendingArrived = arrived
|
|
426
|
+
return
|
|
427
|
+
}
|
|
428
|
+
// Not shown yet: the show slides it to the stop a merge names.
|
|
429
|
+
if (settled == null) {
|
|
430
|
+
if (arrived != null && arrived in stops) selected = arrived
|
|
431
|
+
return
|
|
432
|
+
}
|
|
433
|
+
val shows = awaiting && contentHeight() != null
|
|
434
|
+
if (shows) awaiting = false
|
|
435
|
+
val wanted = arrived ?: if (shows) showTarget() else null
|
|
436
|
+
val target = SheetStops.target(settled, stops, wanted, options.selectedDetent) ?: settled
|
|
437
|
+
behavior.isHideable = swipeToDismiss && target == stops.first()
|
|
438
|
+
// A move, or a sheet that can no longer go down, ends a preview of back on it.
|
|
439
|
+
if (target != settled || !behavior.isHideable) stopBackPreview()
|
|
440
|
+
if (backPreview != null) return
|
|
441
|
+
if (target != settled) selected = target
|
|
442
|
+
if (!relayout) {
|
|
443
|
+
behavior.state = stateOf(target)
|
|
444
|
+
return
|
|
445
|
+
}
|
|
446
|
+
// The new height comes with the sheet's next layout, where the behavior puts a sheet at rest at its state's
|
|
447
|
+
// offset at once; a move to another stop follows it.
|
|
448
|
+
afterNextLayout {
|
|
449
|
+
val gone = closing != null || backFinishing || container.parent == null
|
|
450
|
+
if (gone || backPreview != null) return@afterNextLayout
|
|
451
|
+
if (isMoving()) {
|
|
452
|
+
restopPending = true
|
|
453
|
+
if (target != settled) pendingArrived = target
|
|
454
|
+
return@afterNextLayout
|
|
455
|
+
}
|
|
456
|
+
behavior.state = stateOf(target)
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
private fun isMoving(): Boolean =
|
|
461
|
+
behavior.state == BottomSheetBehavior.STATE_DRAGGING || behavior.state == BottomSheetBehavior.STATE_SETTLING
|
|
462
|
+
|
|
463
|
+
// Runs [step] on the pre-draw of the frame of the sheet's next layout: the behavior has placed it by then.
|
|
464
|
+
private fun afterNextLayout(step: () -> Unit) {
|
|
465
|
+
container.addOnLayoutChangeListener(object : View.OnLayoutChangeListener {
|
|
466
|
+
override fun onLayoutChange(
|
|
467
|
+
view: View, left: Int, top: Int, right: Int, bottom: Int,
|
|
468
|
+
oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int,
|
|
469
|
+
) {
|
|
470
|
+
view.removeOnLayoutChangeListener(this)
|
|
471
|
+
OneShotPreDrawListener.add(view, step)
|
|
472
|
+
}
|
|
473
|
+
})
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
private fun stateOf(detent: SheetDetent): Int = when (detent) {
|
|
477
|
+
stops.last() -> BottomSheetBehavior.STATE_EXPANDED
|
|
478
|
+
stops.first() -> BottomSheetBehavior.STATE_COLLAPSED
|
|
479
|
+
else -> BottomSheetBehavior.STATE_HALF_EXPANDED
|
|
323
480
|
}
|
|
324
481
|
|
|
325
482
|
private fun detentOf(state: Int): SheetDetent? = when (state) {
|
|
326
|
-
BottomSheetBehavior.STATE_EXPANDED ->
|
|
327
|
-
BottomSheetBehavior.STATE_HALF_EXPANDED ->
|
|
328
|
-
BottomSheetBehavior.STATE_COLLAPSED ->
|
|
483
|
+
BottomSheetBehavior.STATE_EXPANDED -> stops.last()
|
|
484
|
+
BottomSheetBehavior.STATE_HALF_EXPANDED -> stops.getOrNull(1)?.takeIf { stops.size == SheetStops.MAX_STOPS }
|
|
485
|
+
BottomSheetBehavior.STATE_COLLAPSED -> stops.first()
|
|
329
486
|
else -> null
|
|
330
487
|
}
|
|
331
488
|
|
|
489
|
+
private fun parentHeight(): Int = if (layout.height > 0) layout.height else layout.rootView.height
|
|
490
|
+
|
|
491
|
+
// The tallest a sheet is: the layout under the status bar.
|
|
492
|
+
private fun fullHeight(): Int = parentHeight() - topGap
|
|
493
|
+
|
|
332
494
|
/** The height of the sheet at the detent, in px. */
|
|
333
495
|
private fun heightOf(detent: SheetDetent): Int {
|
|
334
|
-
val full = (
|
|
496
|
+
val full = fullHeight()
|
|
335
497
|
return when (detent) {
|
|
336
498
|
SheetDetent.Large -> full
|
|
337
499
|
SheetDetent.Medium -> (full + topGap) / 2
|
|
500
|
+
SheetDetent.Content -> contentHeight()?.coerceAtMost(full) ?: full
|
|
338
501
|
is SheetDetent.Height -> detent.dp.toFloat().dp.coerceAtMost(full)
|
|
339
502
|
}
|
|
340
503
|
}
|
|
341
504
|
|
|
505
|
+
// The `content` detent follows the content of the screen shown now, and measures it after each of its mounts; a
|
|
506
|
+
// sheet without the detent follows nothing.
|
|
507
|
+
private fun followContent() {
|
|
508
|
+
val content = if (SheetDetent.Content in detents) shownContent() else null
|
|
509
|
+
if (content !== followed) {
|
|
510
|
+
followed?.setOnContentMounted(CONTENT_KEY, null)
|
|
511
|
+
followed = content
|
|
512
|
+
// The last height stays until the new content has one: a push in the sheet doesn't jump it to the top.
|
|
513
|
+
content?.setOnContentMounted(CONTENT_KEY) { contentMounted() }
|
|
514
|
+
}
|
|
515
|
+
measureContent()
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Fabric tells every mount of the app: the content is measured at most once per [MEASURE_INTERVAL_MS], the last
|
|
519
|
+
// mount of a burst included, so a screen that renders each frame elsewhere doesn't lay the sheet's tree out each
|
|
520
|
+
// frame too.
|
|
521
|
+
private fun contentMounted() {
|
|
522
|
+
val wait = lastMeasure + MEASURE_INTERVAL_MS - SystemClock.uptimeMillis()
|
|
523
|
+
if (wait > 0) {
|
|
524
|
+
if (!measureWaits) {
|
|
525
|
+
measureWaits = true
|
|
526
|
+
container.postDelayed(measureLater, wait)
|
|
527
|
+
}
|
|
528
|
+
return
|
|
529
|
+
}
|
|
530
|
+
measureLater.run()
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// The content's own height at the width it has (the sheet's, before it is laid out); kept until a measure gives
|
|
534
|
+
// one again.
|
|
535
|
+
private fun measureContent() {
|
|
536
|
+
val content = followed ?: return
|
|
537
|
+
val width = content.asView().width.takeIf { it > 0 } ?: container.width.takeIf { it > 0 } ?: sheetWidth()
|
|
538
|
+
content.contentSize(width)?.let { contentBody = it.height }
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// The height of the `content` detent: the content's own, the bars above it in the sheet (a stack's top bar), and
|
|
542
|
+
// the navigation bar below it, which a detent leaves room for as UIKit adds the bottom safe area; none before the
|
|
543
|
+
// content is measured.
|
|
544
|
+
private fun contentHeight(): Int? {
|
|
545
|
+
val body = contentBody ?: return null
|
|
546
|
+
val insets = ViewCompat.getRootWindowInsets(layout)
|
|
547
|
+
val bottom = insets?.getInsets(WindowInsetsCompat.Type.navigationBars())?.bottom ?: 0
|
|
548
|
+
return offsetAbove() + body + bottom
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// How far below the sheet's top the content stands, by layout: the views between them, without their transforms.
|
|
552
|
+
private fun offsetAbove(): Int {
|
|
553
|
+
var view: View = followed?.asView() ?: return 0
|
|
554
|
+
var top = 0
|
|
555
|
+
while (view !== container) {
|
|
556
|
+
top += view.top
|
|
557
|
+
view = view.parent as? View ?: return 0
|
|
558
|
+
}
|
|
559
|
+
return top
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// The content may have changed its height: the stops follow it.
|
|
563
|
+
private fun contentChanged() {
|
|
564
|
+
if (contentHeight() != appliedContentHeight) restop()
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
private fun sheetWidth(): Int {
|
|
568
|
+
val width = if (layout.width > 0) layout.width else layout.rootView.width
|
|
569
|
+
return width.coerceAtMost(MAX_WIDTH_DP.dp)
|
|
570
|
+
}
|
|
571
|
+
|
|
342
572
|
private fun onState(state: Int) {
|
|
343
573
|
when (state) {
|
|
344
|
-
BottomSheetBehavior.STATE_DRAGGING ->
|
|
574
|
+
BottomSheetBehavior.STATE_DRAGGING -> {
|
|
575
|
+
dragged = true
|
|
576
|
+
awaiting = false
|
|
577
|
+
}
|
|
345
578
|
BottomSheetBehavior.STATE_HIDDEN -> {
|
|
346
579
|
backFinishing = false
|
|
347
580
|
when {
|
|
@@ -357,7 +590,7 @@ internal class ModalSheet(
|
|
|
357
590
|
if (closing != null) return
|
|
358
591
|
val detent = detentOf(state)
|
|
359
592
|
// A drag down closes the sheet from its smallest detent only; from a larger one it stops at the next.
|
|
360
|
-
behavior.isHideable = swipeToDismiss && detent ==
|
|
593
|
+
behavior.isHideable = swipeToDismiss && detent == stops.first()
|
|
361
594
|
shown?.let {
|
|
362
595
|
shown = null
|
|
363
596
|
it()
|
|
@@ -367,6 +600,12 @@ internal class ModalSheet(
|
|
|
367
600
|
events.onDetentChanged(detent)
|
|
368
601
|
}
|
|
369
602
|
dragged = false
|
|
603
|
+
if (restopPending) {
|
|
604
|
+
val arrived = pendingArrived
|
|
605
|
+
restopPending = false
|
|
606
|
+
pendingArrived = null
|
|
607
|
+
container.post { restop(arrived = arrived) }
|
|
608
|
+
}
|
|
370
609
|
}
|
|
371
610
|
}
|
|
372
611
|
updateScrim()
|
|
@@ -384,9 +623,9 @@ internal class ModalSheet(
|
|
|
384
623
|
// Hidden at the end of back's slide, it still stands at its detent until the next layout.
|
|
385
624
|
if (behavior.state == BottomSheetBehavior.STATE_HIDDEN) return setDim(0f)
|
|
386
625
|
val height = layout.height - sheetTop()
|
|
387
|
-
val undimmed = options.largestUndimmedDetent
|
|
388
|
-
val from = undimmed?.let {
|
|
389
|
-
val to =
|
|
626
|
+
val undimmed = stops.indexOf(options.largestUndimmedDetent).takeIf { it >= 0 }
|
|
627
|
+
val from = undimmed?.let { stopHeights.getOrNull(it) } ?: 0
|
|
628
|
+
val to = stopHeights.firstOrNull { it > from } ?: return run { setDim(0f) }
|
|
390
629
|
setDim(((height - from).toFloat() / (to - from)).coerceIn(0f, 1f))
|
|
391
630
|
}
|
|
392
631
|
|
|
@@ -406,9 +645,35 @@ internal class ModalSheet(
|
|
|
406
645
|
|
|
407
646
|
private fun cornerRadius(): Int = if (options.cornerRadius.hasValue()) options.cornerRadius.get().toFloat().dp else 28.dp
|
|
408
647
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
648
|
+
// React Native mounts the lists of the shown screen after the sheet's layout, and its views take no layout of their
|
|
649
|
+
// own: the sheet hears each mount of the content and turns a new list to scroll nested there.
|
|
650
|
+
private fun hearContent() {
|
|
651
|
+
val content = if (behavior.dragAnywhere) shownContent() else null
|
|
652
|
+
if (content !== heard) {
|
|
653
|
+
heard?.setOnContentMounted(SCROLL_KEY, null)
|
|
654
|
+
heard = content
|
|
655
|
+
content?.setOnContentMounted(SCROLL_KEY) { turnScrollViews() }
|
|
656
|
+
}
|
|
657
|
+
turnScrollViews()
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// The behavior finds the lists that scroll nested only in its layout of the sheet: a list turned so gets the sheet
|
|
661
|
+
// one more, else the behavior knows no list and takes a drag down on a scrolled one for a drag of the sheet.
|
|
662
|
+
private fun turnScrollViews() {
|
|
663
|
+
if (behavior.dragAnywhere && enableNestedScrolling(container)) container.requestLayout()
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// Whether a list was turned to scroll nested now.
|
|
667
|
+
private fun enableNestedScrolling(view: View): Boolean {
|
|
668
|
+
var turned = false
|
|
669
|
+
if (view is ScrollView && !view.isNestedScrollingEnabled) {
|
|
670
|
+
view.isNestedScrollingEnabled = true
|
|
671
|
+
turned = true
|
|
672
|
+
}
|
|
673
|
+
if (view is ViewGroup) {
|
|
674
|
+
for (i in 0 until view.childCount) turned = enableNestedScrolling(view.getChildAt(i)) || turned
|
|
675
|
+
}
|
|
676
|
+
return turned
|
|
412
677
|
}
|
|
413
678
|
|
|
414
679
|
/** With `expandsOnScroll: false` only the top of the sheet, around the grabber, drags it; the rest scrolls. */
|
|
@@ -424,7 +689,7 @@ internal class ModalSheet(
|
|
|
424
689
|
when (event.actionMasked) {
|
|
425
690
|
MotionEvent.ACTION_DOWN -> {
|
|
426
691
|
downY = event.rawY
|
|
427
|
-
downAtSmallest = detentOf(state) ==
|
|
692
|
+
downAtSmallest = detentOf(state) == stops.first()
|
|
428
693
|
}
|
|
429
694
|
MotionEvent.ACTION_UP -> {
|
|
430
695
|
if (!swipeToDismiss && closing == null && downAtSmallest && event.rawY - downY > ATTEMPT_DISTANCE_DP.dp) {
|
|
@@ -467,5 +732,14 @@ internal class ModalSheet(
|
|
|
467
732
|
private const val SCRIM_ALPHA = 0.32f
|
|
468
733
|
private const val GRAB_AREA_DP = 48
|
|
469
734
|
private const val ATTEMPT_DISTANCE_DP = 48
|
|
735
|
+
|
|
736
|
+
// The key of the sheet's handler of its content's mounts.
|
|
737
|
+
private const val CONTENT_KEY = "sheet.content"
|
|
738
|
+
|
|
739
|
+
// The least time between two measures of the content after its mounts.
|
|
740
|
+
private const val MEASURE_INTERVAL_MS = 100L
|
|
741
|
+
|
|
742
|
+
// The key of the sheet's handler that turns its content's lists to scroll nested.
|
|
743
|
+
private const val SCROLL_KEY = "sheet.scroll"
|
|
470
744
|
}
|
|
471
745
|
}
|
|
@@ -14,10 +14,13 @@ import android.widget.FrameLayout
|
|
|
14
14
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
15
15
|
import androidx.core.view.ViewCompat
|
|
16
16
|
import androidx.core.view.WindowInsetsCompat
|
|
17
|
+
import dev.apexnative.navigation.options.DefaultOptions
|
|
17
18
|
import dev.apexnative.navigation.options.ModalAnimationOptions
|
|
18
19
|
import dev.apexnative.navigation.options.Options
|
|
19
20
|
import dev.apexnative.navigation.utils.TestIds
|
|
20
21
|
import dev.apexnative.navigation.utils.dp
|
|
22
|
+
import dev.apexnative.navigation.viewcontrollers.component.ComponentViewController
|
|
23
|
+
import dev.apexnative.navigation.viewcontrollers.parent.ParentController
|
|
21
24
|
import dev.apexnative.navigation.viewcontrollers.viewcontroller.ViewController
|
|
22
25
|
import dev.apexnative.navigation.views.animations.AnimationLayer
|
|
23
26
|
import dev.apexnative.navigation.views.animations.AnimationRules
|
|
@@ -96,7 +99,7 @@ internal class ModalViews(
|
|
|
96
99
|
val container = parts.container
|
|
97
100
|
container.isWaiting = false
|
|
98
101
|
container.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
|
|
99
|
-
if (record.isSheet) return presentSheet(record, parts, options, seen, events, onEnd)
|
|
102
|
+
if (record.isSheet) return presentSheet(record, parts, options, animated, seen, events, onEnd)
|
|
100
103
|
applyTestIds(record, parts, options)
|
|
101
104
|
if (!animated || !seen) {
|
|
102
105
|
SideAnimator.rest(container)
|
|
@@ -118,11 +121,13 @@ internal class ModalViews(
|
|
|
118
121
|
}
|
|
119
122
|
|
|
120
123
|
// The root's view leaves the container and goes into the sheet's, in one turn; the sheet slides to its first
|
|
121
|
-
// detent. Nobody sees the activity: the show counts as done now, the
|
|
124
|
+
// detent, or stands there at once when not [animated]. Nobody sees the activity: the show counts as done now, the
|
|
125
|
+
// sheet's later end does nothing.
|
|
122
126
|
private fun presentSheet(
|
|
123
127
|
record: ModalRecord,
|
|
124
128
|
parts: Parts,
|
|
125
129
|
options: Options,
|
|
130
|
+
animated: Boolean,
|
|
126
131
|
seen: Boolean,
|
|
127
132
|
events: ModalSheet.Events,
|
|
128
133
|
onEnd: () -> Unit,
|
|
@@ -130,7 +135,16 @@ internal class ModalViews(
|
|
|
130
135
|
val container = parts.container
|
|
131
136
|
val view = record.root.view
|
|
132
137
|
container.removeView(view)
|
|
133
|
-
val sheet = ModalSheet(
|
|
138
|
+
val sheet = ModalSheet(
|
|
139
|
+
container,
|
|
140
|
+
view,
|
|
141
|
+
events,
|
|
142
|
+
shownContent = { (shownScreen(record.root) as? ComponentViewController)?.content },
|
|
143
|
+
adjustsForKeyboard = {
|
|
144
|
+
val screen = shownScreen(record.root)
|
|
145
|
+
screen == null || !screen.resolveCurrentOptions(DefaultOptions.current).layout.adjustResize.isFalse
|
|
146
|
+
},
|
|
147
|
+
)
|
|
134
148
|
parts.sheet = sheet
|
|
135
149
|
container.alpha = 1f
|
|
136
150
|
applyTestIds(record, parts, options)
|
|
@@ -141,11 +155,18 @@ internal class ModalViews(
|
|
|
141
155
|
onEnd()
|
|
142
156
|
}
|
|
143
157
|
}
|
|
144
|
-
sheet.show(options.modal.sheet, swipeToDismissOf(options)) { shown() }
|
|
158
|
+
sheet.show(options.modal.sheet, swipeToDismissOf(options), animated) { shown() }
|
|
145
159
|
if (!seen) shown()
|
|
146
160
|
return { told = true }
|
|
147
161
|
}
|
|
148
162
|
|
|
163
|
+
// The screen a layout shows now: down through the child each parent shows.
|
|
164
|
+
private fun shownScreen(root: ViewController<*>): ViewController<*>? {
|
|
165
|
+
var node = root
|
|
166
|
+
while (node is ParentController<*>) node = node.currentChild ?: return null
|
|
167
|
+
return node
|
|
168
|
+
}
|
|
169
|
+
|
|
149
170
|
override fun dismiss(
|
|
150
171
|
record: ModalRecord,
|
|
151
172
|
animated: Boolean,
|
|
@@ -159,7 +180,7 @@ internal class ModalViews(
|
|
|
159
180
|
if (sheet != null) {
|
|
160
181
|
if (!seen) return none(onEnd)
|
|
161
182
|
var told = false
|
|
162
|
-
sheet.close {
|
|
183
|
+
sheet.close(animated) {
|
|
163
184
|
if (!told) {
|
|
164
185
|
told = true
|
|
165
186
|
onEnd()
|