react-native-screens 4.10.0-beta.2 → 4.10.0-beta.3

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 (23) hide show
  1. package/android/src/fabric/java/com/swmansion/rnscreens/FabricEnabledHeaderConfigViewGroup.kt +3 -8
  2. package/android/src/main/java/com/swmansion/rnscreens/CustomToolbar.kt +111 -15
  3. package/android/src/main/java/com/swmansion/rnscreens/Screen.kt +11 -2
  4. package/android/src/main/java/com/swmansion/rnscreens/ScreenContainer.kt +1 -1
  5. package/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt +113 -33
  6. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +31 -285
  7. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt +88 -53
  8. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigShadowNode.kt +3 -0
  9. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackViewManager.kt +1 -1
  10. package/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt +257 -3
  11. package/android/src/main/java/com/swmansion/rnscreens/ext/ViewExt.kt +2 -0
  12. package/android/src/main/java/com/swmansion/rnscreens/utils/InsetsKt.kt +31 -0
  13. package/android/src/main/java/com/swmansion/rnscreens/utils/PaddingBundle.kt +1 -0
  14. package/android/src/paper/java/com/swmansion/rnscreens/FabricEnabledHeaderConfigViewGroup.kt +14 -5
  15. package/ios/RNSFullWindowOverlay.mm +6 -6
  16. package/lib/commonjs/components/Screen.js +4 -2
  17. package/lib/commonjs/components/Screen.js.map +1 -1
  18. package/lib/module/components/Screen.js +4 -2
  19. package/lib/module/components/Screen.js.map +1 -1
  20. package/lib/typescript/components/Screen.d.ts.map +1 -1
  21. package/package.json +1 -1
  22. package/src/components/Screen.tsx +6 -4
  23. package/android/src/main/java/com/swmansion/rnscreens/NativeDismissalObserver.kt +0 -12
@@ -7,7 +7,6 @@ import android.annotation.SuppressLint
7
7
  import android.content.Context
8
8
  import android.graphics.Color
9
9
  import android.graphics.drawable.ColorDrawable
10
- import android.os.Build
11
10
  import android.os.Bundle
12
11
  import android.view.LayoutInflater
13
12
  import android.view.Menu
@@ -16,33 +15,23 @@ import android.view.MenuItem
16
15
  import android.view.View
17
16
  import android.view.ViewGroup
18
17
  import android.view.WindowInsets
19
- import android.view.WindowManager
20
18
  import android.view.animation.Animation
21
19
  import android.view.animation.AnimationSet
22
20
  import android.view.animation.Transformation
23
- import android.view.inputmethod.InputMethodManager
24
21
  import android.widget.LinearLayout
25
- import androidx.annotation.RequiresApi
26
22
  import androidx.appcompat.widget.Toolbar
27
23
  import androidx.coordinatorlayout.widget.CoordinatorLayout
28
- import androidx.core.view.WindowInsetsCompat
29
24
  import com.facebook.react.uimanager.PixelUtil
30
25
  import com.facebook.react.uimanager.ReactPointerEventsView
31
26
  import com.facebook.react.uimanager.UIManagerHelper
32
27
  import com.google.android.material.appbar.AppBarLayout
33
28
  import com.google.android.material.appbar.AppBarLayout.ScrollingViewBehavior
34
29
  import com.google.android.material.bottomsheet.BottomSheetBehavior
35
- import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback
36
30
  import com.google.android.material.shape.CornerFamily
37
31
  import com.google.android.material.shape.MaterialShapeDrawable
38
32
  import com.google.android.material.shape.ShapeAppearanceModel
39
33
  import com.swmansion.rnscreens.bottomsheet.DimmingViewManager
40
34
  import com.swmansion.rnscreens.bottomsheet.SheetDelegate
