react-native-screens 4.25.1 → 4.25.2

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.
@@ -248,7 +248,7 @@ class Screen(
248
248
  }
249
249
 
250
250
  if (coordinatorLayoutDidChange) {
251
- updateShadowNodeScreenSize(width, height, top)
251
+ updateShadowNodeScreenSize(width, height, top + translationY.toInt())
252
252
  }
253
253
 
254
254
  footer?.onParentLayout(coordinatorLayoutDidChange, left, top, right, bottom, container!!.height)
@@ -0,0 +1,37 @@
1
+ package com.swmansion.rnscreens.gamma.tabs.container
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.content.Context
5
+ import com.google.android.material.bottomnavigation.BottomNavigationView
6
+
7
+ @SuppressLint("ViewConstructor") // Should not be restored & should only be constructed by us.
8
+ class CustomBottomNavigationView(
9
+ context: Context,
10
+ val container: TabsContainer,
11
+ ) : BottomNavigationView(context) {
12
+ private var actionOrigin: TabsActionOrigin? = null
13
+
14
+ internal fun setSelectedItemIdWithActionOrigin(
15
+ itemId: Int,
16
+ actionOrigin: TabsActionOrigin,
17
+ ) {
18
+ require(actionOrigin !== TabsActionOrigin.USER) {
19
+ "[RNScreens] User-triggered actions should be processed via regular setSelectedItemId callback"
20
+ }
21
+ this.actionOrigin = actionOrigin
22
+ selectedItemId = itemId
23
+ this.actionOrigin = null
24
+ }
25
+
26
+ override fun setSelectedItemId(itemId: Int) {
27
+ if (this.actionOrigin == null) {
28
+ this.actionOrigin = TabsActionOrigin.USER
29
+ }
30
+
31
+ val actionOrigin = checkNotNull(this.actionOrigin)
32
+ super.setSelectedItemId(itemId)
33
+ container.onAfterSetSelectedItemId(itemId, actionOrigin)
34
+
35
+ this.actionOrigin = null
36
+ }
37
+ }
@@ -109,8 +109,8 @@ class TabsContainer internal constructor(
109
109
  R.style.Theme_Material3_DayNight_NoActionBar,
110
110
  )
111
111
 
