@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.
Files changed (182) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +1 -1
  3. package/android/CLAUDE.md +14 -12
  4. package/android/CORE.md +9 -6
  5. package/android/src/main/java/dev/apexnative/navigation/NavigationActivity.kt +36 -5
  6. package/android/src/main/java/dev/apexnative/navigation/options/BottomTabsOptions.kt +13 -8
  7. package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +3 -3
  8. package/android/src/main/java/dev/apexnative/navigation/react/ReloadCoordinator.kt +0 -2
  9. package/android/src/main/java/dev/apexnative/navigation/react/ScreenEnvironment.kt +177 -0
  10. package/android/src/main/java/dev/apexnative/navigation/react/ScreenRegions.kt +12 -49
  11. package/android/src/main/java/dev/apexnative/navigation/react/ScrollEvents.kt +49 -128
  12. package/android/src/main/java/dev/apexnative/navigation/safearea/SafeAreaView.kt +6 -6
  13. package/android/src/main/java/dev/apexnative/navigation/utils/ViewExtensions.kt +0 -13
  14. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/arrangement/ArrangementController.kt +9 -7
  15. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsCollapseBehavior.kt +5 -3
  16. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsController.kt +13 -10
  17. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/CustomTabRowGeometry.kt +22 -0
  18. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/TabsRoom.kt +0 -5
  19. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ComponentViewController.kt +7 -8
  20. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ScreenSafeArea.kt +29 -11
  21. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/externalcomponent/ExternalComponentViewController.kt +2 -6
  22. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalCoordinator.kt +20 -2
  23. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalSheet.kt +119 -8
  24. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +7 -0
  25. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/SheetBack.kt +42 -0
  26. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt +4 -0
  27. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/ParentController.kt +3 -6
  28. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/PreparedFrame.kt +3 -3
  29. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackCommands.kt +2 -2
  30. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +4 -0
  31. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +3 -0
  32. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/viewcontroller/ScrollEventListener.kt +6 -9
  33. package/android/src/main/java/dev/apexnative/navigation/views/animations/AnimationLayer.kt +73 -0
  34. package/android/src/main/java/dev/apexnative/navigation/views/animations/ValueInterpolation.kt +14 -0
  35. package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/BottomTabsLayout.kt +61 -2
  36. package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/CustomTabRow.kt +72 -10
  37. package/android/src/main/java/dev/apexnative/navigation/views/element/ElementTransitions.kt +9 -2
  38. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ElementPictures.kt +11 -0
  39. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/FlightGeometry.kt +318 -25
  40. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMatrixAnimator.kt +18 -20
  41. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PlainViewAnimator.kt +17 -25
  42. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ReactImageAnimator.kt +15 -22
  43. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/SharedAnimatorFactory.kt +12 -9
  44. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/TextChangeAnimator.kt +84 -35
  45. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewChangeAnimator.kt +49 -40
  46. package/docs-md/architecture/transitions.md +4 -2
  47. package/docs-md/containers/modals.md +2 -0
  48. package/docs-md/containers/overlays.md +1 -1
  49. package/docs-md/containers/side-menu.md +1 -1
  50. package/docs-md/containers/tabs.md +2 -0
  51. package/docs-md/flows/bottom-sheets.md +1 -1
  52. package/docs-md/fundamentals/starting-the-app.md +1 -1
  53. package/docs-md/help/glossary.md +2 -2
  54. package/docs-md/help/troubleshooting.md +21 -0
  55. package/docs-md/index.json +12 -0
  56. package/docs-md/migration/from-expo-router.md +1 -1
  57. package/docs-md/migration/from-react-native-navigation.md +1 -1
  58. package/docs-md/migration/from-react-navigation.md +2 -2
  59. package/docs-md/screen-ui/back-and-gestures.md +14 -0
  60. package/docs-md/screen-ui/bar-buttons.md +4 -4
  61. package/docs-md/screen-ui/search.md +1 -1
  62. package/docs-md/screen-ui/system-ui.md +16 -11
  63. package/docs-md/screen-ui/toolbar-and-fab.md +2 -2
  64. package/docs-md/screen-ui/top-bar.md +8 -4
  65. package/ios/Animations/ANNFrameDriver.mm +2 -0
  66. package/ios/Animations/Elements/ANNSharedElementFlight.mm +70 -4
  67. package/ios/Animations/Interpolators/ANNInterpolate.h +2 -30
  68. package/ios/Animations/Interpolators/ANNTransformParts.h +48 -0
  69. package/ios/Animations/Interpolators/ANNTransformParts.mm +507 -0
  70. package/ios/Animations/Interpolators/ANNViewTracks.h +6 -4
  71. package/ios/Animations/Interpolators/ANNViewTracks.mm +49 -9
  72. package/ios/CLAUDE.md +17 -15
  73. package/ios/CORE.md +24 -20
  74. package/ios/Contract/ANNCommandRunner.h +8 -7
  75. package/ios/Contract/ANNCommandRunner.mm +21 -27
  76. package/ios/Contract/ANNLayoutNode.h +3 -3
  77. package/ios/Contract/ANNLayoutReader.h +3 -3
  78. package/ios/Core/ANNJavaScriptCalls.h +11 -12
  79. package/ios/Core/ANNJavaScriptCalls.mm +7 -16
  80. package/ios/Core/ANNReloadCoordinator.mm +4 -8
  81. package/ios/Core/ApexNavigation+Internal.h +1 -1
  82. package/ios/Layouts/Arrangement/ANNArrangementState.h +2 -17
  83. package/ios/Layouts/Arrangement/ANNArrangementState.mm +0 -38
  84. package/ios/Layouts/Arrangement/ANNArrangementViewController.mm +4 -4
  85. package/ios/Layouts/Screen/ANNComponentViewController.h +2 -2
  86. package/ios/Layouts/Screen/ANNComponentViewController.mm +9 -22
  87. package/ios/Layouts/Screen/ANNPreviewInteraction.h +7 -4
  88. package/ios/Layouts/Screen/ANNPreviewInteraction.mm +72 -10
  89. package/ios/Layouts/Screen/ANNScreenEnvironment.h +63 -0
  90. package/ios/Layouts/Screen/ANNScreenEnvironment.mm +181 -0
  91. package/ios/Layouts/Screen/ANNScreenRegions.h +3 -18
  92. package/ios/Layouts/Screen/ANNScreenRegions.mm +0 -38
  93. package/ios/Layouts/Screen/ANNScreenSafeArea.h +4 -9
  94. package/ios/Layouts/Screen/ANNScreenSafeArea.mm +14 -16
  95. package/ios/Layouts/Stack/ANNPreviewPush.h +7 -6
  96. package/ios/Layouts/Stack/ANNPreviewPush.mm +57 -5
  97. package/ios/Layouts/Tabs/ANNBottomTabsAccessoryView.h +12 -1
  98. package/ios/Layouts/Tabs/ANNBottomTabsAccessoryView.mm +23 -0
  99. package/ios/Layouts/Tabs/ANNBottomTabsController.mm +2 -1
  100. package/ios/Layouts/Tabs/ANNBottomTabsCustomRow.mm +8 -3
  101. package/ios/Layouts/Tabs/ANNBottomTabsPresenter.h +4 -0
  102. package/ios/Layouts/Tabs/ANNBottomTabsPresenter.mm +123 -36
  103. package/ios/Layouts/Tabs/ANNTabsDesign.h +6 -5
  104. package/ios/Layouts/Tabs/ANNTabsDesign.mm +9 -1
  105. package/ios/Layouts/Tabs/UITabBar+utils.h +5 -5
  106. package/ios/Layouts/Tabs/UITabBar+utils.mm +32 -13
  107. package/ios/Options/ANNBottomTabsOptions.h +2 -0
  108. package/ios/Options/ANNBottomTabsOptions.mm +1 -0
  109. package/ios/Options/ANNOptions.h +18 -0
  110. package/ios/Options/ANNOptions.mm +112 -1
  111. package/ios/Options/ANNOptionsReader.mm +110 -0
  112. package/ios/Options/Params/ANNParam.h +15 -0
  113. package/ios/Options/Params/ANNParam.mm +61 -0
  114. package/ios/Options/Params/Color.h +3 -0
  115. package/ios/Options/Params/Color.mm +21 -0
  116. package/ios/Options/Params/Image.h +4 -0
  117. package/ios/Options/Params/Image.mm +20 -0
  118. package/ios/React/ANNReactView.mm +6 -25
  119. package/ios/React/ANNSafeAreaViewComponentView.mm +26 -15
  120. package/ios/React/ANNSurfaceHostingView.h +6 -0
  121. package/ios/React/ANNSurfaceHostingView.mm +116 -0
  122. package/ios/TopBar/ANNBackButtonPresenter.mm +6 -5
  123. package/ios/TopBar/ANNBarAppearance.h +2 -1
  124. package/ios/TopBar/ANNBarAppearance.mm +111 -30
  125. package/ios/TopBar/ANNBarBackgroundComponent.mm +16 -0
  126. package/ios/TopBar/ANNBarButtonComponentView.h +4 -4
  127. package/ios/TopBar/ANNBarButtonComponentView.mm +1 -1
  128. package/ios/TopBar/ANNBarButtons.h +5 -3
  129. package/ios/TopBar/ANNBarButtons.mm +186 -17
  130. package/ios/TopBar/ANNLargeTitlePresenter.mm +24 -4
  131. package/ios/TopBar/ANNMainList.h +3 -0
  132. package/ios/TopBar/ANNMainList.mm +4 -0
  133. package/ios/TopBar/ANNScrollEdgeEffects.h +22 -6
  134. package/ios/TopBar/ANNScrollEdgeEffects.mm +123 -16
  135. package/ios/TopBar/ANNSearchPresenter.mm +2 -2
  136. package/ios/TopBar/ANNSearchResultsViewController.mm +5 -5
  137. package/ios/TopBar/ANNTopBarPresenter.h +3 -2
  138. package/ios/TopBar/ANNTopBarPresenter.mm +95 -22
  139. package/ios/TurboModules/ANNTurboModule.mm +20 -16
  140. package/lib/module/ARCHITECTURE.md +8 -4
  141. package/lib/module/api/hooks/useSafeAreaInsets.js +4 -3
  142. package/lib/module/api/hooks/useSafeAreaInsets.js.map +1 -1
  143. package/lib/module/api/options/OptionsChain.js +2 -2
  144. package/lib/module/engine/commands.js +21 -1
  145. package/lib/module/engine/commands.js.map +1 -1
  146. package/lib/module/engine/screens.js +29 -4
  147. package/lib/module/engine/screens.js.map +1 -1
  148. package/lib/typescript/api/app/createNavigation.d.ts +3 -3
  149. package/lib/typescript/api/hooks/useSafeAreaInsets.d.ts +4 -3
  150. package/lib/typescript/api/hooks/useSafeAreaInsets.d.ts.map +1 -1
  151. package/lib/typescript/api/options/OptionsChain.d.ts +2 -2
  152. package/lib/typescript/engine/commands.d.ts.map +1 -1
  153. package/lib/typescript/engine/screens.d.ts +20 -6
  154. package/lib/typescript/engine/screens.d.ts.map +1 -1
  155. package/lib/typescript/interfaces/options/animations.d.ts +12 -7
  156. package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
  157. package/lib/typescript/interfaces/options/bottomTabs.d.ts +18 -5
  158. package/lib/typescript/interfaces/options/bottomTabs.d.ts.map +1 -1
  159. package/lib/typescript/interfaces/options/buttons.d.ts +8 -8
  160. package/lib/typescript/interfaces/options/system.d.ts +8 -5
  161. package/lib/typescript/interfaces/options/system.d.ts.map +1 -1
  162. package/lib/typescript/interfaces/options/toolbar.d.ts +2 -2
  163. package/lib/typescript/interfaces/options/topBar.d.ts +32 -29
  164. package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
  165. package/package.json +1 -1
  166. package/src/ARCHITECTURE.md +8 -4
  167. package/src/api/app/createNavigation.ts +3 -3
  168. package/src/api/hooks/useSafeAreaInsets.ts +4 -3
  169. package/src/api/options/OptionsChain.ts +2 -2
  170. package/src/engine/commands.ts +30 -6
  171. package/src/engine/screens.tsx +29 -8
  172. package/src/interfaces/options/animations.ts +12 -7
  173. package/src/interfaces/options/bottomTabs.ts +18 -5
  174. package/src/interfaces/options/buttons.ts +8 -8
  175. package/src/interfaces/options/system.ts +8 -5
  176. package/src/interfaces/options/toolbar.ts +2 -2
  177. package/src/interfaces/options/topBar.ts +32 -29
  178. package/android/src/main/java/dev/apexnative/navigation/react/ArrangementStates.kt +0 -42
  179. package/android/src/main/java/dev/apexnative/navigation/react/ScreenInsets.kt +0 -70
  180. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/CustomTabRowPlacer.kt +0 -223
  181. package/ios/Layouts/Screen/ANNScreenInsets.h +0 -30
  182. package/ios/Layouts/Screen/ANNScreenInsets.mm +0 -70
