react-native-screens 4.25.0-beta.1 → 4.25.0-beta.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.
Files changed (96) hide show
  1. package/android/src/main/java/com/swmansion/rnscreens/gamma/stack/header/StackHeaderAppBarLayout.kt +6 -14
  2. package/android/src/main/java/com/swmansion/rnscreens/gamma/stack/header/StackHeaderAppBarLayoutBehavior.kt +29 -0
  3. package/android/src/main/java/com/swmansion/rnscreens/gamma/stack/header/StackHeaderCoordinator.kt +56 -0
  4. package/android/src/main/java/com/swmansion/rnscreens/gamma/stack/header/config/StackHeaderConfig.kt +11 -0
  5. package/android/src/main/java/com/swmansion/rnscreens/gamma/stack/header/config/StackHeaderConfigProviding.kt +5 -0
  6. package/android/src/main/java/com/swmansion/rnscreens/gamma/stack/header/config/StackHeaderConfigViewManager.kt +35 -0
  7. package/android/src/main/java/com/swmansion/rnscreens/gamma/stack/header/subview/StackHeaderSubview.kt +3 -7
  8. package/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/container/TabsActionOrigin.kt +26 -0
  9. package/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/container/TabsContainer.kt +227 -151
  10. package/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/container/TabsNavigationState.kt +60 -0
  11. package/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/container/{TabsContainerDelegate.kt → TabsNavigationStateObserver.kt} +19 -14
  12. package/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/container/TabsNavigationStateObserverRegistry.kt +88 -0
  13. package/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/TabsHost.kt +40 -24
  14. package/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/TabsHostEventEmitter.kt +11 -9
  15. package/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/TabsHostViewManager.kt +19 -7
  16. package/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/event/TabsHostTabSelectedEvent.kt +4 -3
  17. package/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/event/TabsHostTabSelectionPreventedEvent.kt +3 -3
  18. package/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/event/TabsHostTabSelectionRejectedEvent.kt +11 -10
  19. package/ios/conversion/RNSConversions-Tabs.mm +19 -0
  20. package/ios/conversion/RNSConversions.h +3 -0
  21. package/ios/tabs/bottom-accessory/RNSTabsBottomAccessoryHelper.mm +34 -5
  22. package/ios/tabs/host/RNSTabBarController.h +152 -99
  23. package/ios/tabs/host/RNSTabBarController.mm +137 -113
  24. package/ios/tabs/host/RNSTabsHostComponentView.h +7 -8
  25. package/ios/tabs/host/RNSTabsHostComponentView.mm +37 -33
  26. package/ios/tabs/host/RNSTabsHostEventEmitter.h +4 -4
  27. package/ios/tabs/host/RNSTabsHostEventEmitter.mm +5 -3
  28. package/ios/tabs/host/RNSTabsNavigationState.h +142 -27
  29. package/ios/tabs/host/RNSTabsNavigationState.mm +35 -2
  30. package/ios/tabs/host/RNSTabsNavigationStateObserverRegistry.h +62 -0
  31. package/ios/tabs/host/RNSTabsNavigationStateObserverRegistry.mm +104 -0
  32. package/lib/commonjs/components/gamma/stack/header/StackHeaderConfig.android.js +46 -1
  33. package/lib/commonjs/components/gamma/stack/header/StackHeaderConfig.android.js.map +1 -1
  34. package/lib/commonjs/components/safe-area/SafeAreaView.web.js +2 -3
  35. package/lib/commonjs/components/safe-area/SafeAreaView.web.js.map +1 -1
  36. package/lib/commonjs/components/tabs/host/TabsHost.android.js +2 -2
  37. package/lib/commonjs/components/tabs/host/TabsHost.android.js.map +1 -1
  38. package/lib/commonjs/components/tabs/host/TabsHost.ios.js +2 -2
  39. package/lib/commonjs/components/tabs/host/TabsHost.ios.js.map +1 -1
  40. package/lib/commonjs/fabric/gamma/stack/StackHeaderConfigAndroidNativeComponent.js.map +1 -1
  41. package/lib/commonjs/flags.js +1 -0
  42. package/lib/commonjs/flags.js.map +1 -1
  43. package/lib/module/components/gamma/stack/header/StackHeaderConfig.android.js +46 -1
  44. package/lib/module/components/gamma/stack/header/StackHeaderConfig.android.js.map +1 -1
  45. package/lib/module/components/safe-area/SafeAreaView.web.js +1 -1
  46. package/lib/module/components/safe-area/SafeAreaView.web.js.map +1 -1
  47. package/lib/module/components/tabs/host/TabsHost.android.js +2 -2
  48. package/lib/module/components/tabs/host/TabsHost.android.js.map +1 -1
  49. package/lib/module/components/tabs/host/TabsHost.ios.js +2 -2
  50. package/lib/module/components/tabs/host/TabsHost.ios.js.map +1 -1
  51. package/lib/module/fabric/gamma/stack/StackHeaderConfigAndroidNativeComponent.js.map +1 -1
  52. package/lib/module/flags.js +1 -0
  53. package/lib/module/flags.js.map +1 -1
  54. package/lib/typescript/components/gamma/split/SplitHost.types.d.ts +1 -1
  55. package/lib/typescript/components/gamma/split/SplitHost.types.d.ts.map +1 -1
  56. package/lib/typescript/components/gamma/stack/header/StackHeaderConfig.android.d.ts.map +1 -1
  57. package/lib/typescript/components/gamma/stack/header/StackHeaderConfig.android.types.d.ts +183 -8
  58. package/lib/typescript/components/gamma/stack/header/StackHeaderConfig.android.types.d.ts.map +1 -1
  59. package/lib/typescript/components/gamma/stack/header/StackHeaderConfig.types.d.ts +37 -0
  60. package/lib/typescript/components/gamma/stack/header/StackHeaderConfig.types.d.ts.map +1 -1
  61. package/lib/typescript/components/gamma/stack/header/android/StackHeaderSubview.android.types.d.ts +1 -1
  62. package/lib/typescript/components/gamma/stack/header/android/StackHeaderSubview.android.types.d.ts.map +1 -1
  63. package/lib/typescript/components/gamma/stack/host/StackHost.types.d.ts +1 -1
  64. package/lib/typescript/components/gamma/stack/host/StackHost.types.d.ts.map +1 -1
  65. package/lib/typescript/components/safe-area/SafeAreaView.web.d.ts +1 -1
  66. package/lib/typescript/components/safe-area/SafeAreaView.web.d.ts.map +1 -1
  67. package/lib/typescript/components/tabs/host/TabsHost.types.d.ts +27 -17
  68. package/lib/typescript/components/tabs/host/TabsHost.types.d.ts.map +1 -1
  69. package/lib/typescript/components/tabs/index.d.ts +1 -1
  70. package/lib/typescript/components/tabs/index.d.ts.map +1 -1
  71. package/lib/typescript/fabric/gamma/stack/StackHeaderConfigAndroidNativeComponent.d.ts +5 -0
  72. package/lib/typescript/fabric/gamma/stack/StackHeaderConfigAndroidNativeComponent.d.ts.map +1 -1
  73. package/lib/typescript/fabric/tabs/TabsHostAndroidNativeComponent.d.ts +4 -4
  74. package/lib/typescript/fabric/tabs/TabsHostAndroidNativeComponent.d.ts.map +1 -1
  75. package/lib/typescript/fabric/tabs/TabsHostIOSNativeComponent.d.ts +4 -4
  76. package/lib/typescript/fabric/tabs/TabsHostIOSNativeComponent.d.ts.map +1 -1
  77. package/lib/typescript/flags.d.ts +1 -0
  78. package/lib/typescript/flags.d.ts.map +1 -1
  79. package/package.json +1 -1
  80. package/src/components/gamma/split/SplitHost.types.ts +1 -1
  81. package/src/components/gamma/stack/header/StackHeaderConfig.android.tsx +72 -2
  82. package/src/components/gamma/stack/header/StackHeaderConfig.android.types.ts +183 -8
  83. package/src/components/gamma/stack/header/StackHeaderConfig.types.ts +37 -0
  84. package/src/components/gamma/stack/header/android/StackHeaderSubview.android.types.ts +1 -1
  85. package/src/components/gamma/stack/host/StackHost.types.ts +1 -1
  86. package/src/components/safe-area/SafeAreaView.web.tsx +1 -1
  87. package/src/components/tabs/host/TabsHost.android.tsx +2 -2
  88. package/src/components/tabs/host/TabsHost.ios.tsx +2 -2
  89. package/src/components/tabs/host/TabsHost.types.ts +27 -17
  90. package/src/components/tabs/index.ts +1 -1
  91. package/src/fabric/gamma/stack/StackHeaderConfigAndroidNativeComponent.ts +6 -0
  92. package/src/fabric/tabs/TabsHostAndroidNativeComponent.ts +4 -4
  93. package/src/fabric/tabs/TabsHostIOSNativeComponent.ts +4 -4
  94. package/src/flags.ts +1 -0
  95. package/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/container/TabsContainerOps.kt +0 -7
  96. package/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/container/TabsNavState.kt +0 -43