41
- import com.swmansion.rnscreens.bottomsheet.SheetUtils
42
- import com.swmansion.rnscreens.bottomsheet.isSheetFitToContents
43
- import com.swmansion.rnscreens.bottomsheet.useSingleDetent
44
- import com.swmansion.rnscreens.bottomsheet.useThreeDetents
45
- import com.swmansion.rnscreens.bottomsheet.useTwoDetents
46
35
  import com.swmansion.rnscreens.bottomsheet.usesFormSheetPresentation
47
36
  import com.swmansion.rnscreens.events.ScreenAnimationDelegate
48
37
  import com.swmansion.rnscreens.events.ScreenDismissedEvent
@@ -65,7 +54,6 @@ class KeyboardVisible(
65
54
  class ScreenStackFragment :
66
55
  ScreenFragment,
67
56
  ScreenStackFragmentWrapper {
68
- public var nativeDismissalObserver: NativeDismissalObserver? = null
69
57
  private var appBarLayout: AppBarLayout? = null
70
58
  private var toolbar: Toolbar? = null
71
59
  private var isToolbarShadowHidden = false
@@ -159,51 +147,6 @@ class ScreenStackFragment :
159
147
  }
160
148
  }
161
149
 
162
- // If the Screen has `formSheet` presentation this callback is attached to its behavior.
163
- // It is responsible for firing detent changed events & removing the sheet from the container
164
- // once it is hidden by user gesture.
165
- private val bottomSheetStateCallback =
166
- object : BottomSheetCallback() {
167
- private var lastStableState: Int =
168
- SheetUtils.sheetStateFromDetentIndex(
169
- screen.sheetInitialDetentIndex,
170
- screen.sheetDetents.count(),
171
- )
172
-
173
- override fun onStateChanged(
174
- bottomSheet: View,
175
- newState: Int,
176
- ) {
177
- if (SheetUtils.isStateStable(newState)) {
178
- lastStableState = newState
179
- screen.notifySheetDetentChange(
180
- SheetUtils.detentIndexFromSheetState(
181
- lastStableState,
182
- screen.sheetDetents.count(),
183
- ),
184
- true,
185
- )
186
- } else if (newState == BottomSheetBehavior.STATE_DRAGGING) {
187
- screen.notifySheetDetentChange(
188
- SheetUtils.detentIndexFromSheetState(
189
- lastStableState,
190
- screen.sheetDetents.count(),
191
- ),
192
- false,
193
- )
194
- }
195
-
196
- if (newState == BottomSheetBehavior.STATE_HIDDEN) {
197
- dismissSelf()
198
- }
199
- }
200
-
201
- override fun onSlide(
202
- bottomSheet: View,
203
- slideOffset: Float,
204
- ) = Unit
205
- }
206
-
207
150
  /**
208
151
  * Currently this method dispatches event to JS where state is recomputed and fragment
209
152
  * gets removed in the result of incoming state update.
@@ -241,7 +184,7 @@ class ScreenStackFragment :
241
184
  ).apply {
242
185
  behavior =
243
186
  if (screen.usesFormSheetPresentation()) {
244
- createAndConfigureBottomSheetBehaviour()
187
+ createBottomSheetBehaviour()
245
188
  } else if (isToolbarTranslucent) {
246
189
  null
247
190
  } else {
@@ -249,13 +192,8 @@ class ScreenStackFragment :
249
192
  }
250
193
  }
251
194
 
252
- if (screen.usesFormSheetPresentation()) {
253
- screen.clipToOutline = true
254
- // TODO(@kkafar): without this line there is no drawable / outline & nothing shows...? Determine what's going on here
255
- attachShapeToScreen(screen)
256
- screen.elevation = screen.sheetElevation
257
- }
258
-
195
+ // This must be called before further sheet configuration.
196
+ // Otherwise there is no enter animation -> dunno why, just observed it.
259
197
  coordinatorLayout.addView(screen.recycle())
260
198
 
261
199
  if (!screen.usesFormSheetPresentation()) {
@@ -279,7 +217,28 @@ class ScreenStackFragment :
279
217
  }
280
218
  toolbar?.let { appBarLayout?.addView(it.recycle()) }
281
219
  setHasOptionsMenu(true)
220
+ } else {
221
+ screen.clipToOutline = true
222
+ // TODO(@kkafar): without this line there is no drawable / outline & nothing shows...? Determine what's going on here
223
+ attachShapeToScreen(screen)
224
+ screen.elevation = screen.sheetElevation
225
+
226
+ // Lifecycle of sheet delegate is tied to fragment.
227
+ val sheetDelegate = requireSheetDelegate()
228
+ sheetDelegate.configureBottomSheetBehaviour(screen.sheetBehavior!!)
229
+
230
+ val dimmingDelegate = requireDimmingDelegate(forceCreation = true)
231
+ dimmingDelegate.onViewHierarchyCreated(screen, coordinatorLayout)
232
+ dimmingDelegate.onBehaviourAttached(screen, screen.sheetBehavior!!)
233
+
234
+ val container = screen.container!!
235
+ coordinatorLayout.measure(
236
+ View.MeasureSpec.makeMeasureSpec(container.width, View.MeasureSpec.EXACTLY),
237
+ View.MeasureSpec.makeMeasureSpec(container.height, View.MeasureSpec.EXACTLY),
238
+ )
239
+ coordinatorLayout.layout(0, 0, container.width, container.height)
282
240
  }
241
+
283
242
  return coordinatorLayout
284
243
  }
285
244
 
@@ -288,24 +247,6 @@ class ScreenStackFragment :
288
247
  savedInstanceState: Bundle?,
289
248
  ) {
290
249
  super.onViewCreated(view, savedInstanceState)
291
-
292
- if (!screen.usesFormSheetPresentation()) {
293
- return
294
- }
295
-
296
- sheetDelegate = SheetDelegate(screen)
297
-
298
- assert(view == coordinatorLayout)
299
- val dimmingDelegate = requireDimmingDelegate(forceCreation = true)
300
- dimmingDelegate.onViewHierarchyCreated(screen, coordinatorLayout)
301
- dimmingDelegate.onBehaviourAttached(screen, screen.sheetBehavior!!)
302
-
303
- val container = screen.container!!
304
- coordinatorLayout.measure(
305
- View.MeasureSpec.makeMeasureSpec(container.width, View.MeasureSpec.EXACTLY),
306
- View.MeasureSpec.makeMeasureSpec(container.height, View.MeasureSpec.EXACTLY),
307
- )
308
- coordinatorLayout.layout(0, 0, container.width, container.height)
309
250
  }
310
251
 
311
252
  override fun onCreateAnimation(
@@ -387,210 +328,8 @@ class ScreenStackFragment :
387
328
  return animatorSet
388
329
  }
389
330
 
390
- /**
391
- * This method might return slightly different values depending on code path,
392
- * but during testing I've found this effect negligible. For practical purposes
393
- * this is acceptable.
394
- */
395
- private fun tryResolveContainerHeight(): Int? {
396
- if (screen.container != null) {
397
- return screenStack.height
398
- }
399
-
400
- context
401
- ?.resources
402
- ?.displayMetrics
403
- ?.heightPixels
404
- ?.let { return it }
405
-
406
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
407
- (context?.getSystemService(Context.WINDOW_SERVICE) as? WindowManager)
408
- ?.currentWindowMetrics
409
- ?.bounds
410
- ?.height()
411
- ?.let { return it }
412
- }
413
- return null
414
- }
415
-
416
- private val keyboardSheetCallback =
417
- object : BottomSheetCallback() {
418
- @RequiresApi(Build.VERSION_CODES.M)
419
- override fun onStateChanged(
420
- bottomSheet: View,
421
- newState: Int,
422
- ) {
423
- if (newState == BottomSheetBehavior.STATE_COLLAPSED) {
424
- val isImeVisible =
425
- WindowInsetsCompat
426
- .toWindowInsetsCompat(bottomSheet.rootWindowInsets)
427
- .isVisible(WindowInsetsCompat.Type.ime())
428
- if (isImeVisible) {
429
- // Does it not interfere with React Native focus mechanism? In any case I'm not aware
430
- // of different way of hiding the keyboard.
431
- // https://stackoverflow.com/questions/1109022/how-can-i-close-hide-the-android-soft-keyboard-programmatically
432
- // https://developer.android.com/develop/ui/views/touch-and-input/keyboard-input/visibility
433
-
434
- // I want to be polite here and request focus before dismissing the keyboard,
435
- // however even if it fails I want to try to hide the keyboard. This sometimes works...
436
- bottomSheet.requestFocus()
437
- val imm = requireContext().getSystemService(InputMethodManager::class.java)
438
- imm.hideSoftInputFromWindow(bottomSheet.windowToken, 0)
439
- }
440
- }
441
- }
442
-
443
- override fun onSlide(
444
- bottomSheet: View,
445
- slideOffset: Float,
446
- ) = Unit
447
- }
448
-
449
- internal fun configureBottomSheetBehaviour(
450
- behavior: BottomSheetBehavior<Screen>,
451
- keyboardState: KeyboardState = KeyboardNotVisible,
452
- ): BottomSheetBehavior<Screen> {
453
- val containerHeight = tryResolveContainerHeight()
454
- check(containerHeight != null) {
455
- "[RNScreens] Failed to find window height during bottom sheet behaviour configuration"
456
- }
457
-
458
- behavior.apply {
459
- isHideable = true
460
- isDraggable = true
461
-
462
- // It seems that there is a guard in material implementation that will prevent
463
- // this callback from being registered multiple times.
464
- addBottomSheetCallback(bottomSheetStateCallback)
465
- }
466
-
467
- screen.footer?.registerWithSheetBehavior(behavior)
468
-
469
- return when (keyboardState) {
470
- is KeyboardNotVisible -> {
471
- when (screen.sheetDetents.count()) {
472
- 1 ->
473
- behavior.apply {
474
- val height =
475
- if (screen.isSheetFitToContents()) {
476
- screen.contentWrapper
477
- .get()
478
- ?.height
479
- .takeIf { screen.contentWrapper.get()?.isLaidOut == true }
480
- } else {
481
- (screen.sheetDetents.first() * containerHeight).toInt()
482
- }
483
- useSingleDetent(height = height)
484
- }
485
-
486
- 2 ->
487
- behavior.useTwoDetents(
488
- state =
489
- SheetUtils.sheetStateFromDetentIndex(
490
- screen.sheetInitialDetentIndex,
491
- screen.sheetDetents.count(),
492
- ),
493
- firstHeight = (screen.sheetDetents[0] * containerHeight).toInt(),
494
- secondHeight = (screen.sheetDetents[1] * containerHeight).toInt(),
495
- )
496
-
497
- 3 ->
498
- behavior.useThreeDetents(
499
- state =
500
- SheetUtils.sheetStateFromDetentIndex(
501
- screen.sheetInitialDetentIndex,
502
- screen.sheetDetents.count(),
503
- ),
504
- firstHeight = (screen.sheetDetents[0] * containerHeight).toInt(),
505
- halfExpandedRatio = (screen.sheetDetents[1] / screen.sheetDetents[2]).toFloat(),
506
- expandedOffsetFromTop = ((1 - screen.sheetDetents[2]) * containerHeight).toInt(),
507
- )
508
-
509
- else -> throw IllegalStateException(
510
- "[RNScreens] Invalid detent count ${screen.sheetDetents.count()}. Expected at most 3.",
511
- )
512
- }
513
- }
514
-
515
- is KeyboardVisible -> {
516
- val newMaxHeight =
517
- if (behavior.maxHeight - keyboardState.height > 1) {
518
- behavior.maxHeight - keyboardState.height
519
- } else {
520
- behavior.maxHeight
521
- }
522
- when (screen.sheetDetents.count()) {
523
- 1 ->
524
- behavior.apply {
525
- useSingleDetent(height = newMaxHeight)
526
- addBottomSheetCallback(keyboardSheetCallback)
527
- }
528
-
529
- 2 ->
530
- behavior.apply {
531
- useTwoDetents(
532
- state = BottomSheetBehavior.STATE_EXPANDED,
533
- secondHeight = newMaxHeight,
534
- )
535
- addBottomSheetCallback(keyboardSheetCallback)
536
- }
537
-
538
- 3 ->
539
- behavior.apply {
540
- useThreeDetents(
541
- state = BottomSheetBehavior.STATE_EXPANDED,
542
- )
543
- maxHeight = newMaxHeight
544
- addBottomSheetCallback(keyboardSheetCallback)
545
- }
546
-
547
- else -> throw IllegalStateException(
548
- "[RNScreens] Invalid detent count ${screen.sheetDetents.count()}. Expected at most 3.",
549
- )
550
- }
551
- }
552
-
553
- is KeyboardDidHide -> {
554
- // Here we assume that the keyboard was either closed explicitly by user,
555
- // or the user dragged the sheet down. In any case the state should
556
- // stay unchanged.
557
-
558
- behavior.removeBottomSheetCallback(keyboardSheetCallback)
559
- when (screen.sheetDetents.count()) {
560
- 1 ->
561
- behavior.useSingleDetent(
562
- height = (screen.sheetDetents.first() * containerHeight).toInt(),
563
- forceExpandedState = false,
564
- )
565
-
566
- 2 ->
567
- behavior.useTwoDetents(
568
- firstHeight = (screen.sheetDetents[0] * containerHeight).toInt(),
569
- secondHeight = (screen.sheetDetents[1] * containerHeight).toInt(),
570
- )
571
-
572
- 3 ->
573
- behavior.useThreeDetents(
574
- firstHeight = (screen.sheetDetents[0] * containerHeight).toInt(),
575
- halfExpandedRatio = (screen.sheetDetents[1] / screen.sheetDetents[2]).toFloat(),
576
- expandedOffsetFromTop = ((1 - screen.sheetDetents[2]) * containerHeight).toInt(),
577
- )
578
-
579
- else -> throw IllegalStateException(
580
- "[RNScreens] Invalid detent count ${screen.sheetDetents.count()}. Expected at most 3.",
581
- )
582
- }
583
- }
584
- }
585
- }
586
-
587
331
  private fun createBottomSheetBehaviour(): BottomSheetBehavior<Screen> = BottomSheetBehavior<Screen>()
