react-native-screens 4.19.0-nightly-20251203-1746a584e → 4.19.0-nightly-20251205-910978b9a

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.
@@ -220,7 +220,7 @@ dependencies {
220
220
  implementation 'androidx.fragment:fragment-ktx:1.6.1'
221
221
  implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
222
222
  implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
223
- implementation 'com.google.android.material:material:1.12.0'
223
+ implementation 'com.google.android.material:material:1.13.0'
224
224
  implementation "androidx.core:core-ktx:1.8.0"
225
225
 
226
226
  constraints {
@@ -0,0 +1,25 @@
1
+ package com.swmansion.rnscreens
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.content.Context
5
+ import com.google.android.material.appbar.AppBarLayout
6
+
7
+ @SuppressLint("ViewConstructor")
8
+ class CustomAppBarLayout(
9
+ context: Context,
10
+ ) : AppBarLayout(context) {
11
+ /**
12
+ * Handles the layout correction from the child Toolbar.
13
+ */
14
+ internal fun applyToolbarLayoutCorrection(toolbarPaddingTop: Int) {
15
+ applyFrameCorrectionByTopInset(toolbarPaddingTop)
16
+ }
17
+
18
+ private fun applyFrameCorrectionByTopInset(topInset: Int) {
19
+ measure(
20
+ MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
21
+ MeasureSpec.makeMeasureSpec(height + topInset, MeasureSpec.EXACTLY),
22
+ )
23
+ layout(left, top, right, bottom + topInset)
24
+ }
25
+ }
@@ -34,11 +34,31 @@ open class CustomToolbar(
34
34
 
35
35
  private val shouldApplyTopInset = true
36
36
 
37
+ private var shouldApplyLayoutCorrectionForTopInset = false
38
+
37
39
  private var lastInsets = InsetsCompat.NONE
38
40
 
39
41
  private var isForceShadowStateUpdateOnLayoutRequested = false
40
42
 
41
43
  private var isLayoutEnqueued = false
44
+
45
+ init {
46
+ // Ensure ActionMenuView is initialized as soon as the Toolbar is created.
47
+ //
48
+ // Android measures Toolbar height based on the tallest child view.
49
+ // During the first measurement:
50
+ // 1. The Toolbar is created but not yet added to the action bar via `activity.setSupportActionBar(toolbar)`
51
+ // (typically called in `onUpdate` method from `ScreenStackHeaderConfig`).
52
+ // 2. At this moment, the title view may exist, but ActionMenuView (which may be taller) hasn't been added yet.
53
+ // 3. This causes the initial height calculation to be based on the title view, potentially too small.
54
+ // 4. When ActionMenuView is eventually attached, the Toolbar might need to re-layout due to the size change.
55
+ //
56
+ // By referencing the menu here, we trigger `ensureMenu`, which creates and attaches ActionMenuView early.
57
+ // This guarantees that all size-dependent children are present during the first layout pass,
58
+ // resulting in correct height determination from the beginning.
59
+ menu
60
+ }
61
+
42
62
  private val layoutCallback: Choreographer.FrameCallback =
43
63
  object : Choreographer.FrameCallback {
44
64
  override fun doFrame(frameTimeNanos: Long) {
@@ -55,6 +75,17 @@ open class CustomToolbar(
55
75
 
56
76
  override fun requestLayout() {
57
77
  super.requestLayout()
78
+
79
+ val maybeAppBarLayout = parent as? CustomAppBarLayout
80
+ maybeAppBarLayout?.let {
81
+ if (shouldApplyLayoutCorrectionForTopInset && !it.isInLayout) {
82
+ // In `applyToolbarLayoutCorrection`, we call and immediate layout on AppBarLayout
83
+ // to update it right away and avoid showing a potentially wrong UI state.
84
+ it.applyToolbarLayoutCorrection(paddingTop)
85
+ shouldApplyLayoutCorrectionForTopInset = false
86
+ }
87
+ }
88
+
58
89
  val softInputMode =
59
90
  (context as ThemedReactContext)
60
91
  .currentActivity
@@ -161,6 +192,7 @@ open class CustomToolbar(
161
192
  right: Int,
162
193
  bottom: Int,
163
194
  ) {
195
+ shouldApplyLayoutCorrectionForTopInset = true
164
196
  requestForceShadowStateUpdateOnLayout()
165
197
  setPadding(left, top, right, bottom)
166
198
  }
@@ -8,6 +8,7 @@ import android.util.SparseArray
8
8
  import android.view.MotionEvent
9
9
  import android.view.View
10
10
  import android.view.ViewGroup
11
+ import android.view.WindowInsets
11
12
  import android.view.WindowManager
12
13
  import android.webkit.WebView
13
14
  import android.widget.ImageView
@@ -35,6 +36,7 @@ import com.swmansion.rnscreens.events.SheetDetentChangedEvent
35
36
  import com.swmansion.rnscreens.ext.asScreenStackFragment
36
37
  import com.swmansion.rnscreens.ext.parentAsViewGroup
37
38
  import com.swmansion.rnscreens.gamma.common.FragmentProviding
39
+ import com.swmansion.rnscreens.utils.getDecorViewTopInset
38
40
  import kotlin.math.max
39
41
 
40
42
  @SuppressLint("ViewConstructor") // Only we construct this view, it is never inflated.
@@ -52,6 +54,8 @@ class Screen(
52
54
  val reactEventDispatcher: EventDispatcher?
53
55
  get() = UIManagerHelper.getEventDispatcherForReactTag(reactContext, id)
54
56
 
57
+ var insetsApplied = false
58
+
55
59
  var fragmentWrapper: ScreenFragmentWrapper? = null
56
60
  var container: ScreenContainer? = null
57
61
  var activityState: ActivityState? = null
@@ -191,7 +195,20 @@ class Screen(
191
195
  val width = r - l
192
196
  val height = b - t
193
197
 
194
- dispatchShadowStateUpdate(width, height, t)
198
+ if (!insetsApplied && headerConfig?.isHeaderHidden == false && headerConfig?.isHeaderTranslucent == false) {
199
+ val topLevelDecorView =
200
+ requireNotNull(
201
+ reactContext.currentActivity?.window?.decorView,
202
+ ) { "[RNScreens] DecorView is required for applying inset correction, but was null." }
203
+
204
+ val topInset = getDecorViewTopInset(topLevelDecorView)
205
+ val correctedHeight = height - topInset
206
+ val correctedOffsetY = t + topInset
207
+
208
+ dispatchShadowStateUpdate(width, correctedHeight, correctedOffsetY)
209
+ } else {
210
+ dispatchShadowStateUpdate(width, height, t)
211
+ }
195
212
  }
196
213
  }
197
214
 
@@ -502,6 +519,12 @@ class Screen(
502
519
  }
503
520
  }
504
521
 
522
+ override fun onApplyWindowInsets(insets: WindowInsets?): WindowInsets? {
523
+ insetsApplied = true
524
+
525
+ return super.onApplyWindowInsets(insets)
526
+ }
527
+
505
528
  override fun onAttachedToWindow() {
506
529
  super.onAttachedToWindow()
507
530
 
@@ -411,18 +411,27 @@ open class ScreenContainer(
411
411
  // attach newly activated screens
412
412
  var addedBefore = false
413
413
  val pendingFront: ArrayList<ScreenFragmentWrapper> = ArrayList()
414
-
415
414
  for (fragmentWrapper in screenWrappers) {
415
+ fragmentWrapper.screen.setTransitioning(transitioning)
416
+
416
417
  val activityState = getActivityState(fragmentWrapper)
417
- if (activityState !== ActivityState.INACTIVE && !fragmentWrapper.fragment.isAdded) {
418
- addedBefore = true
419
- attachScreen(it, fragmentWrapper.fragment)
420
- } else if (activityState !== ActivityState.INACTIVE && addedBefore) {
421
- // we detach the screen and then reattach it later to make it appear on front
422
- detachScreen(it, fragmentWrapper.fragment)
423
- pendingFront.add(fragmentWrapper)
418
+ if (activityState == ActivityState.INACTIVE) {
419
+ continue
420
+ }
421
+
422
+ if (fragmentWrapper.fragment.isAdded) {
423
+ if (addedBefore) {
424
+ detachScreen(it, fragmentWrapper.fragment)
425
+ pendingFront.add(fragmentWrapper)
426
+ }
427
+ } else {
428
+ if (addedBefore) {
429
+ pendingFront.add(fragmentWrapper)
430
+ } else {
431
+ addedBefore = true
432
+ attachScreen(it, fragmentWrapper.fragment)
433
+ }
424
434
  }
425
- fragmentWrapper.screen.setTransitioning(transitioning)
426
435
  }
427
436
 
428
437
  for (screenFragment in pendingFront) {
@@ -52,7 +52,7 @@ class KeyboardVisible(
52
52
  class ScreenStackFragment :
53
53
  ScreenFragment,
54
54
  ScreenStackFragmentWrapper {
55
- private var appBarLayout: AppBarLayout? = null
55
+ private var appBarLayout: CustomAppBarLayout? = null
56
56
  private var toolbar: Toolbar? = null
57
57
  private var isToolbarShadowHidden = false
58
58
  private var isToolbarTranslucent = false
@@ -204,7 +204,7 @@ class ScreenStackFragment :
204
204
 
205
205
  if (!screen.usesFormSheetPresentation()) {
206
206
  appBarLayout =
207
- context?.let { AppBarLayout(it) }?.apply {
207
+ context?.let { CustomAppBarLayout(it) }?.apply {
208
208
  // By default AppBarLayout will have a background color set but since we cover the whole layout
209
209
  // with toolbar (that can be semi-transparent) the bar layout background color does not pay a
210
210
  // role. On top of that it breaks screens animations when alfa offscreen compositing is off
@@ -0,0 +1,23 @@
1
+ package com.swmansion.rnscreens.utils
2
+
3
+ import android.view.View
4
+ import androidx.core.view.ViewCompat
5
+ import androidx.core.view.WindowInsetsCompat
6
+
7
+ /**
8
+ * Retrieves the top system inset (such as status bar or display cutout) from the given decor view.
9
+ *
10
+ * @param decorView The top-level window decor view.
11
+ * @return The top inset in pixels.
12
+ */
13
+ internal fun getDecorViewTopInset(decorView: View): Int {
14
+ val insetsCompat = ViewCompat.getRootWindowInsets(decorView) ?: return 0
15
+
16
+ return getTopInset(insetsCompat)
17
+ }
18
+
19
+ private fun getTopInset(insetsCompat: WindowInsetsCompat): Int =
20
+ insetsCompat
21
+ .getInsets(
22
+ WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout(),
23
+ ).top
@@ -179,8 +179,9 @@ internal class ScreenDummyLayoutHelper(
179
179
  }
180
180
 
181
181
  val topLevelDecorView = requireActivity().window.decorView
182
+ val topInset = getDecorViewTopInset(topLevelDecorView)
182
183
 
183
- // These dimensions are not accurate, as they do include status bar & navigation bar, however
184
+ // These dimensions are not accurate, as they do include navigation bar, however
184
185
  // it is ok for our purposes.
185
186
  val decorViewWidth = topLevelDecorView.width
186
187
  val decorViewHeight = topLevelDecorView.height
@@ -208,7 +209,10 @@ internal class ScreenDummyLayoutHelper(
208
209
  // scenarios when layout violates measured dimensions.
209
210
  coordinatorLayout.layout(0, 0, decorViewWidth, decorViewHeight)
210
211
 
211
- val headerHeight = PixelUtil.toDIPFromPixel(appBarLayout.height.toFloat())
212
+ // Include the top inset to account for the extra padding manually applied to the CustomToolbar.
213
+ val totalAppBarLayoutHeight = appBarLayout.height.toFloat() + topInset
214
+
215
+ val headerHeight = PixelUtil.toDIPFromPixel(totalAppBarLayoutHeight)
212
216
  cache = CacheEntry(CacheKey(fontSize, isTitleEmpty), headerHeight)
213
217
  return headerHeight
214
218
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-screens",
3
- "version": "4.19.0-nightly-20251203-1746a584e",
3
+ "version": "4.19.0-nightly-20251205-910978b9a",
4
4
  "description": "Native navigation primitives for your React Native app.",
5
5
  "scripts": {
6
6
  "submodules": "git submodule update --init --recursive && (cd react-navigation && yarn && yarn build && cd ../)",
@@ -86,14 +86,14 @@
86
86
  "@react-native-community/cli": "20.0.1",
87
87
  "@react-native-community/cli-platform-android": "20.0.0",
88
88
  "@react-native-community/cli-platform-ios": "20.0.0",
89
- "@react-native/babel-preset": "0.82.1",
90
- "@react-native/eslint-config": "0.82.1",
91
- "@react-native/metro-config": "0.82.1",
92
- "@react-native/typescript-config": "0.82.1",
89
+ "@react-native/babel-preset": "0.83.0-rc.4",
90
+ "@react-native/eslint-config": "0.83.0-rc.4",
91
+ "@react-native/metro-config": "0.83.0-rc.4",
92
+ "@react-native/typescript-config": "0.83.0-rc.4",
93
93
  "@react-navigation/native": "^5.8.0",
94
94
  "@react-navigation/stack": "^5.10.0",
95
95
  "@types/jest": "^29.5.13",
96
- "@types/react": "^19.1.1",
96
+ "@types/react": "^19.2.0",
97
97
  "@types/react-test-renderer": "^19.1.0",
98
98
  "@types/shelljs": "^0",
99
99
  "@typescript-eslint/eslint-plugin": "^6.5.0",
@@ -110,15 +110,15 @@
110
110
  "jest": "^29.6.3",
111
111
  "lint-staged": "^14.0.1",
112
112
  "prettier": "^2.8.8",
113
- "react": "^19.1.1",
113
+ "react": "^19.2.0",
114
114
  "react-dom": "^19.1.0",
115
- "react-native": "0.82.1",
115
+ "react-native": "0.83.0-rc.4",
116
116
  "react-native-builder-bob": "^0.23.2",
117
117
  "react-native-gesture-handler": "^2.28.0",
118
118
  "react-native-reanimated": "^3.19.0",
119
119
  "react-native-safe-area-context": "5.6.0",
120
120
  "react-native-windows": "^0.64.8",
121
- "react-test-renderer": "^19.1.1",
121
+ "react-test-renderer": "^19.2.0",
122
122
  "release-it": "^15.6.0",
123
123
  "shelljs": "^0.9.2",
124
124
  "typescript": "^5.8.3"