@@ -1,25 +1,17 @@
1
1
  package dev.apexnative.navigation.react
2
2
 
3
- import android.content.Context
4
- import android.util.Log
5
- import com.facebook.react.ReactApplication
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 lists, as the screens' scroll listeners hear them: from the event dispatcher of
20
- * the running React Native, every list of the app. React Native keeps the offset, the speed and the kind of a scroll
21
- * event in private fields: they are looked up once, with their types; if React Native renames them, one warning goes to
22
- * the log and the screens hear no scroll, with no error at each event.
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
- private class Fields(val type: Field, val offsetY: Field, val velocityY: Field)
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
- private var fields: Fields? = null
34
- private var lookedUp = false
26
+ // The screens' listenings, in the order they came.
27
+ private val listenings = mutableListOf<Listening>()
35
28
 
36
- // The screens' listeners that found no event dispatcher, held weakly until React Native has one. Main thread.
37
- private val waiting: MutableSet<ScrollEventListener> = Collections.newSetFromMap(WeakHashMap())
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
- /** [listener] found no event dispatcher: it is asked to listen again once React Native has one. */
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] on the main thread with each scroll event of the app's React Native, until the function returned
53
- * is called. None when React Native has no event dispatcher now (around a reload, the caller asks again later) or
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(context: Context, handler: (Reading) -> Unit): (() -> Unit)? {
57
- if (fields() == null) return null
58
- val reactContext = (context.applicationContext as? ReactApplication)?.reactHost?.currentReactContext
59
- ?: return null
60
- val dispatcher = try {
61
- UIManagerHelper.getEventDispatcher(reactContext)
62
- } catch (e: RuntimeException) {
63
- null
64
- } ?: return null
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
- private fun read(event: Event<*>): Reading? {
75
- if (event !is ScrollEvent) return null
76
- val fields = fields() ?: return null
77
- return try {
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
- private fun kindOf(type: ScrollEventType): Kind = when (type) {
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.ScreenInsets
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: ScreenInsets.Insets? = null
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 -> ScreenInsets.listenToPadding(id) { applyPadding() } }
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(ScreenInsets::padding) ?: return
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): ScreenInsets.Insets? {
72
+ private fun paddingOf(data: ReadableMap): ScreenEnvironment.Insets? {
73
73
  if (!data.hasKey("top")) return null
74
- return ScreenInsets.Insets(data.getDouble("top"), data.getDouble("right"), data.getDouble("bottom"),
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.ArrangementStates
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 = ArrangementStates.State(PRIMARY, layout, front = overlay, hidden = false)
150
- ArrangementStates.set(primary.id, state, notify, EventEmitter.of(primary.jsGeneration))
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 = ArrangementStates.State(SECONDARY, layout, front = false, hidden = single)
154
- ArrangementStates.set(secondary.id, state, notify, EventEmitter.of(secondary.jsGeneration))
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: a compact bar floats over its bottom margin and the navigation bar.
47
- val under = ((view.parent as? View)?.let { it.height - view.bottom } ?: 0).coerceAtLeast(0)
48
- view.animate().translationY((view.height + under).toFloat()).setDuration(DURATION).start()
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.playTogether(exit, FadeThrough.enter(coming))
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), off screen one container becomes invisible and the other visible at once; then [switched]. The
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 window, the scroll it followed. A press's event still waiting goes with them.
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`
@@ -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.ScreenInsets
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 = ScreenInsets.padding(id) != null,
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
- bottomFree = parent?.leavesBottomEdgeFree(this) == true,
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
- ScreenInsets.update(id, insets, ScreenSafeArea.padding(insets, edges), emitter)
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
- ScreenInsets.remove(id)
249
- ScreenRegions.remove(id)
250
- ArrangementStates.remove(id)
249
+ ScreenEnvironment.remove(id)
251
250
  ScreenReadiness.remove(id)
252
251
  }
253
252
 
@@ -1,6 +1,6 @@
1
1
  package dev.apexnative.navigation.viewcontrollers.component
2
2
 
3
- import dev.apexnative.navigation.react.ScreenInsets
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
- ): ScreenInsets.Insets {
61
+ ): Insets {
61
62
  val coveredAbove = maxOf(system.top, headerBottom ?: 0)
62
63
  val coveredBelow = minOf(windowHeight - system.bottom, tabsTop ?: windowHeight)
63
- return ScreenInsets.Insets(
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
- * else all four, but the top when the stack's header hides on scroll or a large title shows ([topFree]) and the
74
- * bottom when the tabs hide on scroll ([bottomFree]).
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, bottomFree: Boolean): Set<String> {
77
- if (safeAreaEdges != null) return safeAreaEdges.filterTo(LinkedHashSet()) { it in ALL_EDGES }
78
- val edges = LinkedHashSet(ALL_EDGES)
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: ScreenInsets.Insets, edges: Set<String>): ScreenInsets.Insets = ScreenInsets.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.ScreenInsets
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
- ScreenInsets.remove(id)
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
  }