112
- internal val bottomNavigationView: BottomNavigationView =
113
- BottomNavigationView(themedContext).apply {
112
+ internal val bottomNavigationView: CustomBottomNavigationView =
113
+ CustomBottomNavigationView(themedContext, this).apply {
114
114
  layoutParams =
115
115
  LayoutParams(
116
116
  LayoutParams.MATCH_PARENT,
@@ -256,6 +256,16 @@ class TabsContainer internal constructor(
256
256
  setPendingNavigationStateUpdate(null)
257
257
  }
258
258
 
259
+ internal fun onAfterSetSelectedItemId(
260
+ itemId: Int,
261
+ actionOrigin: TabsActionOrigin,
262
+ ) {
263
+ if (actionOrigin === TabsActionOrigin.USER) {
264
+ // For non-user actions these will be performed in [performContainerUpdate]
265
+ performPostSelectedTabUpdateActions()
266
+ }
267
+ }
268
+
259
269
  // endregion
260
270
 
261
271
  // region View lifecycle / insets / appearance
@@ -406,27 +416,62 @@ class TabsContainer internal constructor(
406
416
 
407
417
  // region Private helpers
408
418
 
419
+ /**
420
+ * This is where programmatic update flow starts.
421
+ * This includes any JS-triggered action (navigation state update, prop update, etc.)
422
+ * and native programmatic actions - tab change.
423
+ *
424
+ * This method is supposed to perform all the necessary steps, satisfying all invalidation
425
+ * signals in a coordinated manner.
426
+ *
427
+ * The update actions are split into three phases: pre-, selection change, and post-.
428
+ * Pre-selection actions are run only here, in programmatic flow. This is not a hard requirement,
429
+ * it is just not needed now.
430
+ *
431
+ * Post-selection actions are performed here or in parallel flow triggered on user selection.
432
+ *
433
+ * The selected tab update takes place here only for programmatic changes.
434
+ * User triggered changes have separate entry-point.
435
+ */
409
436
  private fun performContainerUpdate() {
437
+ performPreSelectedTabUpdateActions()
438
+ performSelectedTabUpdateIfNeeded()
439
+ performPostSelectedTabUpdateActions()
440
+ }
441
+
442
+ private fun performPreSelectedTabUpdateActions() {
443
+ updateNavigationMenuStructureIfNeeded()
444
+ }
445
+
446
+ private fun performPostSelectedTabUpdateActions() {
447
+ updateBottomNavigationViewAppearanceIfNeeded()
448
+ }
449
+
450
+ private fun updateNavigationMenuStructureIfNeeded() {
410
451
  if (invalidationFlags.isNavigationMenuStructureInvalidated) {
411
452
  invalidationFlags.isNavigationMenuStructureInvalidated = false
412
453
  updateNavigationMenuStructure()
413
454
  }
455
+ }
414
456
 
457
+ private fun performSelectedTabUpdateIfNeeded() {
415
458
  if (invalidationFlags.isSelectedTabInvalidated) {
416
459
  invalidationFlags.isSelectedTabInvalidated = false
417
- performOperation()
460
+ performSelectedTabUpdate()
418
461
  }
462
+ }
419
463
 
464
+ private fun updateBottomNavigationViewAppearanceIfNeeded() {
420
465
  if (invalidationFlags.isNavigationMenuAppearanceInvalidated) {
421
466
  invalidationFlags.isNavigationMenuAppearanceInvalidated = false
422
- this.updateBottomNavigationViewAppearance()
467
+ updateBottomNavigationViewAppearance()
423
468
  a11yCoordinator.setA11yPropertiesToAllTabItems()
424
469
  }
425
470
  }
426
471
 
427
- private fun performOperation() {
472
+ private fun performSelectedTabUpdate() {
428
473
  if (pendingStateUpdateRequest == null) {
429
- RNSLog.w(TAG, "TabsContainer::performOperation called w/o pending operation; skipping update")
474
+ RNSLog.w(TAG, "TabsContainer::performSelectedTabUpdate called w/o pending operation; skipping update")
430
475
  return
431
476
  }
432
477
 
@@ -450,7 +495,7 @@ class TabsContainer internal constructor(
450
495
  if (bottomNavigationView.selectedItemId != nextSelectedMenuItemId || navState.isEmpty()) {
451
496
  isInExternalOperationContext = true
452
497
  // This triggers on OnMenuItemClicked callback, where we perform actual update from
453
- bottomNavigationView.selectedItemId = nextSelectedMenuItemId
498
+ bottomNavigationView.setSelectedItemIdWithActionOrigin(nextSelectedMenuItemId, stateUpdateRequest.actionOrigin)
454
499
  isInExternalOperationContext = false
455
500
  } else {
456
501
  observerRegistry.emitOnNavigationStateUpdateRejected(
@@ -559,6 +604,13 @@ class TabsContainer internal constructor(
559
604
  val hasTriggeredSpecialEffect =
560
605
  if (isRepeated) specialEffectsHandler.handleRepeatedTabSelection() else false
561
606
 
607
+ if (stateChanged && !isRepeated) {
608
+ // If we've effectively changed the tab, we need to raise appropriate flags.
609
+ // This line assumes that any required e.g. appearance actions will be performed
610
+ // synchronously later in the flow.
611
+ invalidationFlags.invalidateOnSelectedTabChanged()
612
+ }
613
+
562
614
  if (stateChanged) {
563
615
  observerRegistry.emitOnNavigationStateUpdate(
564
616
  navState,
@@ -694,4 +746,8 @@ internal class TabsContainerInvalidationFlags(
694
746
  isNavigationMenuAppearanceInvalidated = true
695
747
  isNavigationMenuStructureInvalidated = true
696
748
  }
749
+
750
+ internal fun invalidateOnSelectedTabChanged() {
751
+ isNavigationMenuAppearanceInvalidated = true
752
+ }
697
753
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-screens",
3
- "version": "4.25.1",
3
+ "version": "4.25.2",
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 ../)",