@@ -3,10 +3,12 @@
3
3
  #import <React/RCTLog.h>
4
4
  #import <objc/message.h>
5
5
  #import <objc/runtime.h>
6
+ #import <limits>
6
7
  #import "NSString+RNSUtility.h"
7
8
  #import "RNSLog.h"
8
9
  #import "RNSScreenWindowTraits.h"
9
10
  #import "RNSTabsHostComponentView.h"
11
+ #import "RNSTabsNavigationStateObserverRegistry.h"
10
12
 
11
13
  #define RNS_MORE_NAVIGATION_CONTROLLER_AVAILABLE !TARGET_OS_TV && !TARGET_OS_VISION
12
14
 
@@ -34,16 +36,17 @@
34
36
  * (reached via UIKit's `tabBarController` property on the parent chain)
35
37
  * to check whether the push should be prevented (e.g. due to `preventNativeSelection`).
36
38
  */
37
- static void
38
- rns_pushViewController(__unsafe_unretained id self, SEL _cmd, UIViewController *viewController, BOOL animated)
39
+ static void rns_pushViewController(__unsafe_unretained id self,
40
+ SEL _cmd,
41
+ UIViewController *viewController,
42
+ BOOL animated)
39
43
  {
40
44
  UITabBarController *rawTabBarController = static_cast<UIViewController *>(self).tabBarController;
41
45
 
42
- RCTAssert(
43
- [rawTabBarController isKindOfClass:RNSTabBarController.class],
44
- @"[RNScreens] Expected tabBarController to be of class %@, got: %@",
45
- RNSTabBarController.class,
46
- rawTabBarController.class);
46
+ RCTAssert([rawTabBarController isKindOfClass:RNSTabBarController.class],
47
+ @"[RNScreens] Expected tabBarController to be of class %@, got: %@",
48
+ RNSTabBarController.class,
49
+ rawTabBarController.class);
47
50
  RNSTabBarController *tabBarController = static_cast<RNSTabBarController *>(rawTabBarController);
48
51
 
49
52
  if ([tabBarController moreNavigationController:self shouldPushViewController:viewController]) {
@@ -73,12 +76,14 @@ rns_pushViewController(__unsafe_unretained id self, SEL _cmd, UIViewController *
73
76
  /// This property is nullable until first container update. Later it MUST NOT be nil.
74
77
  RNSTabsNavigationState *_Nullable _lastUINavigationState;
75
78
 
76
- RNSTabsNavigationState *_Nullable _pendingOperation;
79
+ RNSTabsNavigationStateUpdateRequest *_Nullable _pendingStateUpdate;
77
80
 
78
81
  /// When YES, the controller is inside an explicit selection-changing code path (container update,
79
82
  /// delegate handling). Setter overrides skip reconciliation while this flag is set.
80
83
  BOOL _isHandlingExplicitSelectionUpdate;
81
84
 
85
+ RNSTabsNavigationStateObserverRegistry *_observerRegistry;
86
+
82
87
  #if !RCT_NEW_ARCH_ENABLED
83
88
  BOOL _isControllerFlushBlockScheduled;
84
89
  #endif // !RCT_NEW_ARCH_ENABLED
@@ -91,8 +96,9 @@ rns_pushViewController(__unsafe_unretained id self, SEL _cmd, UIViewController *
91
96
  _tabBarAppearanceCoordinator = [RNSTabBarAppearanceCoordinator new];
92
97
  _tabsHostComponentView = nil;
93
98
  _navigationState = nil;
94
- _pendingOperation = nil;
99
+ _pendingStateUpdate = nil;
95
100
  _shouldProgressStateOnMoreNavigationControllerPush = NO;
101
+ _observerRegistry = [RNSTabsNavigationStateObserverRegistry new];
96
102
 
97
103
  // Delegate field retains weakly, no risk of cycle.
98
104
  self.delegate = self;
@@ -104,6 +110,41 @@ rns_pushViewController(__unsafe_unretained id self, SEL _cmd, UIViewController *
104
110
  return self;
105
111
  }
106
112
 
113
+ #pragma mark - Public API
114
+
115
+ - (void)submitSelectionOfTabsScreenWithKey:(nonnull NSString *)screenKey
116
+ {
117
+ RCTAssert(screenKey != nil, @"[RNScreens] Requested screenKey MUST NOT be nil");
118
+ int baseProvenance = _navigationState != nil ? _navigationState.provenance : std::numeric_limits<int>::min();
119
+ RNSTabsNavigationStateUpdateRequest *request =
120
+ [RNSTabsNavigationStateUpdateRequest requestWithSelectedScreenKey:screenKey
121
+ baseProvenance:baseProvenance
122
+ actionOrigin:RNSTabsActionOriginProgrammaticNative];
123
+ [self setPendingNavigationStateUpdate:request];
124
+ }
125
+
126
+ - (void)flushPendingUpdates
127
+ {
128
+ [self performContainerUpdate];
129
+ }
130
+
131
+ - (BOOL)addNavigationStateObserver:(id<RNSTabsNavigationStateObserver>)observer
132
+ {
133
+ return [_observerRegistry addObserver:observer];
134
+ }
135
+
136
+ - (BOOL)removeNavigationStateObserver:(id<RNSTabsNavigationStateObserver>)observer
137
+ {
138
+ return [_observerRegistry removeObserver:observer];
139
+ }
140
+
141
+ - (void)tearDown
142
+ {
143
+ [_observerRegistry clear];
144
+ _pendingStateUpdate = nil;
145
+ _tabsHostComponentView = nil;
146
+ }
147
+
107
148
  - (instancetype)initWithTabsHostComponentView:(nullable RNSTabsHostComponentView *)tabsHostComponentView
108
149
  {
109
150
  if (self = [self init]) {
@@ -146,9 +187,9 @@ rns_pushViewController(__unsafe_unretained id self, SEL _cmd, UIViewController *
146
187
 
147
188
  #pragma mark - Signals
148
189
 
149
- - (void)setPendingNavigationStateUpdate:(nullable RNSTabsNavigationState *)navState
190
+ - (void)setPendingNavigationStateUpdate:(nullable RNSTabsNavigationStateUpdateRequest *)stateUpdate
150
191
  {
151
- _pendingOperation = navState;
192
+ _pendingStateUpdate = stateUpdate;
152
193
  }
153
194
 
154
195
  - (void)childViewControllersHaveChangedTo:(NSArray<RNSTabsScreenViewController *> *)reactChildControllers
@@ -232,11 +273,10 @@ rns_pushViewController(__unsafe_unretained id self, SEL _cmd, UIViewController *
232
273
 
233
274
  UIViewController *currSelectedViewController = self.selectedViewController;
234
275
 
235
- RCTAssert(
236
- ![NSString rnscreens_isBlankOrNull:screenKey],
237
- @"[RNScreens] The screenKey MUST NOT be null if the view controller is not null");
276
+ RCTAssert(![NSString rnscreens_isBlankOrNull:screenKey],
277
+ @"[RNScreens] The screenKey MUST NOT be null if the view controller is not null");
238
278
 
239
- [self progressNavigationState:screenKey withSource:RNSTabsNavigationStateUpdateSourceExternal];
279
+ [self progressNavigationState:screenKey withOrigin:RNSTabsActionOriginProgrammaticJs];
240
280
 
241
281
  if (currSelectedViewController == nextSelectedViewController) {
242
282
  return YES;
@@ -254,14 +294,13 @@ rns_pushViewController(__unsafe_unretained id self, SEL _cmd, UIViewController *
254
294
  */
255
295
  - (void)updateNavigationStateOnModelUpdate
256
296
  {
257
- [self progressNavigationState:[self screenKeyForSelectedViewController]
258
- withSource:RNSTabsNavigationStateUpdateSourceUser];
297
+ [self progressNavigationState:[self screenKeyForSelectedViewController] withOrigin:RNSTabsActionOriginUser];
259
298
  }
260
299
 
261
300
  - (void)userDidRepeatViewControllerSelection:(nonnull UIViewController *)viewController
262
301
  {
263
- RCTAssert(
264
- self.selectedViewController == viewController, @"[RNScreens] Expected UIKit to update selectedViewController");
302
+ RCTAssert(self.selectedViewController == viewController,
303
+ @"[RNScreens] Expected UIKit to update selectedViewController");
265
304
 
266
305
  if ([self isSelectedViewControllerTheMoreNavigationController]) {
267
306
  // We don't want to run neither state update nor side effects.
@@ -277,15 +316,15 @@ rns_pushViewController(__unsafe_unretained id self, SEL _cmd, UIViewController *
277
316
  [[RNSTabsNavigationStateUpdateContext alloc] initWithNavState:_navigationState
278
317
  isRepeated:YES
279
318
  hasTriggeredSpecialEffect:repeatedSelectionHandledBySpecialEffect
280
- isNativeAction:YES];
281
- [self.tabsHostComponentView tabBarController:self didUpdateStateTo:_navigationState withContext:updateContext];
319
+ actionOrigin:RNSTabsActionOriginUser];
320
+ [_observerRegistry emitDidUpdateStateTo:_navigationState withContext:updateContext sender:self];
282
321
  }
283
322
 
284
323
  - (void)userDidSelectViewController:(nonnull UIViewController *)viewController
285
324
  {
286
325
  // At this moment the `UITabBarController` model is already updated.
287
- RCTAssert(
288
- self.selectedViewController == viewController, @"[RNScreens] Expected UIKit to update selectedViewController");
326
+ RCTAssert(self.selectedViewController == viewController,
327
+ @"[RNScreens] Expected UIKit to update selectedViewController");
289
328
 
290
329
  if ([self isSelectedViewControllerTheMoreNavigationController]) {
291
330
  [self disableNavigationBarInMoreNavigationController];
@@ -293,20 +332,20 @@ rns_pushViewController(__unsafe_unretained id self, SEL _cmd, UIViewController *
293
332
 
294
333
  // We don't want to progress state in case a user selected the more navigation controller.
295
334
  // Instead, we emit a dedicated event so JS knows the More tab was tapped.
296
- [self.tabsHostComponentView tabBarController:self didSelectMoreTabWithCurrentState:_navigationState];
335
+ [_observerRegistry emitDidSelectMoreTabWithCurrentState:_navigationState sender:self];
297
336
  } else {
298
337
  [self updateNavigationStateOnModelUpdate];
299
338
  auto *updateContext = [[RNSTabsNavigationStateUpdateContext alloc] initWithNavState:_navigationState
300
339
  isRepeated:NO
301
340
  hasTriggeredSpecialEffect:NO
302
- isNativeAction:YES];
303
- [self.tabsHostComponentView tabBarController:self didUpdateStateTo:_navigationState withContext:updateContext];
341
+ actionOrigin:RNSTabsActionOriginUser];
342
+ [_observerRegistry emitDidUpdateStateTo:_navigationState withContext:updateContext sender:self];
304
343
  }
305
344
  }
306
345
 
307
346
  - (void)onDidPreventUserFromSelectingViewControllerWithKey:(nonnull NSString *)screenKey
308
347
  {
309
- [self.tabsHostComponentView tabBarController:self preventedSelectionOf:screenKey currentState:_navigationState];
348
+ [_observerRegistry emitPreventedSelectionOf:screenKey currentState:_navigationState sender:self];
310
349
  }
311
350
 
312
351
  - (BOOL)shouldPreventNativeTabSelection:(nonnull UIViewController *)nextViewController
@@ -331,11 +370,10 @@ rns_pushViewController(__unsafe_unretained id self, SEL _cmd, UIViewController *
331
370
  RCTAssert(tabBarController == self, @"[RNScreens] Unexpected type of controller: %@", tabBarController.class);
332
371
 
333
372
  // Can be UINavigationController in case of MoreNavigationController
334
- RCTAssert(
335
- [viewController isKindOfClass:RNSTabsScreenViewController.class] ||
336
- [viewController isKindOfClass:UINavigationController.class],
337
- @"[RNScreens] Unexpected type of controller: %@",
338
- viewController.class);
373
+ RCTAssert([viewController isKindOfClass:RNSTabsScreenViewController.class] ||
374
+ [viewController isKindOfClass:UINavigationController.class],
375
+ @"[RNScreens] Unexpected type of controller: %@",
376
+ viewController.class);
339
377
 
340
378
  // TODO: handle enforcing orientation with natively-driven tabs
341
379
 
@@ -384,11 +422,10 @@ rns_pushViewController(__unsafe_unretained id self, SEL _cmd, UIViewController *
384
422
  RCTAssert(self == tabBarController, @"[RNScreens] Unexpected type of controller: %@", tabBarController.class);
385
423
 
386
424
  // Can be UINavigationController in case of MoreNavigationController
387
- RCTAssert(
388
- [viewController isKindOfClass:RNSTabsScreenViewController.class] ||
389
- [viewController isKindOfClass:UINavigationController.class],
390
- @"[RNScreens] Unexpected type of controller: %@",
391
- viewController.class);
425
+ RCTAssert([viewController isKindOfClass:RNSTabsScreenViewController.class] ||
426
+ [viewController isKindOfClass:UINavigationController.class],
427
+ @"[RNScreens] Unexpected type of controller: %@",
428
+ viewController.class);
392
429
 
393
430
  [self userDidSelectViewController:viewController];
394
431
  _isHandlingExplicitSelectionUpdate = NO;
@@ -401,10 +438,9 @@ rns_pushViewController(__unsafe_unretained id self, SEL _cmd, UIViewController *
401
438
  animated:(BOOL)animated
402
439
  {
403
440
  #if RNS_MORE_NAVIGATION_CONTROLLER_AVAILABLE
404
- RCTAssert(
405
- self.moreNavigationController == navigationController,
406
- @"[RNScreens] Unexpected view controller called delegate method: %@",
407
- navigationController);
441
+ RCTAssert(self.moreNavigationController == navigationController,
442
+ @"[RNScreens] Unexpected view controller called delegate method: %@",
443
+ navigationController);
408
444
 
409
445
  // The root view controller is of different type.
410
446
  if ([viewController isKindOfClass:RNSTabsScreenViewController.class] &&
@@ -439,60 +475,58 @@ rns_pushViewController(__unsafe_unretained id self, SEL _cmd, UIViewController *
439
475
 
440
476
  - (void)updateSelectedViewControllerIfNeeded
441
477
  {
442
- if (_pendingOperation != nil) {
478
+ if (_pendingStateUpdate != nil) {
443
479
  [self updateSelectedViewController];
444
480
  }
445
481
  }
446
482
 
447
483
  - (void)updateSelectedViewController
448
484
  {
449
- if (_pendingOperation == nil || self.viewControllers.count == 0) {
485
+ if (_pendingStateUpdate == nil || self.viewControllers.count == 0) {
450
486
  return;
451
487
  }
452
488
 
453
489
  RNSLog(@"TabBarCtrl updateSelectedViewController");
454
490
  [self updateSelectedViewControllerInner];
455
- _pendingOperation = nil;
491
+ _pendingStateUpdate = nil;
456
492
  }
457
493
 
458
494
  /**
459
495
  * NEVER call this method directly. Call the proper function `updateSelectedViewController`
460
496
  *
461
- * The logic is extracted to an inner method to correctly manage _pendingOperation cleanup.
497
+ * The logic is extracted to an inner method to correctly manage `_pendingStateUpdate` cleanup.
462
498
  */
463
499
  - (void)updateSelectedViewControllerInner
464
500
  {
465
501
  UIViewController *_Nonnull currSelectedViewController = self.selectedViewController;
466
502
 
467
- NSString *_Nonnull nextSelectedViewControllerKey = _pendingOperation.selectedScreenKey;
503
+ NSString *_Nonnull nextSelectedViewControllerKey = _pendingStateUpdate.selectedScreenKey;
468
504
  UIViewController *nextSelectedViewController = [self findChildViewControllerForKey:nextSelectedViewControllerKey];
469
505
 
470
- RCTAssert(
471
- nextSelectedViewController != nil,
472
- @"[RNScreens] Failed to determine next selected view controller for key: %@",
473
- nextSelectedViewControllerKey);
474
-
475
- RCTAssert(
476
- [nextSelectedViewController isKindOfClass:RNSTabsScreenViewController.class],
477
- @"[RNScreens] nextSelectedViewController MUST be %@, got: %@",
478
- RNSTabsScreenViewController.class,
479
- nextSelectedViewController.class);
480
-
481
- if (self.rejectStaleNavigationStateUpdates && [self isNavigationStateUpdateStale:_pendingOperation]) {
482
- [self.tabsHostComponentView tabBarController:self
483
- rejectedStateUpdateTo:_pendingOperation
484
- currentState:_navigationState
485
- withReason:RNSTabsNavigationStateRejectionReasonStale];
506
+ RCTAssert(nextSelectedViewController != nil,
507
+ @"[RNScreens] Failed to determine next selected view controller for key: %@",
508
+ nextSelectedViewControllerKey);
509
+
510
+ RCTAssert([nextSelectedViewController isKindOfClass:RNSTabsScreenViewController.class],
511
+ @"[RNScreens] nextSelectedViewController MUST be %@, got: %@",
512
+ RNSTabsScreenViewController.class,
513
+ nextSelectedViewController.class);
514
+
515
+ if (self.rejectStaleNavigationStateUpdates && [self isNavigationStateUpdateStale:_pendingStateUpdate]) {
516
+ [_observerRegistry emitRejectedStateUpdate:_pendingStateUpdate
517
+ currentState:_navigationState
518
+ withReason:RNSTabsNavigationStateRejectionReasonStale
519
+ sender:self];
486
520
  return;
487
521
  }
488
522
 
489
523
  if (currSelectedViewController == nextSelectedViewController && _navigationState != nil) {
490
524
  // Nothing to do, we don't allow for programmatic repeat selection, unless
491
525
  // we're during first render.
492
- [self.tabsHostComponentView tabBarController:self
493
- rejectedStateUpdateTo:_pendingOperation
494
- currentState:_navigationState
495
- withReason:RNSTabsNavigationStateRejectionReasonRepeated];
526
+ [_observerRegistry emitRejectedStateUpdate:_pendingStateUpdate
527
+ currentState:_navigationState
528
+ withReason:RNSTabsNavigationStateRejectionReasonRepeated
529
+ sender:self];
496
530
  return;
497
531
  }
498
532
 
@@ -517,8 +551,8 @@ rns_pushViewController(__unsafe_unretained id self, SEL _cmd, UIViewController *
517
551
  [[RNSTabsNavigationStateUpdateContext alloc] initWithNavState:_navigationState
518
552
  isRepeated:NO
519
553
  hasTriggeredSpecialEffect:NO
520
- isNativeAction:NO];
521
- [self.tabsHostComponentView tabBarController:self didUpdateStateTo:_navigationState withContext:context];
554
+ actionOrigin:_pendingStateUpdate.actionOrigin];
555
+ [_observerRegistry emitDidUpdateStateTo:_navigationState withContext:context sender:self];
522
556
  }
523
557
  }
524
558
 
@@ -562,10 +596,9 @@ rns_pushViewController(__unsafe_unretained id self, SEL _cmd, UIViewController *
562
596
  return nil;
563
597
  }
564
598
  for (UIViewController *viewController in self.viewControllers) {
565
- RCTAssert(
566
- [viewController isKindOfClass:RNSTabsScreenViewController.class],
567
- @"[RNScreens] Unexpected type of controller: %@",
568
- viewController.class);
599
+ RCTAssert([viewController isKindOfClass:RNSTabsScreenViewController.class],
600
+ @"[RNScreens] Unexpected type of controller: %@",
601
+ viewController.class);
569
602
  auto *screenViewController = static_cast<RNSTabsScreenViewController *>(viewController);
570
603
  if ([screenViewController.getScreenKeyOrNull isEqualToString:screenKey]) {
571
604
  return screenViewController;
@@ -574,8 +607,7 @@ rns_pushViewController(__unsafe_unretained id self, SEL _cmd, UIViewController *
574
607
  return nil;
575
608
  }
576
609
 
577
- - (void)progressNavigationState:(nonnull NSString *)newSelectedScreenKey
578
- withSource:(RNSTabsNavigationStateUpdateSource)updateSource
610
+ - (void)progressNavigationState:(nonnull NSString *)newSelectedScreenKey withOrigin:(RNSTabsActionOrigin)origin
579
611
  {
580
612
  RCTAssert(newSelectedScreenKey != nil, @"[RNScreens] newSelectedScreenKey MUST NOT be nil");
581
613
 
@@ -587,7 +619,7 @@ rns_pushViewController(__unsafe_unretained id self, SEL _cmd, UIViewController *
587
619
  _navigationState = [RNSTabsNavigationState stateWithSelectedScreenKey:newSelectedScreenKey
588
620
  provenance:_navigationState.provenance + 1];
589
621
 
590
- if (updateSource != RNSTabsNavigationStateUpdateSourceExternal) {
622
+ if (origin != RNSTabsActionOriginProgrammaticJs) {
591
623
  _lastUINavigationState = [_navigationState cloneState];
592
624
  }
593
625
  }
@@ -598,20 +630,18 @@ rns_pushViewController(__unsafe_unretained id self, SEL _cmd, UIViewController *
598
630
  */
599
631
  - (RNSTabsScreenViewController *)selectedScreenViewController
600
632
  {
601
- RCTAssert(
602
- [self.selectedViewController isKindOfClass:RNSTabsScreenViewController.class],
603
- @"[RNScreens] Unexpected type of selectedViewController: %@",
604
- self.selectedViewController.class);
633
+ RCTAssert([self.selectedViewController isKindOfClass:RNSTabsScreenViewController.class],
634
+ @"[RNScreens] Unexpected type of selectedViewController: %@",
635
+ self.selectedViewController.class);
605
636
  return static_cast<RNSTabsScreenViewController *>(self.selectedViewController);
606
637
  }
607
638
 
608
639
  - (nonnull NSString *)screenKeyForViewController:(nonnull UIViewController *)viewController
609
640
  {
610
- RCTAssert(
611
- [viewController isKindOfClass:RNSTabsScreenViewController.class],
612
- @"[RNScreens] Expected selected view controller to be of class %@, got: %@",
613
- RNSTabsScreenViewController.class,
614
- viewController.class);
641
+ RCTAssert([viewController isKindOfClass:RNSTabsScreenViewController.class],
642
+ @"[RNScreens] Expected selected view controller to be of class %@, got: %@",
643
+ RNSTabsScreenViewController.class,
644
+ viewController.class);
615
645
 
616
646
  auto *screenKey = static_cast<RNSTabsScreenViewController *>(viewController).getScreenKeyOrNull;
617
647
  RCTAssert(screenKey != nil, @"[RNScreens] screenKey MUST NOT be nil");
@@ -650,10 +680,9 @@ rns_pushViewController(__unsafe_unretained id self, SEL _cmd, UIViewController *
650
680
  }
651
681
 
652
682
  if (![self.selectedViewController isKindOfClass:RNSTabsScreenViewController.class]) {
653
- RCTAssert(
654
- NO,
655
- @"[RNScreens] Unexpected controller type during state reconciliation: %@",
656
- self.selectedViewController.class);
683
+ RCTAssert(NO,
684
+ @"[RNScreens] Unexpected controller type during state reconciliation: %@",
685
+ self.selectedViewController.class);
657
686
  return;
658
687
  }
659
688
 
@@ -662,25 +691,24 @@ rns_pushViewController(__unsafe_unretained id self, SEL _cmd, UIViewController *
662
691
  return;
663
692
  }
664
693
 
665
- RNSLog(
666
- @"TabBarCtrl reconcileNavigationStateWithUIKitState: %@ -> %@",
667
- _navigationState.selectedScreenKey,
668
- selectedScreenKey);
669
- [self progressNavigationState:selectedScreenKey withSource:RNSTabsNavigationStateUpdateSourceImplicit];
694
+ RNSLog(@"TabBarCtrl reconcileNavigationStateWithUIKitState: %@ -> %@",
695
+ _navigationState.selectedScreenKey,
696
+ selectedScreenKey);
697
+ [self progressNavigationState:selectedScreenKey withOrigin:RNSTabsActionOriginImplicit];
670
698
 
671
699
  auto *context = [[RNSTabsNavigationStateUpdateContext alloc] initWithNavState:_navigationState
672
700
  isRepeated:NO
673
701
  hasTriggeredSpecialEffect:NO
674
- isNativeAction:YES];
675
- [self.tabsHostComponentView tabBarController:self didUpdateStateTo:_navigationState withContext:context];
702
+ actionOrigin:RNSTabsActionOriginImplicit];
703
+ [_observerRegistry emitDidUpdateStateTo:_navigationState withContext:context sender:self];
676
704
  }
677
705
 
678
706
  /**
679
707
  * This function assumes that the source of the state is NOT user. In current model, user update is never stale.
680
708
  */
681
- - (BOOL)isNavigationStateUpdateStale:(nullable RNSTabsNavigationState *)newState
709
+ - (BOOL)isNavigationStateUpdateStale:(nullable RNSTabsNavigationStateUpdateRequest *)stateUpdate
682
710
  {
683
- if (newState == nil) {
711
+ if (stateUpdate == nil) {
684
712
  return YES;
685
713
  }
686
714
 
@@ -688,7 +716,7 @@ rns_pushViewController(__unsafe_unretained id self, SEL _cmd, UIViewController *
688
716
  return NO;
689
717
  }
690
718
 
691
- return newState.provenance < _lastUINavigationState.provenance;
719
+ return stateUpdate.baseProvenance < _lastUINavigationState.provenance;
692
720
  }
693
721
 
694
722
  #pragma mark-- More Navigation Controller
@@ -754,10 +782,9 @@ rns_pushViewController(__unsafe_unretained id self, SEL _cmd, UIViewController *
754
782
  // We quietly assume here, that the root view controller is the `UIMoreListViewController`.
755
783
  if (shouldRespectSelectionPrevention) {
756
784
  UIViewController *topViewController = self.moreNavigationController.topViewController;
757
- RCTAssert(
758
- [topViewController isKindOfClass:RNSTabsScreenViewController.class],
759
- @"[RNScreens] Unexpected type of view controller on moreNavigationControllerStack: %@",
760
- topViewController.class);
785
+ RCTAssert([topViewController isKindOfClass:RNSTabsScreenViewController.class],
786
+ @"[RNScreens] Unexpected type of view controller on moreNavigationControllerStack: %@",
787
+ topViewController.class);
761
788
  RNSTabsScreenViewController *screenController = static_cast<RNSTabsScreenViewController *>(topViewController);
762
789
  if (screenController.isPreventNativeSelectionEnabled) {
763
790
  return [self popToRootMoreNavigationController:self.moreNavigationController animated:shouldAnimate];
@@ -785,9 +812,8 @@ rns_pushViewController(__unsafe_unretained id self, SEL _cmd, UIViewController *
785
812
  }
786
813
 
787
814
  auto *poppedViewControllers = [moreNavigationController popToRootViewControllerAnimated:animated];
788
- RCTAssert(
789
- poppedViewControllers != nil && poppedViewControllers.count == 1,
790
- @"[RNScreens] Expected exactly one view controller to be popped");
815
+ RCTAssert(poppedViewControllers != nil && poppedViewControllers.count == 1,
816
+ @"[RNScreens] Expected exactly one view controller to be popped");
791
817
  return [poppedViewControllers firstObject];
792
818
  }
793
819
 
@@ -842,11 +868,10 @@ rns_pushViewController(__unsafe_unretained id self, SEL _cmd, UIViewController *
842
868
  RCTAssert(dynamicSubclass != nil, @"[RNScreens] Failed to allocate dynamic subclass of %s", currentClassName);
843
869
 
844
870
  Method pushMethod = class_getInstanceMethod(currentClass, @selector(pushViewController:animated:));
845
- class_addMethod(
846
- dynamicSubclass,
847
- @selector(pushViewController:animated:),
848
- (IMP)rns_pushViewController,
849
- method_getTypeEncoding(pushMethod));
871
+ class_addMethod(dynamicSubclass,
872
+ @selector(pushViewController:animated:),
873
+ (IMP)rns_pushViewController,
874
+ method_getTypeEncoding(pushMethod));
850
875
 
851
876
  objc_registerClassPair(dynamicSubclass);
852
877
  }
@@ -982,9 +1007,8 @@ rns_pushViewController(__unsafe_unretained id self, SEL _cmd, UIViewController *
982
1007
  }
983
1008
  #endif // RNS_IPHONE_OS_VERSION_AVAILABLE(17_0)
984
1009
 
985
- RCTAssert(
986
- self.parentViewController != nil,
987
- @"[RNScreens] Expected non-null parent view controller for layout direction update.");
1010
+ RCTAssert(self.parentViewController != nil,
1011
+ @"[RNScreens] Expected non-null parent view controller for layout direction update.");
988
1012
  [self.parentViewController
989
1013
  setOverrideTraitCollection:[UITraitCollection
990
1014
  traitCollectionWithLayoutDirection:self.tabsHostComponentView.layoutDirection]
@@ -26,14 +26,13 @@ NS_ASSUME_NONNULL_BEGIN
26
26
  * 2. provider of React state & props for the tab bar controller
27
27
  * 3. two way communication channel with React (commands & events)
28
28
  */
29
- @interface RNSTabsHostComponentView : RNSReactBaseView <
30
- RNSScreenContainerDelegate,
31
- RNSTabBarControllerDelegate
29
+ @interface RNSTabsHostComponentView : RNSReactBaseView <RNSScreenContainerDelegate,
30
+ RNSTabsNavigationStateObserver
32
31
  #if !RCT_NEW_ARCH_ENABLED
33
- ,
34
- RCTInvalidating
32
+ ,
33
+ RCTInvalidating
35
34
  #endif
36
- >
35
+ >
37
36
 
38
37
  #if !RCT_NEW_ARCH_ENABLED
39
38
  - (instancetype)initWithFrame:(CGRect)frame reactImageLoader:(RCTImageLoader *)imageLoader;
@@ -48,9 +47,9 @@ NS_ASSUME_NONNULL_BEGIN
48
47
  @interface RNSTabsHostComponentView ()
49
48
 
50
49
  /**
51
- * Last navigation state requested by JS. Will be nonnull after first prop update.
50
+ * Last navigation state update requested by JS. Will be nonnull after first prop update.
52
51
  */
53
- @property (nonatomic, strong, readonly, nullable) RNSTabsNavigationState *navState;
52
+ @property (nonatomic, strong, readonly, nullable) RNSTabsNavigationStateUpdateRequest *navStateRequest;
54
53
 
55
54
  @property (nonatomic, readonly) BOOL rejectStaleNavStateUpdates;
56
55