588
332
 
589
- // In general it would be great to create BottomSheetBehaviour only via this method as it runs some
590
- // side effects.
591
- private fun createAndConfigureBottomSheetBehaviour(): BottomSheetBehavior<Screen> =
592
- configureBottomSheetBehaviour(createBottomSheetBehaviour())
593
-
594
333
  private fun resolveBackgroundColor(screen: Screen): Int? {
595
334
  val screenColor =
596
335
  (screen.background as? ColorDrawable?)?.color
@@ -724,6 +463,13 @@ class ScreenStackFragment :
724
463
  return dimmingDelegate!!
725
464
  }
726
465
 
466
+ private fun requireSheetDelegate(): SheetDelegate {
467
+ if (sheetDelegate == null) {
468
+ sheetDelegate = SheetDelegate(screen)
469
+ }
470
+ return sheetDelegate!!
471
+ }
472
+
727
473
  private class ScreensCoordinatorLayout(
728
474
  context: Context,
729
475
  private val fragment: ScreenStackFragment,
@@ -3,12 +3,10 @@ package com.swmansion.rnscreens
3
3
  import android.content.Context
4
4
  import android.graphics.PorterDuff
5
5
  import android.graphics.PorterDuffColorFilter
6
- import android.os.Build
7
6
  import android.text.TextUtils
8
7
  import android.util.TypedValue
9
8
  import android.view.Gravity
10
9
  import android.view.View.OnClickListener
11
- import android.view.WindowInsets
12
10
  import android.widget.ImageView
13
11
  import android.widget.TextView
14
12
  import androidx.appcompat.app.AppCompatActivity
@@ -21,6 +19,7 @@ import com.facebook.react.uimanager.UIManagerHelper
21
19
  import com.facebook.react.views.text.ReactTypefaceUtils
22
20
  import com.swmansion.rnscreens.events.HeaderAttachedEvent
23
21
  import com.swmansion.rnscreens.events.HeaderDetachedEvent
22
+ import kotlin.math.max
24
23
 
25
24
  class ScreenStackHeaderConfig(
26
25
  context: Context,
@@ -28,8 +27,8 @@ class ScreenStackHeaderConfig(
28
27
  private val configSubviews = ArrayList<ScreenStackHeaderSubview>(3)
29
28
  val toolbar: CustomToolbar
30
29
  var isHeaderHidden = false // named this way to avoid conflict with platform's isHidden
31
- var isHeaderTranslucent = false // named this way to avoid conflict with platform's isTranslucent
32
- private var headerTopInset: Int? = null
30
+ var isHeaderTranslucent =
31
+ false // named this way to avoid conflict with platform's isTranslucent
33
32
  private var title: String? = null
34
33
  private var titleColor = 0
35
34
  private var titleFontFamily: String? = null
@@ -41,7 +40,9 @@ class ScreenStackHeaderConfig(
41
40
  private var isShadowHidden = false
42
41
  private var isDestroyed = false
43
42
  private var backButtonInCustomView = false
44
- private var isTopInsetEnabled = true
43
+ var isTopInsetEnabled = true
44
+ private set
45
+
45
46
  private var tintColor = 0
46
47
  private var isAttachedToWindow = false
47
48
  private val defaultStartInset: Int
@@ -69,19 +70,76 @@ class ScreenStackHeaderConfig(
69
70
  }
70
71
  }
71
72
 
73
+ var isTitleEmpty: Boolean = false
74
+
75
+ val preferredContentInsetStart
76
+ get() = defaultStartInset
77
+
78
+ val preferredContentInsetEnd
79
+ get() = defaultStartInset
80
+
81
+ val preferredContentInsetStartWithNavigation
82
+ get() =
83
+ // Reset toolbar insets. By default we set symmetric inset for start and end to match iOS
84
+ // implementation where both right and left icons are offset from the edge by default. We also
85
+ // reset startWithNavigation inset which corresponds to the distance between navigation icon and
86
+ // title. If title isn't set we clear that value few lines below to give more space to custom
87
+ // center-mounted views.
88
+ if (isTitleEmpty) {
89
+ 0
90
+ } else {
91
+ defaultStartInsetWithNavigation
92
+ }
93
+
94
+ fun destroy() {
95
+ isDestroyed = true
96
+ }
97
+
98
+ /**
99
+ * Native toolbar should notify the header config component that it has completed its layout.
100
+ */
101
+ fun onNativeToolbarLayout(
102
+ toolbar: Toolbar,
103
+ shouldUpdateShadowStateHint: Boolean,
104
+ ) {
105
+ if (!shouldUpdateShadowStateHint) {
106
+ return
107
+ }
108
+
109
+ val isBackButtonDisplayed = toolbar.navigationIcon != null
110
+
111
+ val contentInsetStartEstimation =
112
+ if (isBackButtonDisplayed) {
113
+ toolbar.currentContentInsetStart + toolbar.paddingStart
114
+ } else {
115
+ max(toolbar.currentContentInsetStart, toolbar.paddingStart)
116
+ }
117
+
118
+ // Assuming that there is nothing to the left of back button here, the content
119
+ // offset we're interested in in ShadowTree is the `left` of the subview left.
120
+ // In case it is not available we fallback to approximation.
121
+ val contentInsetStart =
122
+ configSubviews.firstOrNull { it.type === ScreenStackHeaderSubview.Type.LEFT }?.left
123
+ ?: contentInsetStartEstimation
124
+
125
+ val contentInsetEnd = toolbar.currentContentInsetEnd + toolbar.paddingEnd
126
+
127
+ // Note that implementation of the callee differs between architectures.
128
+ updateHeaderConfigState(
129
+ toolbar.width,
130
+ toolbar.height,
131
+ contentInsetStart,
132
+ contentInsetEnd,
133
+ )
134
+ }
135
+
72
136
  override fun onLayout(
73
137
  changed: Boolean,
74
138
  l: Int,
75
139
  t: Int,
76
140
  r: Int,
77
141
  b: Int,
78
- ) {
79
- // no-op
80
- }
81
-
82
- fun destroy() {
83
- isDestroyed = true
84
- }
142
+ ) = Unit
85
143
 
86
144
  override fun onAttachedToWindow() {
87
145
  super.onAttachedToWindow()
@@ -90,19 +148,6 @@ class ScreenStackHeaderConfig(
90
148
  UIManagerHelper
91
149
  .getEventDispatcherForReactTag(context as ReactContext, id)
92
150
  ?.dispatchEvent(HeaderAttachedEvent(surfaceId, id))
93
- // we want to save the top inset before the status bar can be hidden, which would resolve in
94
- // inset being 0
95
- if (headerTopInset == null) {
96
- headerTopInset =
97
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
98
- rootWindowInsets.getInsets(WindowInsets.Type.systemBars()).top
99
- } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
100
- rootWindowInsets.systemWindowInsetTop
101
- } else {
102
- // Hacky fallback for old android. Before Marshmallow, the status bar height was always 25
103
- (25 * resources.displayMetrics.density).toInt()
104
- }
105
- }
106
151
  onUpdate()
107
152
  }
108
153
 
@@ -176,32 +221,27 @@ class ScreenStackHeaderConfig(
176
221
  screenFragment?.setToolbar(toolbar)
177
222
  }
178
223
 
179
- if (isTopInsetEnabled) {
180
- headerTopInset.let {
181
- toolbar.setPadding(0, it ?: 0, 0, 0)
182
- }
183
- } else {
184
- if (toolbar.paddingTop > 0) {
185
- toolbar.setPadding(0, 0, 0, 0)
186
- }
187
- }
188
-
189
224
  activity.setSupportActionBar(toolbar)
190
225
  // non-null toolbar is set in the line above and it is used here
191
226
  val actionBar = requireNotNull(activity.supportActionBar)
192
227
 
228
+ // hide back button
229
+ actionBar.setDisplayHomeAsUpEnabled(
230
+ screenFragment?.canNavigateBack() == true && !isBackButtonHidden,
231
+ )
232
+
233
+ // title
234
+ actionBar.title = title
235
+ if (TextUtils.isEmpty(title)) {
236
+ isTitleEmpty = true
237
+ }
238
+
193
239
  // Reset toolbar insets. By default we set symmetric inset for start and end to match iOS
194
240
  // implementation where both right and left icons are offset from the edge by default. We also
195
241
  // reset startWithNavigation inset which corresponds to the distance between navigation icon and
196
242
  // title. If title isn't set we clear that value few lines below to give more space to custom
197
243
  // center-mounted views.
198
- toolbar.contentInsetStartWithNavigation = defaultStartInsetWithNavigation
199
- toolbar.setContentInsetsRelative(defaultStartInset, defaultStartInset)
200
-
201
- // hide back button
202
- actionBar.setDisplayHomeAsUpEnabled(
203
- screenFragment?.canNavigateBack() == true && !isBackButtonHidden,
204
- )
244
+ toolbar.updateContentInsets()
205
245
 
206
246
  // when setSupportActionBar is called a toolbar wrapper gets initialized that overwrites
207
247
  // navigation click listener. The default behavior set in the wrapper is to call into
@@ -214,15 +254,6 @@ class ScreenStackHeaderConfig(
214
254
  // translucent
215
255
  screenFragment?.setToolbarTranslucent(isHeaderTranslucent)
216
256
 
217
- // title
218
- actionBar.title = title
219
- if (TextUtils.isEmpty(title)) {
220
- // if title is empty we set start navigation inset to 0 to give more space to custom rendered
221
- // views. When it is set to default it'd take up additional distance from the back button
222
- // which would impact the position of custom header views rendered at the center.
223
- toolbar.contentInsetStartWithNavigation = 0
224
- }
225
-
226
257
  val titleTextView = findTitleTextViewInToolbar(toolbar)
227
258
  if (titleColor != 0) {
228
259
  toolbar.setTitleTextColor(titleColor)
@@ -250,7 +281,8 @@ class ScreenStackHeaderConfig(
250
281
 
251
282
  // color
252
283
  if (tintColor != 0) {
253
- toolbar.navigationIcon?.colorFilter = PorterDuffColorFilter(tintColor, PorterDuff.Mode.SRC_ATOP)
284
+ toolbar.navigationIcon?.colorFilter =
285
+ PorterDuffColorFilter(tintColor, PorterDuff.Mode.SRC_ATOP)
254
286
  }
255
287
 
256
288
  // subviews
@@ -288,12 +320,14 @@ class ScreenStackHeaderConfig(
288
320
  toolbar.title = null
289
321
  params.gravity = Gravity.START
290
322
  }
323
+
291
324
  ScreenStackHeaderSubview.Type.RIGHT -> params.gravity = Gravity.END
292
325
  ScreenStackHeaderSubview.Type.CENTER -> {
293
326
  params.width = LayoutParams.MATCH_PARENT
294
327
  params.gravity = Gravity.CENTER_HORIZONTAL
295
328
  toolbar.title = null
296
329
  }
330
+
297
331
  else -> {}
298
332
  }
299
333
  view.layoutParams = params
@@ -402,7 +436,8 @@ class ScreenStackHeaderConfig(
402
436
 
403
437
  init {
404
438
  visibility = GONE
405
- toolbar = if (BuildConfig.DEBUG) DebugMenuToolbar(context, this) else CustomToolbar(context, this)
439
+ toolbar =
440
+ if (BuildConfig.DEBUG) DebugMenuToolbar(context, this) else CustomToolbar(context, this)
406
441
  defaultStartInset = toolbar.contentInsetStart
407
442
  defaultStartInsetWithNavigation = toolbar.contentInsetStartWithNavigation
408
443
 
@@ -10,14 +10,17 @@ internal class ScreenStackHeaderConfigShadowNode(
10
10
  ) : LayoutShadowNode() {
11
11
  var paddingStart: Float = 0f
12
12
  var paddingEnd: Float = 0f
13
+ var height: Float = 0f
13
14
 
14
15
  override fun setLocalData(data: Any?) {
15
16
  if (data is PaddingBundle) {
16
17
  paddingStart = data.paddingStart
17
18
  paddingEnd = data.paddingEnd
19
+ height = data.height
18
20
 
19
21
  setPadding(Spacing.START, paddingStart)
20
22
  setPadding(Spacing.END, paddingEnd)
23
+ setPosition(Spacing.TOP, -height)
21
24
  } else {
22
25
  super.setLocalData(data)
23
26
  }
@@ -30,7 +30,7 @@ class ScreenStackViewManager :
30
30
  child: View,
31
31
  index: Int,
32
32
  ) {
33
- require(child is Screen) { "Attempt attach child that is not of type RNScreen" }
33
+ require(child is Screen) { "Attempt attach child that is not of type Screen" }
34
34
  NativeProxy.addScreenToMap(child.id, child)
35
35
  parent.addScreen(child, index)
36
36
  }