2217 lines · 106.1 KB · Text
RawDownload
diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.ktindex fa1b77317f609983ae2434764174072e51130550..270c81c90971b0ff3038b999973dd4d128a9d140 100644--- a/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt+++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt@@ -124,6 +124,34 @@ class ScreenStackHeaderConfigViewManager :         config.setTitle(title)     } +    // The t3 patch adds these iOS glass-chrome props to the native spec; they have+    // no Android implementation, but the codegen-generated interface requires the+    // setters to exist. No-ops keep the Android build compiling.+    override fun setSubtitle(+        config: ScreenStackHeaderConfig,+        subtitle: String?,+    ) = Unit++    override fun setLargeSubtitle(+        config: ScreenStackHeaderConfig,+        largeSubtitle: String?,+    ) = Unit++    override fun setNavigationItemStyle(+        config: ScreenStackHeaderConfig,+        navigationItemStyle: String?,+    ) = Unit++    override fun setHeaderCenterBarButtonItems(+        config: ScreenStackHeaderConfig,+        headerCenterBarButtonItems: ReadableArray?,+    ) = Unit++    override fun setHeaderToolbarItems(+        config: ScreenStackHeaderConfig,+        headerToolbarItems: ReadableArray?,+    ) = Unit+     override fun setTitleFontFamily(         config: ScreenStackHeaderConfig,         titleFontFamily: String?,diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderSubviewManager.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderSubviewManager.ktindex 64f14f0c5ade16eb9519b8780d4ce013f88fbd3a..6cb1d69f97a95759c662a6006a9626925d9fefd9 100644--- a/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderSubviewManager.kt+++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderSubviewManager.kt@@ -47,6 +47,12 @@ class ScreenStackHeaderSubviewManager :         Log.w("[RNScreens]", "hidesSharedBackground prop is not available on Android")     } +    // Bar button transition identifiers only apply on iOS.+    override fun setIdentifier(+        view: ScreenStackHeaderSubview,+        identifier: String?,+    ) = Unit+     // synchronousShadowStateUpdatesEnabled is not available on Android atm,     // however we must override their setters     override fun setSynchronousShadowStateUpdatesEnabled(diff --git a/ios/RNSBarButtonItem.h b/ios/RNSBarButtonItem.hindex a26d1254eafd89268f65d968988cf6a3b8c1e457..62eea42ac26c532801189556c5f51012c3df3a52 100644--- a/ios/RNSBarButtonItem.h+++ b/ios/RNSBarButtonItem.h@@ -15,6 +15,10 @@ typedef void (^RNSBarButtonMenuItemAction)(NSString *menuId);                         action:(RNSBarButtonItemAction)action                     menuAction:(RNSBarButtonMenuItemAction)menuAction                    imageLoader:(RCTImageLoader *)imageLoader;+++ (UIMenu *)initUIMenuWithDict:(NSDictionary<NSString *, id> *)dict+                    menuAction:(RNSBarButtonMenuItemAction)menuAction+                   imageLoader:(RCTImageLoader *)imageLoader; #endif // defined(__cplusplus)  @enddiff --git a/ios/RNSBarButtonItem.mm b/ios/RNSBarButtonItem.mmindex 0eb1f09dee82edd99e3e614938233db5cdeaebfe..73298f10807520970f625e166d7f5dd1338206f1 100644--- a/ios/RNSBarButtonItem.mm+++ b/ios/RNSBarButtonItem.mm@@ -110,6 +110,58 @@ - (instancetype)initWithConfig:(NSDictionary<NSString *, id> *)dict   if (dict[@"accessibilityHint"]) {     self.accessibilityHint = dict[@"accessibilityHint"];   }+#if !TARGET_OS_TV && RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)+  NSNumber *glassEffectNum = dict[@"glassEffect"];+  if (@available(iOS 26.0, *)) {+    if ([glassEffectNum boolValue]) {+      UIButtonConfiguration *configuration = [UIButtonConfiguration glassButtonConfiguration];+      configuration.cornerStyle = UIButtonConfigurationCornerStyleCapsule;+      UIBackgroundConfiguration *background = configuration.background;+      background.strokeWidth = 0.0;+      background.strokeColor = UIColor.clearColor;+      configuration.background = background;+      configuration.contentInsets = NSDirectionalEdgeInsetsZero;+      if (title != nil) {+        configuration.title = title;+      }++      UIButton *button = [UIButton buttonWithConfiguration:configuration primaryAction:nil];+      button.translatesAutoresizingMaskIntoConstraints = NO;+      button.enabled = self.enabled;+      button.accessibilityLabel = self.accessibilityLabel;+      button.accessibilityHint = self.accessibilityHint;++      CGFloat resolvedWidth = width != nil ? width.doubleValue : 56.0;+      [button.widthAnchor constraintEqualToConstant:resolvedWidth].active = YES;+      [button.heightAnchor constraintEqualToConstant:50.0].active = YES;++      [[self class] resolveImageFromConfig:dict+                               imageLoader:imageLoader+                           completionBlock:^(UIImage *img) {+                             UIButtonConfiguration *updatedConfiguration = button.configuration;+                             updatedConfiguration.image = img;+                             button.configuration = updatedConfiguration;+                           }];++      NSDictionary *menu = dict[@"menu"];+      if (menu) {+        button.menu = [[self class] initUIMenuWithDict:menu menuAction:menuAction imageLoader:imageLoader];+        button.showsMenuAsPrimaryAction = YES;+      } else {+        NSString *buttonId = dict[@"buttonId"];+        if (buttonId && action) {+          UIAction *pressAction = [UIAction actionWithHandler:^(__kindof UIAction *_Nonnull sender) {+            action(buttonId);+          }];+          [button addAction:pressAction forControlEvents:UIControlEventTouchUpInside];+        }+      }++      self.customView = button;+      return self;+    }+  }+#endif  #if !TARGET_OS_TV || __TV_OS_VERSION_MAX_ALLOWED >= 170000   if (@available(tvOS 17.0, *)) {diff --git a/ios/RNSScreenStackHeaderConfig.h b/ios/RNSScreenStackHeaderConfig.hindex 8a41697223f6c4b5cb2dd6d450fc58cbb3b27536..309ba2f762068dcf452c172f7c7cbf2a55d03cb2 100644--- a/ios/RNSScreenStackHeaderConfig.h+++ b/ios/RNSScreenStackHeaderConfig.h@@ -23,6 +23,8 @@ NS_ASSUME_NONNULL_BEGIN  @property (nonatomic, retain) NSString *title;+@property (nonatomic, retain) NSString *subtitle;+@property (nonatomic, retain) NSString *largeSubtitle; @property (nonatomic, retain) NSString *titleFontFamily; @property (nonatomic, retain) NSNumber *titleFontSize; @property (nonatomic, retain) NSString *titleFontWeight;@@ -47,9 +49,12 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic) BOOL backButtonInCustomView; @property (nonatomic) UISemanticContentAttribute direction; @property (nonatomic) UINavigationItemBackButtonDisplayMode backButtonDisplayMode;+@property (nonatomic) NSInteger navigationItemStyle; @property (nonatomic) RNSBlurEffectStyle blurEffect; @property (nonatomic, copy, nullable) NSArray<NSDictionary<NSString *, id> *> *headerRightBarButtonItems; @property (nonatomic, copy, nullable) NSArray<NSDictionary<NSString *, id> *> *headerLeftBarButtonItems;+@property (nonatomic, copy, nullable) NSArray<NSDictionary<NSString *, id> *> *headerCenterBarButtonItems;+@property (nonatomic, copy, nullable) NSArray<NSDictionary<NSString *, id> *> *headerToolbarItems; @property (nonatomic, readwrite) BOOL synchronousShadowStateUpdatesEnabled;  NS_ASSUME_NONNULL_ENDdiff --git a/ios/RNSScreenStackHeaderConfig.mm b/ios/RNSScreenStackHeaderConfig.mmindex 1c844846a5c66e31cfa530ca462774d2fb6bbea1..a60b96b44e85d3f4b29dc0d190b95ab60e185033 100644--- a/ios/RNSScreenStackHeaderConfig.mm+++ b/ios/RNSScreenStackHeaderConfig.mm@@ -24,11 +24,46 @@ #import "RNSSearchBar.h" #import "UINavigationBar+RNSUtility.h" +#import <objc/runtime.h>+ namespace react = facebook::react;  static const NSNumber *const DEFAULT_TITLE_FONT_SIZE = @17;++#if !TARGET_OS_TV+// Each placement keeps its own cache so changes to one side do not move live+// items on the other side into new groups during a navigation transition.+static char RNSAppliedLeadingHeaderItemsKey;+static char RNSAppliedTrailingHeaderItemsKey;+static char RNSAppliedCenterHeaderItemsKey;++// Records a placement's owner and contents, returning whether they changed.+static BOOL RNSUpdateHeaderItemsCache(UINavigationItem *navitem, const void *key, NSArray *itemsKey)+{+  if ([objc_getAssociatedObject(navitem, key) isEqual:itemsKey]) {+    return NO;+  }+  objc_setAssociatedObject(navitem, key, itemsKey, OBJC_ASSOCIATION_RETAIN_NONATOMIC);+  return YES;+}+#endif+static char RNSAppliedToolbarConfigsKey; static const NSNumber *const DEFAULT_TITLE_LARGE_FONT_SIZE = @34; +static NSInteger navigationItemStyleFromCppEquivalent(+    react::RNSScreenStackHeaderConfigNavigationItemStyle navigationItemStyle)+{+  switch (navigationItemStyle) {+    case react::RNSScreenStackHeaderConfigNavigationItemStyle::Browser:+      return UINavigationItemStyleBrowser;+    case react::RNSScreenStackHeaderConfigNavigationItemStyle::Editor:+      return UINavigationItemStyleEditor;+    case react::RNSScreenStackHeaderConfigNavigationItemStyle::Navigator:+    default:+      return UINavigationItemStyleNavigator;+  }+}+ @interface RCTImageLoader (Private) - (id<RCTImageCache>)imageCache; @end@@ -46,6 +81,9 @@ + (BOOL)rnscreens_isBlankOrNull:(NSString *)string @end  @interface RNSScreenStackHeaderConfig () <RCTMountingTransactionObserving>+#if !TARGET_OS_TV+- (NSArray<UIBarButtonItemGroup *> *)barButtonItemGroupsFromItems:(NSArray<UIBarButtonItem *> *)items;+#endif @end  @implementation RNSScreenStackHeaderConfig {@@ -80,6 +118,7 @@ - (void)initProps   self.hidden = YES;   _reactSubviews = [NSMutableArray new];   _backTitleVisible = YES;+  _navigationItemStyle = UINavigationItemStyleNavigator;   _blurEffect = RNSBlurEffectStyleNone;   _synchronousShadowStateUpdatesEnabled = YES; }@@ -507,6 +546,10 @@ + (void)updateViewController:(UIViewController *)vc    if (shouldHide) {     navitem.title = config.title;+    if (@available(iOS 26.0, *)) {+      navitem.subtitle = config.subtitle;+      navitem.largeSubtitle = config.largeSubtitle;+    }      // Setting navigation bar visibility is split to mitigate iOS 26 bug with bar button items.     [navctr setNavigationBarHidden:YES animated:animated];@@ -523,11 +566,19 @@ + (void)updateViewController:(UIViewController *)vc   }   navitem.largeTitleDisplayMode =       config.largeTitle ? UINavigationItemLargeTitleDisplayModeAlways : UINavigationItemLargeTitleDisplayModeNever;++  if (@available(iOS 16.0, *)) {+    navitem.style = (UINavigationItemStyle)config.navigationItemStyle;+  } #endif    UINavigationBarAppearance *appearance = [self buildAppearance:vc withConfig:config];   navitem.standardAppearance = appearance;   navitem.compactAppearance = appearance;+  if (@available(iOS 26.0, *)) {+    navitem.subtitle = config.subtitle;+    navitem.largeSubtitle = config.largeSubtitle;+  }  // appearance does not apply to the tvOS so we need to use lagacy customization #if TARGET_OS_TV@@ -559,8 +610,6 @@ + (void)updateViewController:(UIViewController *)vc   navitem.leftItemsSupplementBackButton = config.backButtonInCustomView; #endif   navitem.titleView = nil;-  navitem.leftBarButtonItems = nil;-  navitem.rightBarButtonItems = nil;  #if !TARGET_OS_TV   // We want to set navitem.searchController to nil only if we are sure@@ -568,22 +617,18 @@ + (void)updateViewController:(UIViewController *)vc   bool searchBarPresent = false; #endif /* !TARGET_OS_TV */ +  NSMutableArray<UIBarButtonItem *> *subviewLeftItems = [NSMutableArray array];+  NSMutableArray<UIBarButtonItem *> *subviewRightItems = [NSMutableArray array];   for (RNSScreenStackHeaderSubview *subview in config.reactSubviews) {     // This code should be kept in sync on Fabric with analogous switch statement in     // `- [RNSScreenStackHeaderConfig replaceNavigationBarViewsWithSnapshotOfSubview:]` method.     switch (subview.type) {       case RNSScreenStackHeaderSubviewTypeLeft: {-        NSArray<UIBarButtonItem *> *currentItems = navitem.leftBarButtonItems ?: @[];-        NSMutableArray<UIBarButtonItem *> *mutableItems = [currentItems mutableCopy];-        [mutableItems addObject:[subview getUIBarButtonItem]];-        navitem.leftBarButtonItems = mutableItems;+        [subviewLeftItems addObject:[subview getUIBarButtonItem]];         break;       }       case RNSScreenStackHeaderSubviewTypeRight: {-        NSArray<UIBarButtonItem *> *currentItems = navitem.rightBarButtonItems ?: @[];-        NSMutableArray<UIBarButtonItem *> *mutableItems = [currentItems mutableCopy];-        [mutableItems addObject:[subview getUIBarButtonItem]];-        navitem.rightBarButtonItems = mutableItems;+        [subviewRightItems addObject:[subview getUIBarButtonItem]];         break;       }       case RNSScreenStackHeaderSubviewTypeCenter:@@ -648,10 +693,470 @@ + (void)updateViewController:(UIViewController *)vc   // This assignment should be done after `navitem.titleView = ...` assignment (iOS 16.0 bug).   // See: https://github.com/software-mansion/react-native-screens/issues/1570 (comments)   navitem.title = config.title;-  navitem.leftBarButtonItems = [config barButtonItemsFromConfigs:config.headerLeftBarButtonItems-                                                withCurrentItems:navitem.leftBarButtonItems];-  navitem.rightBarButtonItems = [config barButtonItemsFromConfigs:config.headerRightBarButtonItems-                                                 withCurrentItems:navitem.rightBarButtonItems];+  // Retain the config in the key so its address cannot be reused while cached.+  // Items capture this config's event emitter in their press handlers, so a+  // remounted header-config view with value-equal configs must still rebuild+  // instead of dispatching presses into the old config's emitter.+  // getUIBarButtonItem is stable for the lifetime of its React header+  // subview. Include those native identities in the key so React-backed+  // items can use the same cache without reusing a group after its view was+  // replaced.+  // A bar button item can belong to only one group. Even if the item itself+  // is reused, constructing a new group removes it from the old group that+  // UIKit may still be animating. Preserve each unchanged placement's group,+  // including the empty leading group beside the system back button.+#if !TARGET_OS_TV+  if (@available(iOS 16.0, *)) {+    NSArray *headerLeftConfigs = config.headerLeftBarButtonItems ?: @[];+    NSArray *headerRightConfigs = config.headerRightBarButtonItems ?: @[];+    NSArray *headerCenterConfigs = config.headerCenterBarButtonItems ?: @[];+    if (RNSUpdateHeaderItemsCache(+            navitem, &RNSAppliedLeadingHeaderItemsKey, @[ config, headerLeftConfigs, subviewLeftItems ])) {+      NSArray *items = [config barButtonItemsFromConfigs:config.headerLeftBarButtonItems+                                      withCurrentItems:subviewLeftItems+                                        navigationItem:navitem+                              allowsSearchBarPlacement:NO];+      navitem.leadingItemGroups = [config barButtonItemGroupsFromItems:items];+    }+    if (RNSUpdateHeaderItemsCache(+            navitem, &RNSAppliedTrailingHeaderItemsKey, @[ config, headerRightConfigs, subviewRightItems ])) {+      NSArray *items = [config barButtonItemsFromConfigs:config.headerRightBarButtonItems+                                      withCurrentItems:subviewRightItems+                                        navigationItem:navitem+                              allowsSearchBarPlacement:NO];+      navitem.trailingItemGroups = [config barButtonItemGroupsFromItems:items];+    }+    if (@available(iOS 26.0, *)) {+      if (RNSUpdateHeaderItemsCache(navitem, &RNSAppliedCenterHeaderItemsKey, @[ config, headerCenterConfigs ])) {+        NSArray *items = [config barButtonItemsFromConfigs:config.headerCenterBarButtonItems+                                        withCurrentItems:@[]+                                          navigationItem:navitem+                                allowsSearchBarPlacement:NO];+        navitem.centerItemGroups = [config barButtonItemGroupsFromItems:items];+      }+    }+  } else+#endif+  {+    NSArray<UIBarButtonItem *> *leftBarButtonItems = [config barButtonItemsFromConfigs:config.headerLeftBarButtonItems+                                                                      withCurrentItems:subviewLeftItems+                                                                        navigationItem:navitem+                                                              allowsSearchBarPlacement:NO];+    NSArray<UIBarButtonItem *> *rightBarButtonItems = [config barButtonItemsFromConfigs:config.headerRightBarButtonItems+                                                                       withCurrentItems:subviewRightItems+                                                                         navigationItem:navitem+                                                               allowsSearchBarPlacement:NO];+    navitem.leftBarButtonItems = leftBarButtonItems;+    navitem.rightBarButtonItems = rightBarButtonItems;+  }+  NSDictionary<NSString *, id> *mailSearchToolbarConfig = nil;+  NSMutableArray<NSDictionary<NSString *, id> *> *nonMailSearchToolbarConfigs = [NSMutableArray array];+  for (NSDictionary<NSString *, id> *toolbarConfig in config.headerToolbarItems) {+    if (toolbarConfig[@"mailSearchToolbar"]) {+      if (mailSearchToolbarConfig == nil) {+        mailSearchToolbarConfig = toolbarConfig;+      }+    } else {+      [nonMailSearchToolbarConfigs addObject:toolbarConfig];+    }+  }+  NSArray<NSDictionary<NSString *, id> *> *navigationToolbarConfigs = [nonMailSearchToolbarConfigs copy];+  if (mailSearchToolbarConfig == nil) {+    for (NSDictionary<NSString *, id> *rightConfig in config.headerRightBarButtonItems) {+      if ([rightConfig[@"bottomMailSearchToolbar"] boolValue]) {+        mailSearchToolbarConfig = rightConfig;+        break;+      }+    }+  }++  static NSInteger const RNSMailSearchToolbarViewTag = 260628;+  UIView *chromeHostView = vc.view;+  UIView *existingMailSearchToolbar = [chromeHostView viewWithTag:RNSMailSearchToolbarViewTag];+  if (existingMailSearchToolbar == nil) {+    existingMailSearchToolbar = [vc.view viewWithTag:RNSMailSearchToolbarViewTag];+  }+  // Reuse the live toolbar when nothing it was built from changed: rebuilding+  // replaces the fallback UISearchTextField with a fresh empty one, dropping+  // the user's in-progress search text and first responder on every unrelated+  // header update. Keyed on the config instance (the button/search blocks+  // capture its event emitter), the toolbar config values, and the host width+  // (the width constraint constant is resolved from it at build time).+  static char RNSAppliedMailSearchToolbarConfigKey;+  NSArray *mailSearchToolbarKey = mailSearchToolbarConfig != nil+      ? @[ @((uintptr_t)config), mailSearchToolbarConfig, @(chromeHostView.bounds.size.width) ]+      : nil;+  BOOL reuseMailSearchToolbar = existingMailSearchToolbar != nil && mailSearchToolbarKey != nil &&+      [objc_getAssociatedObject(existingMailSearchToolbar, &RNSAppliedMailSearchToolbarConfigKey)+          isEqual:mailSearchToolbarKey];+  if (!reuseMailSearchToolbar) {+    [existingMailSearchToolbar removeFromSuperview];+  }++  if (mailSearchToolbarConfig != nil && !reuseMailSearchToolbar) {+#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)+    if (@available(iOS 26.0, *)) {+      CGFloat horizontalInset = 18.0;+      NSNumber *width = mailSearchToolbarConfig[@"width"];+      CGFloat hostWidth = chromeHostView.bounds.size.width;+      if (hostWidth <= 0.0) {+        hostWidth = vc.view.bounds.size.width;+      }+      if (hostWidth <= 0.0) {+        hostWidth = [UIScreen mainScreen].bounds.size.width;+      }+      CGFloat fallbackWidth = MIN(560.0, MAX(300.0, hostWidth - (horizontalInset * 2.0)));+      CGFloat resolvedWidth = width != nil ? width.doubleValue : fallbackWidth;+      if (hostWidth > 0.0) {+        resolvedWidth = MIN(resolvedWidth, MAX(260.0, hostWidth - 12.0));+      }++      CGFloat toolbarHeight = 48.0;+      CGFloat toolbarRadius = toolbarHeight / 2.0;+      CGFloat buttonSize = toolbarHeight;+      CGFloat sideButtonReserve = buttonSize + 8.0;+      CGFloat searchFieldHeight = 42.0;+      CGFloat toolbarBottomSpacing = 4.0;+      UIFont *searchFont = [UIFont preferredFontForTextStyle:UIFontTextStyleTitle3];+      NSDictionary<NSAttributedStringKey, id> *placeholderAttributes = @{+        NSForegroundColorAttributeName : UIColor.secondaryLabelColor,+        NSFontAttributeName : searchFont,+      };+      UIKeyboardLayoutGuide *keyboardLayoutGuide = chromeHostView.keyboardLayoutGuide;++      UIView *toolbarHost = [[UIView alloc] init];+      toolbarHost.tag = RNSMailSearchToolbarViewTag;+      objc_setAssociatedObject(+          toolbarHost,+          &RNSAppliedMailSearchToolbarConfigKey,+          mailSearchToolbarKey,+          OBJC_ASSOCIATION_RETAIN_NONATOMIC);+      toolbarHost.translatesAutoresizingMaskIntoConstraints = NO;+      [chromeHostView addSubview:toolbarHost];+      // The screen stays mounted beneath pushed routes, so its keyboard layout+      // guide can track a keyboard owned by another screen. Keep the toolbar at+      // rest unless its own search field is editing.+      NSLayoutConstraint *keyboardAvoidConstraint =+          [toolbarHost.bottomAnchor constraintEqualToAnchor:keyboardLayoutGuide.topAnchor+                                                   constant:-toolbarBottomSpacing];+      keyboardAvoidConstraint.priority = UILayoutPriorityDefaultLow;+      NSLayoutConstraint *restingBottomConstraint =+          [toolbarHost.bottomAnchor constraintEqualToAnchor:chromeHostView.safeAreaLayoutGuide.bottomAnchor+                                                   constant:-toolbarBottomSpacing];+      restingBottomConstraint.priority = UILayoutPriorityDefaultHigh;+      [NSLayoutConstraint activateConstraints:@[+        [toolbarHost.centerXAnchor constraintEqualToAnchor:chromeHostView.centerXAnchor],+        [toolbarHost.bottomAnchor constraintLessThanOrEqualToAnchor:chromeHostView.safeAreaLayoutGuide.bottomAnchor+                                                           constant:-toolbarBottomSpacing],+        keyboardAvoidConstraint,+        restingBottomConstraint,+        [toolbarHost.widthAnchor constraintEqualToConstant:resolvedWidth],+        [toolbarHost.heightAnchor constraintEqualToConstant:toolbarHeight],+      ]];+      [chromeHostView bringSubviewToFront:toolbarHost];++      UIGlassEffect *glassEffect = [UIGlassEffect effectWithStyle:UIGlassEffectStyleRegular];+      glassEffect.interactive = YES;+      UIVisualEffectView *glassView = [[UIVisualEffectView alloc] initWithEffect:glassEffect];+      glassView.clipsToBounds = YES;+      glassView.layer.cornerRadius = toolbarRadius;+      glassView.translatesAutoresizingMaskIntoConstraints = NO;+      [toolbarHost addSubview:glassView];+      BOOL hasFilterButton =+          mailSearchToolbarConfig[@"filterButtonId"] != nil || mailSearchToolbarConfig[@"filterMenu"] != nil;+      BOOL hasComposeButton =+          mailSearchToolbarConfig[@"composeButtonId"] != nil || mailSearchToolbarConfig[@"composeMenu"] != nil;+      CGFloat glassLeadingInset = hasFilterButton ? sideButtonReserve : 0.0;+      CGFloat glassTrailingInset = hasComposeButton ? -sideButtonReserve : 0.0;+      NSLayoutConstraint *glassLeadingConstraint =+          [glassView.leadingAnchor constraintEqualToAnchor:toolbarHost.leadingAnchor constant:glassLeadingInset];+      NSLayoutConstraint *glassTrailingConstraint =+          [glassView.trailingAnchor constraintEqualToAnchor:toolbarHost.trailingAnchor constant:glassTrailingInset];+      [NSLayoutConstraint activateConstraints:@[+        glassLeadingConstraint,+        glassTrailingConstraint,+        [glassView.centerYAnchor constraintEqualToAnchor:toolbarHost.centerYAnchor],+        [glassView.heightAnchor constraintEqualToConstant:toolbarHeight],+      ]];++      void (^emitButtonPress)(NSString *) = ^(NSString *buttonId) {+        auto eventEmitter = std::static_pointer_cast<const facebook::react::RNSScreenStackHeaderConfigEventEmitter>(+            config->_eventEmitter);+        if (eventEmitter && buttonId) {+          eventEmitter->onPressHeaderBarButtonItem(+              facebook::react::RNSScreenStackHeaderConfigEventEmitter::OnPressHeaderBarButtonItem{+                  .buttonId = std::string([buttonId UTF8String])});+        }+      };+

Lines 1–500 of 2217.