@apexnative/navigation 1.0.0-alpha.5 → 1.0.0-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/README.md +1 -1
- package/android/CLAUDE.md +14 -12
- package/android/CORE.md +9 -6
- package/android/src/main/java/dev/apexnative/navigation/NavigationActivity.kt +36 -5
- package/android/src/main/java/dev/apexnative/navigation/options/BottomTabsOptions.kt +13 -8
- package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +3 -3
- package/android/src/main/java/dev/apexnative/navigation/react/ReloadCoordinator.kt +0 -2
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenEnvironment.kt +177 -0
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenRegions.kt +12 -49
- package/android/src/main/java/dev/apexnative/navigation/react/ScrollEvents.kt +49 -128
- package/android/src/main/java/dev/apexnative/navigation/safearea/SafeAreaView.kt +6 -6
- package/android/src/main/java/dev/apexnative/navigation/utils/ViewExtensions.kt +0 -13
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/arrangement/ArrangementController.kt +9 -7
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsCollapseBehavior.kt +5 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsController.kt +13 -10
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/CustomTabRowGeometry.kt +22 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/TabsRoom.kt +0 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ComponentViewController.kt +7 -8
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ScreenSafeArea.kt +29 -11
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/externalcomponent/ExternalComponentViewController.kt +2 -6
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalCoordinator.kt +20 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalSheet.kt +119 -8
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +7 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/SheetBack.kt +42 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt +4 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/ParentController.kt +3 -6
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/PreparedFrame.kt +3 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackCommands.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +4 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +3 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/viewcontroller/ScrollEventListener.kt +6 -9
- package/android/src/main/java/dev/apexnative/navigation/views/animations/AnimationLayer.kt +73 -0
- package/android/src/main/java/dev/apexnative/navigation/views/animations/ValueInterpolation.kt +14 -0
- package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/BottomTabsLayout.kt +61 -2
- package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/CustomTabRow.kt +72 -10
- package/android/src/main/java/dev/apexnative/navigation/views/element/ElementTransitions.kt +9 -2
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ElementPictures.kt +11 -0
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/FlightGeometry.kt +318 -25
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMatrixAnimator.kt +18 -20
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PlainViewAnimator.kt +17 -25
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ReactImageAnimator.kt +15 -22
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/SharedAnimatorFactory.kt +12 -9
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/TextChangeAnimator.kt +84 -35
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewChangeAnimator.kt +49 -40
- package/docs-md/architecture/transitions.md +4 -2
- package/docs-md/containers/modals.md +2 -0
- package/docs-md/containers/overlays.md +1 -1
- package/docs-md/containers/side-menu.md +1 -1
- package/docs-md/containers/tabs.md +2 -0
- package/docs-md/flows/bottom-sheets.md +1 -1
- package/docs-md/fundamentals/starting-the-app.md +1 -1
- package/docs-md/help/glossary.md +2 -2
- package/docs-md/help/troubleshooting.md +21 -0
- package/docs-md/index.json +12 -0
- package/docs-md/migration/from-expo-router.md +1 -1
- package/docs-md/migration/from-react-native-navigation.md +1 -1
- package/docs-md/migration/from-react-navigation.md +2 -2
- package/docs-md/screen-ui/back-and-gestures.md +14 -0
- package/docs-md/screen-ui/bar-buttons.md +4 -4
- package/docs-md/screen-ui/search.md +1 -1
- package/docs-md/screen-ui/system-ui.md +16 -11
- package/docs-md/screen-ui/toolbar-and-fab.md +2 -2
- package/docs-md/screen-ui/top-bar.md +8 -4
- package/ios/Animations/ANNFrameDriver.mm +2 -0
- package/ios/Animations/Elements/ANNSharedElementFlight.mm +70 -4
- package/ios/Animations/Interpolators/ANNInterpolate.h +2 -30
- package/ios/Animations/Interpolators/ANNTransformParts.h +48 -0
- package/ios/Animations/Interpolators/ANNTransformParts.mm +507 -0
- package/ios/Animations/Interpolators/ANNViewTracks.h +6 -4
- package/ios/Animations/Interpolators/ANNViewTracks.mm +49 -9
- package/ios/CLAUDE.md +17 -15
- package/ios/CORE.md +24 -20
- package/ios/Contract/ANNCommandRunner.h +8 -7
- package/ios/Contract/ANNCommandRunner.mm +21 -27
- package/ios/Contract/ANNLayoutNode.h +3 -3
- package/ios/Contract/ANNLayoutReader.h +3 -3
- package/ios/Core/ANNJavaScriptCalls.h +11 -12
- package/ios/Core/ANNJavaScriptCalls.mm +7 -16
- package/ios/Core/ANNReloadCoordinator.mm +4 -8
- package/ios/Core/ApexNavigation+Internal.h +1 -1
- package/ios/Layouts/Arrangement/ANNArrangementState.h +2 -17
- package/ios/Layouts/Arrangement/ANNArrangementState.mm +0 -38
- package/ios/Layouts/Arrangement/ANNArrangementViewController.mm +4 -4
- package/ios/Layouts/Screen/ANNComponentViewController.h +2 -2
- package/ios/Layouts/Screen/ANNComponentViewController.mm +9 -22
- package/ios/Layouts/Screen/ANNPreviewInteraction.h +7 -4
- package/ios/Layouts/Screen/ANNPreviewInteraction.mm +72 -10
- package/ios/Layouts/Screen/ANNScreenEnvironment.h +63 -0
- package/ios/Layouts/Screen/ANNScreenEnvironment.mm +181 -0
- package/ios/Layouts/Screen/ANNScreenRegions.h +3 -18
- package/ios/Layouts/Screen/ANNScreenRegions.mm +0 -38
- package/ios/Layouts/Screen/ANNScreenSafeArea.h +4 -9
- package/ios/Layouts/Screen/ANNScreenSafeArea.mm +14 -16
- package/ios/Layouts/Stack/ANNPreviewPush.h +7 -6
- package/ios/Layouts/Stack/ANNPreviewPush.mm +57 -5
- package/ios/Layouts/Tabs/ANNBottomTabsAccessoryView.h +12 -1
- package/ios/Layouts/Tabs/ANNBottomTabsAccessoryView.mm +23 -0
- package/ios/Layouts/Tabs/ANNBottomTabsController.mm +2 -1
- package/ios/Layouts/Tabs/ANNBottomTabsCustomRow.mm +8 -3
- package/ios/Layouts/Tabs/ANNBottomTabsPresenter.h +4 -0
- package/ios/Layouts/Tabs/ANNBottomTabsPresenter.mm +123 -36
- package/ios/Layouts/Tabs/ANNTabsDesign.h +6 -5
- package/ios/Layouts/Tabs/ANNTabsDesign.mm +9 -1
- package/ios/Layouts/Tabs/UITabBar+utils.h +5 -5
- package/ios/Layouts/Tabs/UITabBar+utils.mm +32 -13
- package/ios/Options/ANNBottomTabsOptions.h +2 -0
- package/ios/Options/ANNBottomTabsOptions.mm +1 -0
- package/ios/Options/ANNOptions.h +18 -0
- package/ios/Options/ANNOptions.mm +112 -1
- package/ios/Options/ANNOptionsReader.mm +110 -0
- package/ios/Options/Params/ANNParam.h +15 -0
- package/ios/Options/Params/ANNParam.mm +61 -0
- package/ios/Options/Params/Color.h +3 -0
- package/ios/Options/Params/Color.mm +21 -0
- package/ios/Options/Params/Image.h +4 -0
- package/ios/Options/Params/Image.mm +20 -0
- package/ios/React/ANNReactView.mm +6 -25
- package/ios/React/ANNSafeAreaViewComponentView.mm +26 -15
- package/ios/React/ANNSurfaceHostingView.h +6 -0
- package/ios/React/ANNSurfaceHostingView.mm +116 -0
- package/ios/TopBar/ANNBackButtonPresenter.mm +6 -5
- package/ios/TopBar/ANNBarAppearance.h +2 -1
- package/ios/TopBar/ANNBarAppearance.mm +111 -30
- package/ios/TopBar/ANNBarBackgroundComponent.mm +16 -0
- package/ios/TopBar/ANNBarButtonComponentView.h +4 -4
- package/ios/TopBar/ANNBarButtonComponentView.mm +1 -1
- package/ios/TopBar/ANNBarButtons.h +5 -3
- package/ios/TopBar/ANNBarButtons.mm +186 -17
- package/ios/TopBar/ANNLargeTitlePresenter.mm +24 -4
- package/ios/TopBar/ANNMainList.h +3 -0
- package/ios/TopBar/ANNMainList.mm +4 -0
- package/ios/TopBar/ANNScrollEdgeEffects.h +22 -6
- package/ios/TopBar/ANNScrollEdgeEffects.mm +123 -16
- package/ios/TopBar/ANNSearchPresenter.mm +2 -2
- package/ios/TopBar/ANNSearchResultsViewController.mm +5 -5
- package/ios/TopBar/ANNTopBarPresenter.h +3 -2
- package/ios/TopBar/ANNTopBarPresenter.mm +95 -22
- package/ios/TurboModules/ANNTurboModule.mm +20 -16
- package/lib/module/ARCHITECTURE.md +8 -4
- package/lib/module/api/hooks/useSafeAreaInsets.js +4 -3
- package/lib/module/api/hooks/useSafeAreaInsets.js.map +1 -1
- package/lib/module/api/options/OptionsChain.js +2 -2
- package/lib/module/engine/commands.js +21 -1
- package/lib/module/engine/commands.js.map +1 -1
- package/lib/module/engine/screens.js +29 -4
- package/lib/module/engine/screens.js.map +1 -1
- package/lib/typescript/api/app/createNavigation.d.ts +3 -3
- package/lib/typescript/api/hooks/useSafeAreaInsets.d.ts +4 -3
- package/lib/typescript/api/hooks/useSafeAreaInsets.d.ts.map +1 -1
- package/lib/typescript/api/options/OptionsChain.d.ts +2 -2
- package/lib/typescript/engine/commands.d.ts.map +1 -1
- package/lib/typescript/engine/screens.d.ts +20 -6
- package/lib/typescript/engine/screens.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/animations.d.ts +12 -7
- package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/bottomTabs.d.ts +18 -5
- package/lib/typescript/interfaces/options/bottomTabs.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/buttons.d.ts +8 -8
- package/lib/typescript/interfaces/options/system.d.ts +8 -5
- package/lib/typescript/interfaces/options/system.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/toolbar.d.ts +2 -2
- package/lib/typescript/interfaces/options/topBar.d.ts +32 -29
- package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/ARCHITECTURE.md +8 -4
- package/src/api/app/createNavigation.ts +3 -3
- package/src/api/hooks/useSafeAreaInsets.ts +4 -3
- package/src/api/options/OptionsChain.ts +2 -2
- package/src/engine/commands.ts +30 -6
- package/src/engine/screens.tsx +29 -8
- package/src/interfaces/options/animations.ts +12 -7
- package/src/interfaces/options/bottomTabs.ts +18 -5
- package/src/interfaces/options/buttons.ts +8 -8
- package/src/interfaces/options/system.ts +8 -5
- package/src/interfaces/options/toolbar.ts +2 -2
- package/src/interfaces/options/topBar.ts +32 -29
- package/android/src/main/java/dev/apexnative/navigation/react/ArrangementStates.kt +0 -42
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenInsets.kt +0 -70
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/CustomTabRowPlacer.kt +0 -223
- package/ios/Layouts/Screen/ANNScreenInsets.h +0 -30
- package/ios/Layouts/Screen/ANNScreenInsets.mm +0 -70
|
@@ -1,25 +1,17 @@
|
|
|
1
1
|
package dev.apexnative.navigation.react
|
|
2
2
|
|
|
3
|
-
import android.
|
|
4
|
-
import android.
|
|
5
|
-
import com.facebook.react.
|
|
6
|
-
import com.facebook.react.uimanager.UIManagerHelper
|
|
7
|
-
import com.facebook.react.uimanager.events.Event
|
|
8
|
-
import com.facebook.react.uimanager.events.EventDispatcherListener
|
|
9
|
-
import com.facebook.react.views.scroll.ScrollEvent
|
|
3
|
+
import android.view.ViewGroup
|
|
4
|
+
import android.widget.HorizontalScrollView
|
|
5
|
+
import com.facebook.react.views.scroll.ReactScrollViewHelper
|
|
10
6
|
import com.facebook.react.views.scroll.ScrollEventType
|
|
11
|
-
import dev.apexnative.navigation.utils.NavigationLog
|
|
12
|
-
import dev.apexnative.navigation.utils.UiThread
|
|
13
|
-
import dev.apexnative.navigation.viewcontrollers.viewcontroller.ScrollEventListener
|
|
14
|
-
import java.lang.reflect.Field
|
|
15
|
-
import java.util.Collections
|
|
16
|
-
import java.util.WeakHashMap
|
|
17
7
|
|
|
18
8
|
/**
|
|
19
|
-
* The scroll events of React Native's
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* the
|
|
9
|
+
* The scroll events of React Native's scroll views, as the screens' scroll listeners hear them: through React Native's
|
|
10
|
+
* public scroll listener ([ReactScrollViewHelper.addScrollListener]), which every `ReactScrollView`,
|
|
11
|
+
* `ReactHorizontalScrollView` and `ReactNestedScrollView` of the process calls on the main thread just before it
|
|
12
|
+
* dispatches the same event to JavaScript, after the same throttle. One listener serves every screen and every React
|
|
13
|
+
* context; it is added while a screen listens and removed when the last one stops. Horizontal scroll views are left
|
|
14
|
+
* out: a pager or a carousel doesn't move the bars. Main thread only.
|
|
23
15
|
*/
|
|
24
16
|
internal object ScrollEvents {
|
|
25
17
|
/** What a scroll event is. */
|
|
@@ -28,133 +20,62 @@ internal object ScrollEvents {
|
|
|
28
20
|
/** One scroll event: the list's tag (its view's id), its kind, its vertical offset and speed, in pixels. */
|
|
29
21
|
class Reading(val viewTag: Int, val kind: Kind, val offsetY: Float, val velocityY: Float)
|
|
30
22
|
|
|
31
|
-
|
|
23
|
+
// One screen's listening: its own object, so that a stop takes away that listening and no other.
|
|
24
|
+
private class Listening(val handler: (Reading) -> Unit)
|
|
32
25
|
|
|
33
|
-
|
|
34
|
-
private
|
|
26
|
+
// The screens' listenings, in the order they came.
|
|
27
|
+
private val listenings = mutableListOf<Listening>()
|
|
35
28
|
|
|
36
|
-
//
|
|
37
|
-
private val
|
|
29
|
+
// React Native holds its scroll listeners weakly: this object keeps the one it added for as long as it is added.
|
|
30
|
+
private val listener = object : ReactScrollViewHelper.ScrollListener {
|
|
31
|
+
override fun onScroll(
|
|
32
|
+
scrollView: ViewGroup?,
|
|
33
|
+
scrollEventType: ScrollEventType?,
|
|
34
|
+
xVelocity: Float,
|
|
35
|
+
yVelocity: Float,
|
|
36
|
+
) = hear(scrollView, scrollEventType, yVelocity)
|
|
38
37
|
|
|
39
|
-
|
|
40
|
-
fun waitForDispatcher(listener: ScrollEventListener) {
|
|
41
|
-
waiting += listener
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/** A React context is ready (`ReloadCoordinator`): the listeners waiting for an event dispatcher listen now. */
|
|
45
|
-
fun dispatcherReady() {
|
|
46
|
-
val listeners = waiting.toList()
|
|
47
|
-
waiting.clear()
|
|
48
|
-
listeners.forEach(ScrollEventListener::attachIfNeeded)
|
|
38
|
+
override fun onLayout(scrollView: ViewGroup?) = Unit
|
|
49
39
|
}
|
|
50
40
|
|
|
51
41
|
/**
|
|
52
|
-
* Calls [handler]
|
|
53
|
-
*
|
|
54
|
-
* its scroll events can't be read.
|
|
42
|
+
* Calls [handler] with each scroll event of a vertical scroll view of the app, until the function returned is
|
|
43
|
+
* called.
|
|
55
44
|
*/
|
|
56
|
-
fun listen(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
val listener = EventDispatcherListener { event ->
|
|
66
|
-
// The event goes back to its pool after the dispatch: it is read now, on the thread that dispatches.
|
|
67
|
-
val reading = read(event) ?: return@EventDispatcherListener
|
|
68
|
-
if (UiThread.isMainThread()) handler(reading) else UiThread.post { handler(reading) }
|
|
45
|
+
fun listen(handler: (Reading) -> Unit): () -> Unit {
|
|
46
|
+
val listening = Listening(handler)
|
|
47
|
+
if (listenings.isEmpty()) ReactScrollViewHelper.addScrollListener(listener)
|
|
48
|
+
listenings += listening
|
|
49
|
+
return {
|
|
50
|
+
// By identity: the second call of a stop finds nothing to take away.
|
|
51
|
+
if (listenings.removeAll { it === listening } && listenings.isEmpty()) {
|
|
52
|
+
ReactScrollViewHelper.removeScrollListener(listener)
|
|
53
|
+
}
|
|
69
54
|
}
|
|
70
|
-
dispatcher.addListener(listener)
|
|
71
|
-
return { dispatcher.removeListener(listener) }
|
|
72
55
|
}
|
|
73
56
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
val
|
|
77
|
-
|
|
78
|
-
val type = fields.type.get(event) as? ScrollEventType ?: return null
|
|
79
|
-
val offsetY = readFloat(fields.offsetY, event) ?: return null
|
|
80
|
-
val velocityY = readFloat(fields.velocityY, event) ?: return null
|
|
81
|
-
Reading(event.viewTag, kindOf(type), offsetY, velocityY)
|
|
82
|
-
} catch (e: ReflectiveOperationException) {
|
|
83
|
-
null
|
|
84
|
-
} catch (e: IllegalArgumentException) {
|
|
85
|
-
null
|
|
86
|
-
}
|
|
57
|
+
/** One call of React Native's listener: the handlers listening now hear its reading, if it has one. */
|
|
58
|
+
fun hear(scrollView: ViewGroup?, type: ScrollEventType?, velocityY: Float) {
|
|
59
|
+
val reading = read(scrollView, type, velocityY) ?: return
|
|
60
|
+
listenings.toList().forEach { it.handler(reading) }
|
|
87
61
|
}
|
|
88
62
|
|
|
89
|
-
|
|
63
|
+
/**
|
|
64
|
+
* The reading of one call of React Native's listener: none for a horizontal scroll view, or when React Native gives
|
|
65
|
+
* no view or no kind. The offset is the view's `scrollY` and the speed React Native's own, in pixels, as the event
|
|
66
|
+
* sent to JavaScript carries them before it converts them to dp.
|
|
67
|
+
*/
|
|
68
|
+
fun read(scrollView: ViewGroup?, type: ScrollEventType?, velocityY: Float): Reading? {
|
|
69
|
+
if (scrollView == null || type == null || scrollView is HorizontalScrollView) return null
|
|
70
|
+
return Reading(scrollView.id, kindOf(type), scrollView.scrollY.toFloat(), velocityY)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** The kind of a reading for React Native's [type]. */
|
|
74
|
+
fun kindOf(type: ScrollEventType): Kind = when (type) {
|
|
90
75
|
ScrollEventType.BEGIN_DRAG -> Kind.BeginDrag
|
|
91
76
|
ScrollEventType.END_DRAG -> Kind.EndDrag
|
|
92
77
|
ScrollEventType.SCROLL -> Kind.Scroll
|
|
93
78
|
ScrollEventType.MOMENTUM_BEGIN -> Kind.MomentumBegin
|
|
94
79
|
ScrollEventType.MOMENTUM_END -> Kind.MomentumEnd
|
|
95
80
|
}
|
|
96
|
-
|
|
97
|
-
@Synchronized
|
|
98
|
-
private fun fields(): Fields? {
|
|
99
|
-
if (lookedUp) return fields
|
|
100
|
-
lookedUp = true
|
|
101
|
-
fields = try {
|
|
102
|
-
val owner = ScrollEvent::class.java
|
|
103
|
-
val type = declaredField(owner, TYPE_NAMES) { ScrollEventType::class.java.isAssignableFrom(it) }
|
|
104
|
-
val offsetY = declaredField(owner, OFFSET_NAMES, ::isNumeric)
|
|
105
|
-
val velocityY = declaredField(owner, VELOCITY_NAMES, ::isNumeric)
|
|
106
|
-
if (type != null && offsetY != null && velocityY != null) Fields(type, offsetY, velocityY) else null
|
|
107
|
-
} catch (e: ReflectiveOperationException) {
|
|
108
|
-
null
|
|
109
|
-
} catch (e: SecurityException) {
|
|
110
|
-
null
|
|
111
|
-
}
|
|
112
|
-
if (fields == null) {
|
|
113
|
-
Log.w(
|
|
114
|
-
NavigationLog.TAG,
|
|
115
|
-
"[ApexNavigation] React Native's scroll events changed: hideOnScroll and the large title " +
|
|
116
|
-
"don't follow the scroll",
|
|
117
|
-
)
|
|
118
|
-
}
|
|
119
|
-
return fields
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// The names of the fields: React Native 0.87 first, then the earlier ones with the `m` prefix.
|
|
123
|
-
private val TYPE_NAMES = listOf("scrollEventType", "mScrollEventType")
|
|
124
|
-
private val OFFSET_NAMES = listOf("scrollY", "mScrollY")
|
|
125
|
-
private val VELOCITY_NAMES = listOf("yVelocity", "mYVelocity")
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* The first of the fields [names] that [owner] declares with a type [accepted] takes, made readable; none when no
|
|
129
|
-
* name fits. React Native renamed them and changed their types between versions.
|
|
130
|
-
*/
|
|
131
|
-
fun declaredField(owner: Class<*>, names: List<String>, accepted: (Class<*>) -> Boolean): Field? {
|
|
132
|
-
for (name in names) {
|
|
133
|
-
val field = try {
|
|
134
|
-
owner.getDeclaredField(name)
|
|
135
|
-
} catch (e: NoSuchFieldException) {
|
|
136
|
-
continue
|
|
137
|
-
}
|
|
138
|
-
if (!accepted(field.type)) continue
|
|
139
|
-
field.isAccessible = true
|
|
140
|
-
return field
|
|
141
|
-
}
|
|
142
|
-
return null
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/** A number of any kind: a primitive one (`int`, `float`, `double`, …) or a boxed one. */
|
|
146
|
-
fun isNumeric(type: Class<*>): Boolean =
|
|
147
|
-
type in PRIMITIVE_NUMBERS || Number::class.java.isAssignableFrom(type)
|
|
148
|
-
|
|
149
|
-
/** [field] of [target] as a float, whatever numeric type it has; none when it holds no number. */
|
|
150
|
-
fun readFloat(field: Field, target: Any): Float? = (field.get(target) as? Number)?.toFloat()
|
|
151
|
-
|
|
152
|
-
private val PRIMITIVE_NUMBERS = setOf<Class<*>?>(
|
|
153
|
-
Byte::class.javaPrimitiveType,
|
|
154
|
-
Short::class.javaPrimitiveType,
|
|
155
|
-
Int::class.javaPrimitiveType,
|
|
156
|
-
Long::class.javaPrimitiveType,
|
|
157
|
-
Float::class.javaPrimitiveType,
|
|
158
|
-
Double::class.javaPrimitiveType,
|
|
159
|
-
)
|
|
160
81
|
}
|
|
@@ -10,7 +10,7 @@ import com.facebook.react.uimanager.StateWrapper
|
|
|
10
10
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
11
11
|
import com.facebook.react.uimanager.UIManagerHelper
|
|
12
12
|
import com.facebook.react.views.view.ReactViewGroup
|
|
13
|
-
import dev.apexnative.navigation.react.
|
|
13
|
+
import dev.apexnative.navigation.react.ScreenEnvironment
|
|
14
14
|
import dev.apexnative.navigation.viewcontrollers.component.ScreenReadiness
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -20,7 +20,7 @@ import dev.apexnative.navigation.viewcontrollers.component.ScreenReadiness
|
|
|
20
20
|
@SuppressLint("ViewConstructor")
|
|
21
21
|
class SafeAreaView(reactContext: ThemedReactContext) : ReactViewGroup(reactContext) {
|
|
22
22
|
private var stopListening: (() -> Unit)? = null
|
|
23
|
-
private var applied:
|
|
23
|
+
private var applied: ScreenEnvironment.Insets? = null
|
|
24
24
|
private var readySent = false
|
|
25
25
|
private var contentSignalled = false
|
|
26
26
|
|
|
@@ -48,7 +48,7 @@ class SafeAreaView(reactContext: ThemedReactContext) : ReactViewGroup(reactConte
|
|
|
48
48
|
stopListening?.invoke()
|
|
49
49
|
field = value
|
|
50
50
|
applied = null
|
|
51
|
-
stopListening = value?.let { id ->
|
|
51
|
+
stopListening = value?.let { id -> ScreenEnvironment.listenToPadding(id) { applyPadding() } }
|
|
52
52
|
// The screen has a safe area view: it counts as rendered once this view says so.
|
|
53
53
|
value?.let(ScreenReadiness::wrapped)
|
|
54
54
|
applyPadding()
|
|
@@ -56,7 +56,7 @@ class SafeAreaView(reactContext: ThemedReactContext) : ReactViewGroup(reactConte
|
|
|
56
56
|
|
|
57
57
|
private fun applyPadding() {
|
|
58
58
|
val state = stateWrapper ?: return
|
|
59
|
-
val padding = screenId?.let(
|
|
59
|
+
val padding = screenId?.let(ScreenEnvironment::padding) ?: return
|
|
60
60
|
if (padding == applied) return
|
|
61
61
|
applied = padding
|
|
62
62
|
state.updateState(
|
|
@@ -69,9 +69,9 @@ class SafeAreaView(reactContext: ThemedReactContext) : ReactViewGroup(reactConte
|
|
|
69
69
|
)
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
private fun paddingOf(data: ReadableMap):
|
|
72
|
+
private fun paddingOf(data: ReadableMap): ScreenEnvironment.Insets? {
|
|
73
73
|
if (!data.hasKey("top")) return null
|
|
74
|
-
return
|
|
74
|
+
return ScreenEnvironment.Insets(data.getDouble("top"), data.getDouble("right"), data.getDouble("bottom"),
|
|
75
75
|
data.getDouble("left"))
|
|
76
76
|
}
|
|
77
77
|
|
|
@@ -26,16 +26,3 @@ val View.borderRadius: Float
|
|
|
26
26
|
*/
|
|
27
27
|
internal fun resolveBorderRadius(radius: LengthPercentage, width: Int, density: Float): Float =
|
|
28
28
|
(radius.resolve(width / density) * density).coerceAtLeast(0f)
|
|
29
|
-
|
|
30
|
-
/** The scale the parents of [view] give it, along x and along y: a view in a scaled parent shows smaller. */
|
|
31
|
-
fun computeInheritedScale(view: View): Pair<Float, Float> {
|
|
32
|
-
var scaleX = 1f
|
|
33
|
-
var scaleY = 1f
|
|
34
|
-
var parent = view.parent as? View
|
|
35
|
-
while (parent != null) {
|
|
36
|
-
scaleX *= parent.scaleX
|
|
37
|
-
scaleY *= parent.scaleY
|
|
38
|
-
parent = parent.parent as? View
|
|
39
|
-
}
|
|
40
|
-
return scaleX to scaleY
|
|
41
|
-
}
|
|
@@ -9,7 +9,7 @@ import androidx.window.layout.WindowMetricsCalculator
|
|
|
9
9
|
import dev.apexnative.navigation.NavigationActivity
|
|
10
10
|
import dev.apexnative.navigation.options.DefaultOptions
|
|
11
11
|
import dev.apexnative.navigation.options.Options
|
|
12
|
-
import dev.apexnative.navigation.react.
|
|
12
|
+
import dev.apexnative.navigation.react.ScreenEnvironment
|
|
13
13
|
import dev.apexnative.navigation.react.events.EventEmitter
|
|
14
14
|
import dev.apexnative.navigation.utils.SystemUiUtils
|
|
15
15
|
import dev.apexnative.navigation.utils.WindowFolds
|
|
@@ -146,12 +146,14 @@ class ArrangementController internal constructor(
|
|
|
146
146
|
val overlay = plan.layout == ArrangementPlan.Layout.OVERLAY
|
|
147
147
|
val single = plan.layout == ArrangementPlan.Layout.SINGLE
|
|
148
148
|
if (primary is ComponentViewController) {
|
|
149
|
-
val state =
|
|
150
|
-
|
|
149
|
+
val state = ScreenEnvironment.Arrangement(PRIMARY, layout, front = overlay, hidden = false)
|
|
150
|
+
val changes = ScreenEnvironment.changesOf(EventEmitter.of(primary.jsGeneration))
|
|
151
|
+
ScreenEnvironment.setArrangement(primary.id, state, notify, changes)
|
|
151
152
|
}
|
|
152
153
|
if (secondary is ComponentViewController) {
|
|
153
|
-
val state =
|
|
154
|
-
|
|
154
|
+
val state = ScreenEnvironment.Arrangement(SECONDARY, layout, front = false, hidden = single)
|
|
155
|
+
val changes = ScreenEnvironment.changesOf(EventEmitter.of(secondary.jsGeneration))
|
|
156
|
+
ScreenEnvironment.setArrangement(secondary.id, state, notify, changes)
|
|
155
157
|
}
|
|
156
158
|
}
|
|
157
159
|
|
|
@@ -244,9 +246,9 @@ class ArrangementController internal constructor(
|
|
|
244
246
|
// The stack keeps the bar's title, buttons and search by the view of its screen on top: they go with the pair.
|
|
245
247
|
if (!isDestroyed && peekView() != null) performOnParentController { it.onChildDestroyed(this) }
|
|
246
248
|
folds?.removeListener(onFoldsChanged)
|
|
247
|
-
ArrangementStates.remove(primary.id)
|
|
248
|
-
ArrangementStates.remove(secondary.id)
|
|
249
249
|
shownChildren.containerDisappeared()
|
|
250
|
+
// The screens go with the pair: each clears its own environment, its arrangement too, unless its id is
|
|
251
|
+
// another's now.
|
|
250
252
|
super.destroy()
|
|
251
253
|
}
|
|
252
254
|
|
|
@@ -2,6 +2,7 @@ package dev.apexnative.navigation.viewcontrollers.bottomtabs
|
|
|
2
2
|
|
|
3
3
|
import android.view.View
|
|
4
4
|
import dev.apexnative.navigation.viewcontrollers.viewcontroller.ScrollEventListener
|
|
5
|
+
import dev.apexnative.navigation.views.bottomtabs.BottomTabsLayout
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* bottomTabs.hideOnScroll: the tabs hide after a drag that scrolls the screen's content down and come back after one
|
|
@@ -43,9 +44,10 @@ class BottomTabsCollapseBehavior(private val container: () -> View?) :
|
|
|
43
44
|
override fun onHide() {
|
|
44
45
|
val view = container() ?: return
|
|
45
46
|
hiddenByScroll = true
|
|
46
|
-
// Out of the screen with what is under it
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
// Out of the screen with what is under it (a compact bar floats over its bottom margin and the navigation bar)
|
|
48
|
+
// and the custom row that rises above it.
|
|
49
|
+
val distance = (view.parent as? BottomTabsLayout)?.hideDistance ?: view.height
|
|
50
|
+
view.animate().translationY(distance.toFloat()).setDuration(DURATION).start()
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
private companion object {
|
|
@@ -34,6 +34,7 @@ import dev.apexnative.navigation.viewcontrollers.stack.StackController
|
|
|
34
34
|
import dev.apexnative.navigation.viewcontrollers.stack.fab.FabPlacement
|
|
35
35
|
import dev.apexnative.navigation.viewcontrollers.viewcontroller.Presenter
|
|
36
36
|
import dev.apexnative.navigation.viewcontrollers.viewcontroller.ViewController
|
|
37
|
+
import dev.apexnative.navigation.views.animations.AnimationLayer
|
|
37
38
|
import dev.apexnative.navigation.views.animations.FadeThrough
|
|
38
39
|
import dev.apexnative.navigation.views.bottomtabs.BottomTabItem
|
|
39
40
|
import dev.apexnative.navigation.views.bottomtabs.BottomTabsLayout
|
|
@@ -199,7 +200,9 @@ class BottomTabsController(
|
|
|
199
200
|
}
|
|
200
201
|
})
|
|
201
202
|
val set = AnimatorSet()
|
|
202
|
-
set
|
|
203
|
+
// Each tab fades whole through a hardware layer; a switch ended early ([set]'s end) gives them back too.
|
|
204
|
+
val enter = FadeThrough.enter(coming)
|
|
205
|
+
set.playTogether(AnimationLayer.during(going, exit), AnimationLayer.during(coming, enter))
|
|
203
206
|
set.addListener(object : AnimatorListenerAdapter() {
|
|
204
207
|
override fun onAnimationEnd(animation: Animator) {
|
|
205
208
|
set.removeAllListeners()
|
|
@@ -350,8 +353,8 @@ class BottomTabsController(
|
|
|
350
353
|
|
|
351
354
|
/**
|
|
352
355
|
* Selects the tab at [index]: the bar shows it at once; on screen the tab it left fades through to it (Material's
|
|
353
|
-
* fade through)
|
|
354
|
-
* history of back follows.
|
|
356
|
+
* fade through); off screen, or when the new tab's `bottomTabs.switchAnimation` is `'none'`, one container becomes
|
|
357
|
+
* invisible and the other visible at once; then [switched]. The history of back follows.
|
|
355
358
|
*/
|
|
356
359
|
private fun select(index: Int, how: How) {
|
|
357
360
|
if (index == selectedIndex || index !in tabs.indices || isDestroyed) return
|
|
@@ -381,7 +384,7 @@ class BottomTabsController(
|
|
|
381
384
|
// it must act in this tab, not in the one still fading out.
|
|
382
385
|
if (how != How.Press) AppearanceDispatcher.reportState()
|
|
383
386
|
if (views == null) return switched(index, previous, how, onScreen, hasViews = false)
|
|
384
|
-
endSwitch = views.switch(shown, left, animated = onScreen) {
|
|
387
|
+
endSwitch = views.switch(shown, left, animated = onScreen && fadesThrough(shown)) {
|
|
385
388
|
endSwitch = null
|
|
386
389
|
if (!isDestroyed) switched(index, previous, how, onScreen, hasViews = true)
|
|
387
390
|
}
|
|
@@ -427,6 +430,11 @@ class BottomTabsController(
|
|
|
427
430
|
if (screen == null) finish() else AppearanceDispatcher.afterDidAppear(screen, finish)
|
|
428
431
|
}
|
|
429
432
|
|
|
433
|
+
// `bottomTabs.switchAnimation` of the tab that shows, its shown screen's over its own, the tabs' and the defaults:
|
|
434
|
+
// `'platform'`, the default, fades through; `'none'` switches at once.
|
|
435
|
+
private fun fadesThrough(tab: ViewController<*>): Boolean =
|
|
436
|
+
tab.resolveShownOptions(DefaultOptions.current).bottomTabs.switchAnimation.get(null) != "none"
|
|
437
|
+
|
|
430
438
|
// A switch still running ends at once and a press's `ANN.BottomTabSelected` still waiting goes, before anything
|
|
431
439
|
// newer on these tabs.
|
|
432
440
|
private fun flushPendingSelected() {
|
|
@@ -548,9 +556,6 @@ class BottomTabsController(
|
|
|
548
556
|
return TabsRoom.tabsTop(room(), screenOptions(screen), bottom)
|
|
549
557
|
}
|
|
550
558
|
|
|
551
|
-
override fun leavesBottomEdgeFree(child: ViewController<*>, screen: ViewController<*>): Boolean =
|
|
552
|
-
TabsRoom.leavesBottomEdgeFree(screenOptions(screen)) || super.leavesBottomEdgeFree(child, screen)
|
|
553
|
-
|
|
554
559
|
override fun tabsAtRestOf(child: ViewController<*>, screen: ViewController<*>): FabPlacement.Tabs? {
|
|
555
560
|
if (peekView() == null) return super.tabsAtRestOf(child, screen)
|
|
556
561
|
return TabsRoom.fabTabs(room(), screenOptions(screen))
|
|
@@ -631,7 +636,6 @@ class BottomTabsController(
|
|
|
631
636
|
layout.row = row
|
|
632
637
|
layout.bar.customRowOptionsListener = { applyRow(row, it) }
|
|
633
638
|
layout.bar.customRowOptions = resolveShownOptions(DefaultOptions.current).bottomTabs.customRow.copy()
|
|
634
|
-
CustomTabRowPlacer.track(activity)
|
|
635
639
|
}
|
|
636
640
|
|
|
637
641
|
private fun applyRow(row: CustomTabRow, options: BottomTabsCustomRowOptions) {
|
|
@@ -683,7 +687,7 @@ class BottomTabsController(
|
|
|
683
687
|
|
|
684
688
|
/**
|
|
685
689
|
* Destroys the tabs once: every tab and the registry first, then the cells (each stops its root once), the row out
|
|
686
|
-
* of the
|
|
690
|
+
* of the tabs' view, the scroll it followed. A press's event still waiting goes with them.
|
|
687
691
|
*/
|
|
688
692
|
override fun destroy() {
|
|
689
693
|
if (isDestroyed) return
|
|
@@ -694,7 +698,6 @@ class BottomTabsController(
|
|
|
694
698
|
endSwitch?.invoke()
|
|
695
699
|
peekView()?.let { layout ->
|
|
696
700
|
layout.bar.destroyCustomItemViews()
|
|
697
|
-
layout.row?.let(CustomTabRowPlacer::forget)
|
|
698
701
|
layout.row = null
|
|
699
702
|
}
|
|
700
703
|
cells = emptyList()
|
|
@@ -49,6 +49,22 @@ internal object CustomTabRowGeometry {
|
|
|
49
49
|
content = content,
|
|
50
50
|
)
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
* The row over the bar as the tabs' view lays it out: the bar's [barLeft], [barRight] and [barBottom] in that
|
|
54
|
+
* view, whose height is [tabsHeight] and whose bottom [navigationBar] pixels lie under the navigation bar — the
|
|
55
|
+
* row's bottom never goes over them —, with [margin], [content] and [bottomMargin] as [place] takes them.
|
|
56
|
+
*/
|
|
57
|
+
fun inTabs(
|
|
58
|
+
barLeft: Int,
|
|
59
|
+
barRight: Int,
|
|
60
|
+
barBottom: Int,
|
|
61
|
+
tabsHeight: Int,
|
|
62
|
+
navigationBar: Int,
|
|
63
|
+
margin: Int,
|
|
64
|
+
content: Int,
|
|
65
|
+
bottomMargin: Int,
|
|
66
|
+
): Place = place(barLeft, barRight, minOf(barBottom, tabsHeight - navigationBar), margin, content, bottomMargin)
|
|
67
|
+
|
|
52
68
|
/**
|
|
53
69
|
* The part of the row's frame at [place] below its visible part, in pixels: `bottomMargin`. The row draws and takes
|
|
54
70
|
* touches only above it.
|
|
@@ -61,6 +77,12 @@ internal object CustomTabRowGeometry {
|
|
|
61
77
|
*/
|
|
62
78
|
fun needsPlacing(last: Place?, next: Place): Boolean = last != next
|
|
63
79
|
|
|
80
|
+
/**
|
|
81
|
+
* The row's own lift over its [elevation] while the bar's container stands at [barZ]: as high as the container, so
|
|
82
|
+
* the row, after it in the tabs' view, draws over it and takes the touches first even under a raised bar.
|
|
83
|
+
*/
|
|
84
|
+
fun lift(barZ: Float, elevation: Float): Float = (barZ - elevation).coerceAtLeast(0f)
|
|
85
|
+
|
|
64
86
|
/**
|
|
65
87
|
* The row's background: `customRow.backgroundColor` ([color]; `null` and `'NoColor'` are none); without it
|
|
66
88
|
* `backgroundEffect: 'none'` ([effect]) is clear, any other effect or none the theme's `android:colorBackground`
|
package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/TabsRoom.kt
CHANGED
|
@@ -58,11 +58,6 @@ internal object TabsRoom {
|
|
|
58
58
|
fun tabsTop(bar: Bar, screen: Screen, tabsBottom: Int): Int? =
|
|
59
59
|
if (screen.hidden) null else tabsBottom - bar.reach - bar.rowOvershoot
|
|
60
60
|
|
|
61
|
-
/**
|
|
62
|
-
* The safe area of the screen leaves its bottom edge free: the bar hides on scroll and shows for it.
|
|
63
|
-
*/
|
|
64
|
-
fun leavesBottomEdgeFree(screen: Screen): Boolean = screen.hideOnScroll && !screen.hidden
|
|
65
|
-
|
|
66
61
|
/**
|
|
67
62
|
* The bar at rest under a stack's FAB: also when the screen is under it (`drawBehind`, `hideOnScroll`); none when
|
|
68
63
|
* the screen hides it.
|
|
@@ -8,9 +8,8 @@ import androidx.core.view.ViewCompat
|
|
|
8
8
|
import androidx.core.view.WindowInsetsCompat
|
|
9
9
|
import dev.apexnative.navigation.options.DefaultOptions
|
|
10
10
|
import dev.apexnative.navigation.options.Options
|
|
11
|
-
import dev.apexnative.navigation.react.ArrangementStates
|
|
12
11
|
import dev.apexnative.navigation.react.ReactView
|
|
13
|
-
import dev.apexnative.navigation.react.
|
|
12
|
+
import dev.apexnative.navigation.react.ScreenEnvironment
|
|
14
13
|
import dev.apexnative.navigation.react.ScreenRegions
|
|
15
14
|
import dev.apexnative.navigation.react.events.AppearanceDispatcher
|
|
16
15
|
import dev.apexnative.navigation.react.events.ComponentType
|
|
@@ -157,7 +156,7 @@ class ComponentViewController(
|
|
|
157
156
|
shown = layout.isShown || prepared,
|
|
158
157
|
layoutPending = !prepared && isLayoutPending(content),
|
|
159
158
|
topOfStack = parent?.isTopOfStack(this) ?: true,
|
|
160
|
-
hasSafeArea =
|
|
159
|
+
hasSafeArea = ScreenEnvironment.padding(id) != null,
|
|
161
160
|
)
|
|
162
161
|
if (!measures) return
|
|
163
162
|
val windowInsets = ViewCompat.getRootWindowInsets(content) ?: return
|
|
@@ -176,11 +175,13 @@ class ComponentViewController(
|
|
|
176
175
|
val edges = ScreenSafeArea.paddedEdges(
|
|
177
176
|
safeAreaEdges = resolveCurrentOptions(DefaultOptions.current).layout.safeAreaEdges,
|
|
178
177
|
topFree = parent?.leavesTopEdgeFree(this) == true,
|
|
179
|
-
|
|
178
|
+
largeTitle = parent?.showsLargeTitleOver(this) == true,
|
|
180
179
|
)
|
|
181
180
|
// The events of the screen go to the JavaScript it was made for, and to no other.
|
|
182
181
|
val emitter = EventEmitter.of(jsGeneration)
|
|
183
|
-
|
|
182
|
+
val padding = ScreenSafeArea.padding(insets, edges)
|
|
183
|
+
val uncovered = ScreenSafeArea.uncovered(insets, padding)
|
|
184
|
+
ScreenEnvironment.setInsets(id, uncovered, padding, ScreenEnvironment.changesOf(emitter))
|
|
184
185
|
val area = Rect(place.left, place.top, place.right, place.bottom)
|
|
185
186
|
ScreenRegions.update(activity, id, area, windowInsets, emitter)
|
|
186
187
|
}
|
|
@@ -245,9 +246,7 @@ class ComponentViewController(
|
|
|
245
246
|
}
|
|
246
247
|
setParentController(null)
|
|
247
248
|
if (isIdTakenByAnother) return
|
|
248
|
-
|
|
249
|
-
ScreenRegions.remove(id)
|
|
250
|
-
ArrangementStates.remove(id)
|
|
249
|
+
ScreenEnvironment.remove(id)
|
|
251
250
|
ScreenReadiness.remove(id)
|
|
252
251
|
}
|
|
253
252
|
|
package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ScreenSafeArea.kt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
package dev.apexnative.navigation.viewcontrollers.component
|
|
2
2
|
|
|
3
|
-
import dev.apexnative.navigation.react.
|
|
3
|
+
import dev.apexnative.navigation.react.ScreenEnvironment.Insets
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* The arithmetic of a screen's room, safe area and background on Android, without views: the screen nodes read the
|
|
@@ -19,6 +19,7 @@ internal object ScreenSafeArea {
|
|
|
19
19
|
const val LEFT = "left"
|
|
20
20
|
|
|
21
21
|
private val ALL_EDGES = setOf(TOP, RIGHT, BOTTOM, LEFT)
|
|
22
|
+
private val DEFAULT_EDGES = setOf(TOP, RIGHT, LEFT)
|
|
22
23
|
private const val OPAQUE = 0xFF
|
|
23
24
|
|
|
24
25
|
/**
|
|
@@ -57,10 +58,10 @@ internal object ScreenSafeArea {
|
|
|
57
58
|
headerBottom: Int?,
|
|
58
59
|
tabsTop: Int?,
|
|
59
60
|
density: Float,
|
|
60
|
-
):
|
|
61
|
+
): Insets {
|
|
61
62
|
val coveredAbove = maxOf(system.top, headerBottom ?: 0)
|
|
62
63
|
val coveredBelow = minOf(windowHeight - system.bottom, tabsTop ?: windowHeight)
|
|
63
|
-
return
|
|
64
|
+
return Insets(
|
|
64
65
|
top = dp((coveredAbove - root.top).coerceAtLeast(0), density),
|
|
65
66
|
right = dp((root.right - (windowWidth - system.right)).coerceAtLeast(0), density),
|
|
66
67
|
bottom = dp((root.bottom - coveredBelow).coerceAtLeast(0), density),
|
|
@@ -69,26 +70,43 @@ internal object ScreenSafeArea {
|
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
/**
|
|
72
|
-
* The edges the screen's safe area view keeps its content off: exactly [safeAreaEdges] when the options give them
|
|
73
|
-
*
|
|
74
|
-
*
|
|
73
|
+
* The edges the screen's safe area view keeps its content off: exactly [safeAreaEdges] when the options give them,
|
|
74
|
+
* but the top under a large title ([largeTitle]); else the top and the sides, but the top when the stack's header
|
|
75
|
+
* hides on scroll or a large title shows ([topFree]). The bottom is free by default: the content goes down to the
|
|
76
|
+
* bottom, and a list keeps its last rows clear of the navigation bar with `useSafeAreaInsets()`.
|
|
75
77
|
*/
|
|
76
|
-
fun paddedEdges(safeAreaEdges: List<String>?, topFree: Boolean,
|
|
77
|
-
if (safeAreaEdges != null)
|
|
78
|
-
|
|
78
|
+
fun paddedEdges(safeAreaEdges: List<String>?, topFree: Boolean, largeTitle: Boolean): Set<String> {
|
|
79
|
+
if (safeAreaEdges != null) {
|
|
80
|
+
val edges = safeAreaEdges.filterTo(LinkedHashSet()) { it in ALL_EDGES }
|
|
81
|
+
// Under a large title the list keeps its first rows clear of the bar itself; a padded top would follow the
|
|
82
|
+
// header as it collapses and move the list with it.
|
|
83
|
+
if (largeTitle) edges -= TOP
|
|
84
|
+
return edges
|
|
85
|
+
}
|
|
86
|
+
val edges = LinkedHashSet(DEFAULT_EDGES)
|
|
79
87
|
if (topFree) edges -= TOP
|
|
80
|
-
if (bottomFree) edges -= BOTTOM
|
|
81
88
|
return edges
|
|
82
89
|
}
|
|
83
90
|
|
|
84
91
|
/** The padding of the safe area view: the [insets] on the [edges] it keeps, 0 on the others. */
|
|
85
|
-
fun padding(insets:
|
|
92
|
+
fun padding(insets: Insets, edges: Set<String>): Insets = Insets(
|
|
86
93
|
top = if (TOP in edges) insets.top else 0.0,
|
|
87
94
|
right = if (RIGHT in edges) insets.right else 0.0,
|
|
88
95
|
bottom = if (BOTTOM in edges) insets.bottom else 0.0,
|
|
89
96
|
left = if (LEFT in edges) insets.left else 0.0,
|
|
90
97
|
)
|
|
91
98
|
|
|
99
|
+
/**
|
|
100
|
+
* The insets JavaScript gets (`useSafeAreaInsets`): what of the [insets] still covers the content once the safe
|
|
101
|
+
* area view keeps it off its [padding], 0 on a padded edge.
|
|
102
|
+
*/
|
|
103
|
+
fun uncovered(insets: Insets, padding: Insets) = Insets(
|
|
104
|
+
top = (insets.top - padding.top).coerceAtLeast(0.0),
|
|
105
|
+
right = (insets.right - padding.right).coerceAtLeast(0.0),
|
|
106
|
+
bottom = (insets.bottom - padding.bottom).coerceAtLeast(0.0),
|
|
107
|
+
left = (insets.left - padding.left).coerceAtLeast(0.0),
|
|
108
|
+
)
|
|
109
|
+
|
|
92
110
|
/**
|
|
93
111
|
* The inner padding of a screen's view: the side system bars ([systemBars]) at the sides; at the bottom the larger
|
|
94
112
|
* of the navigation bar (none when the screen draws under it) and the keyboard ([imeBottom]; none with
|
|
@@ -10,11 +10,9 @@ import dev.apexnative.navigation.layout.LayoutException
|
|
|
10
10
|
import dev.apexnative.navigation.nativescreen.NativeScreenContext
|
|
11
11
|
import dev.apexnative.navigation.options.DefaultOptions
|
|
12
12
|
import dev.apexnative.navigation.options.Options
|
|
13
|
-
import dev.apexnative.navigation.react.ArrangementStates
|
|
14
13
|
import dev.apexnative.navigation.react.CommandScope
|
|
15
14
|
import dev.apexnative.navigation.react.ErrorTexts
|
|
16
|
-
import dev.apexnative.navigation.react.
|
|
17
|
-
import dev.apexnative.navigation.react.ScreenRegions
|
|
15
|
+
import dev.apexnative.navigation.react.ScreenEnvironment
|
|
18
16
|
import dev.apexnative.navigation.react.events.AppearanceDispatcher
|
|
19
17
|
import dev.apexnative.navigation.react.events.ComponentType
|
|
20
18
|
import dev.apexnative.navigation.react.events.ScreenAppearance
|
|
@@ -170,9 +168,7 @@ class ExternalComponentViewController internal constructor(
|
|
|
170
168
|
peekView()?.let { (it.parent as? ViewGroup)?.removeView(it) }
|
|
171
169
|
setParentController(null)
|
|
172
170
|
if (isIdTakenByAnother) return
|
|
173
|
-
|
|
174
|
-
ScreenRegions.remove(id)
|
|
175
|
-
ArrangementStates.remove(id)
|
|
171
|
+
ScreenEnvironment.remove(id)
|
|
176
172
|
ScreenReadiness.remove(id)
|
|
177
173
|
if (!notMade) AppearanceDispatcher.nativeScreenReleased(this)
|
|
178
174
|
}
|