first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,380 @@
#import "RNSBarButtonItem.h"
#import <React/RCTConvert.h>
#import <React/RCTFont.h>
#import "RNSDefines.h"
#import "RNSImageLoadingHelper.h"
static UIMenuOptions RNSMakeUIMenuOptionsFromConfig(NSDictionary *config);
@implementation RNSBarButtonItem {
NSString *_buttonId;
RNSBarButtonItemAction _itemAction;
}
- (instancetype)initWithConfig:(NSDictionary<NSString *, id> *)dict
action:(RNSBarButtonItemAction)action
menuAction:(RNSBarButtonMenuItemAction)menuAction
imageLoader:(RCTImageLoader *)imageLoader
{
self = [super init];
if (!self) {
return self;
}
NSString *title = dict[@"title"];
NSDictionary *imageSourceObj = dict[@"imageSource"];
NSDictionary *templateSourceObj = dict[@"templateSource"];
NSString *sfSymbolName = dict[@"sfSymbolName"];
NSString *xcassetName = dict[@"xcassetName"];
if (imageSourceObj != nil || templateSourceObj != nil) {
BOOL isTemplate = imageSourceObj != nil ? NO : YES;
NSDictionary *source = imageSourceObj != nil ? imageSourceObj : templateSourceObj;
[RNSImageLoadingHelper loadImageSyncIfPossibleFromJsonSource:source
withImageLoader:imageLoader
asTemplate:isTemplate
completionBlock:^(UIImage *image) {
self.image = image;
}];
} else if (sfSymbolName != nil) {
self.image = [UIImage systemImageNamed:sfSymbolName];
} else if (xcassetName != nil) {
self.image = [UIImage imageNamed:xcassetName];
}
if (title != nil) {
self.title = title;
NSDictionary *titleStyle = dict[@"titleStyle"];
if (titleStyle != nil) {
[self setTitleStyleFromConfig:titleStyle];
}
}
id tintColorObj = dict[@"tintColor"];
if (tintColorObj) {
self.tintColor = [RCTConvert UIColor:tintColorObj];
}
#if !TARGET_OS_TV
NSNumber *selectedNum = dict[@"selected"];
if (selectedNum != nil) {
self.selected = [selectedNum boolValue];
}
#endif
NSNumber *disabledNum = dict[@"disabled"];
if (disabledNum != nil) {
self.enabled = ![disabledNum boolValue];
}
NSNumber *width = dict[@"width"];
if (width) {
self.width = [width doubleValue];
}
#if !TARGET_OS_TV || __TV_OS_VERSION_MAX_ALLOWED >= 170000
if (@available(tvOS 17.0, *)) {
NSNumber *changesSelectionAsPrimaryActionNum = dict[@"changesSelectionAsPrimaryAction"];
if (changesSelectionAsPrimaryActionNum != nil) {
self.changesSelectionAsPrimaryAction = [changesSelectionAsPrimaryActionNum boolValue];
}
}
#endif
#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)
if (@available(iOS 26.0, *)) {
NSNumber *hidesSharedBackgroundNum = dict[@"hidesSharedBackground"];
if (hidesSharedBackgroundNum != nil) {
self.hidesSharedBackground = [hidesSharedBackgroundNum boolValue];
}
NSNumber *sharesBackgroundNum = dict[@"sharesBackground"];
if (sharesBackgroundNum != nil) {
self.sharesBackground = [sharesBackgroundNum boolValue];
}
NSString *identifier = dict[@"identifier"];
if (identifier != nil) {
self.identifier = identifier;
}
NSDictionary *badgeConfig = dict[@"badge"];
if (badgeConfig != nil) {
[self setBadgeFromConfig:badgeConfig];
}
}
#endif
NSString *variant = dict[@"variant"];
if (variant) {
if ([variant isEqualToString:@"done"]) {
self.style = UIBarButtonItemStyleDone;
} else if ([variant isEqualToString:@"prominent"]) {
#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)
if (@available(iOS 26.0, *)) {
self.style = UIBarButtonItemStyleProminent;
}
#endif
} else {
self.style = UIBarButtonItemStylePlain;
}
}
if (dict[@"accessibilityLabel"]) {
self.accessibilityLabel = dict[@"accessibilityLabel"];
}
if (dict[@"accessibilityHint"]) {
self.accessibilityHint = dict[@"accessibilityHint"];
}
#if !TARGET_OS_TV || __TV_OS_VERSION_MAX_ALLOWED >= 170000
if (@available(tvOS 17.0, *)) {
NSDictionary *menu = dict[@"menu"];
if (menu) {
self.menu = [[self class] initUIMenuWithDict:menu menuAction:menuAction];
}
}
#endif
NSString *buttonId = dict[@"buttonId"];
if (buttonId && action) {
self.target = self;
self.action = @selector(handleBarButtonItemPress:);
_itemAction = action;
_buttonId = buttonId;
}
return self;
}
+ (UIMenu *)initUIMenuWithDict:(NSDictionary<NSString *, id> *)dict menuAction:(RNSBarButtonMenuItemAction)menuAction
{
NSArray *items = dict[@"items"];
NSMutableArray<UIMenuElement *> *elements = [NSMutableArray new];
if (items.count > 0) {
for (NSDictionary *item in items) {
NSString *menuId = item[@"menuId"];
if (menuId) {
UIAction *actionItem = [self createActionItemFromConfig:item menuAction:menuAction];
[elements addObject:actionItem];
} else {
UIMenu *childMenu = [self initUIMenuWithDict:item menuAction:menuAction];
if (childMenu) {
[elements addObject:childMenu];
}
}
}
}
NSString *title = dict[@"title"];
NSString *sfSymbolName = dict[@"sfSymbolName"];
NSString *xcassetName = dict[@"xcassetName"];
UIImage* image = nil;
if (sfSymbolName != nil) {
image = [UIImage systemImageNamed:sfSymbolName];
} else if (xcassetName != nil) {
image = [UIImage imageNamed:xcassetName];
}
return [UIMenu menuWithTitle:title
image:image
identifier:nil
options:RNSMakeUIMenuOptionsFromConfig(dict)
children:elements];
}
+ (UIAction *)createActionItemFromConfig:(NSDictionary *)dict menuAction:(RNSBarButtonMenuItemAction)menuAction
{
NSString *menuId = dict[@"menuId"];
NSString *title = dict[@"title"];
NSString *sfSymbolName = dict[@"sfSymbolName"];
NSString *xcassetName = dict[@"xcassetName"];
UIImage *image = nil;
if (sfSymbolName != nil) {
image = [UIImage systemImageNamed:sfSymbolName];
} else if (xcassetName != nil) {
image = [UIImage imageNamed:xcassetName];
}
UIAction *actionElement = [UIAction actionWithTitle:title
image:image
identifier:nil
handler:^(__kindof UIAction *_Nonnull a) {
menuAction(menuId);
}];
NSString *discoverabilityLabel = dict[@"discoverabilityLabel"];
if (discoverabilityLabel != nil) {
actionElement.discoverabilityTitle = discoverabilityLabel;
}
NSString *state = dict[@"state"];
if ([state isEqualToString:@"on"]) {
actionElement.state = UIMenuElementStateOn;
} else if ([state isEqualToString:@"off"]) {
actionElement.state = UIMenuElementStateOff;
} else if ([state isEqualToString:@"mixed"]) {
actionElement.state = UIMenuElementStateMixed;
}
NSNumber *disabled = dict[@"disabled"];
NSNumber *hidden = dict[@"hidden"];
NSNumber *destructive = dict[@"destructive"];
NSNumber *keepsMenuPresented = dict[@"keepsMenuPresented"];
if (disabled != nil && [disabled boolValue]) {
actionElement.attributes |= UIMenuElementAttributesDisabled;
}
if (hidden != nil && [hidden boolValue]) {
actionElement.attributes |= UIMenuElementAttributesHidden;
}
if (destructive != nil && [destructive boolValue]) {
actionElement.attributes |= UIMenuElementAttributesDestructive;
}
if (keepsMenuPresented != nil && [keepsMenuPresented boolValue]) {
#if RNS_IPHONE_OS_VERSION_AVAILABLE(16_0)
if (@available(iOS 16.0, *)) {
actionElement.attributes |= UIMenuElementAttributesKeepsMenuPresented;
}
#endif
#if TARGET_OS_TV && __TV_OS_VERSION_MAX_ALLOWED >= 160000
if (@available(tvOS 16.0, *)) {
actionElement.attributes |= UIMenuElementAttributesKeepsMenuPresented;
}
#endif
}
NSString *subtitle = dict[@"subtitle"];
if (subtitle != nil) {
actionElement.subtitle = subtitle;
}
return actionElement;
}
- (void)handleBarButtonItemPress:(UIBarButtonItem *)item
{
if (_itemAction && _buttonId) {
_itemAction(_buttonId);
}
}
- (void)setTitleStyleFromConfig:(NSDictionary *)titleStyle
{
NSString *fontFamily = titleStyle[@"fontFamily"];
NSNumber *fontSize = titleStyle[@"fontSize"];
NSString *fontWeight = titleStyle[@"fontWeight"];
NSMutableDictionary *attrs = [NSMutableDictionary new];
if (fontFamily || fontWeight) {
NSNumber *resolvedFontSize = fontSize;
if (!resolvedFontSize) {
#if TARGET_OS_TV
resolvedFontSize = [NSNumber numberWithDouble:17.0];
#else
resolvedFontSize = [NSNumber numberWithFloat:[UIFont labelFontSize]];
#endif
}
attrs[NSFontAttributeName] = [RCTFont updateFont:nil
withFamily:fontFamily
size:resolvedFontSize
weight:fontWeight
style:nil
variant:nil
scaleMultiplier:1.0];
} else {
CGFloat resolvedFontSize = fontSize ? [fontSize floatValue] : 0;
if (resolvedFontSize == 0) {
#if TARGET_OS_TV
resolvedFontSize = 17.0;
#else
resolvedFontSize = [UIFont labelFontSize];
#endif
}
attrs[NSFontAttributeName] = [UIFont systemFontOfSize:resolvedFontSize];
}
id titleColor = titleStyle[@"color"];
if (titleColor) {
attrs[NSForegroundColorAttributeName] = [RCTConvert UIColor:titleColor];
}
[self setTitleTextAttributes:attrs forState:UIControlStateNormal];
[self setTitleTextAttributes:attrs forState:UIControlStateHighlighted];
[self setTitleTextAttributes:attrs forState:UIControlStateDisabled];
[self setTitleTextAttributes:attrs forState:UIControlStateSelected];
[self setTitleTextAttributes:attrs forState:UIControlStateFocused];
}
#if RNS_IPHONE_OS_VERSION_AVAILABLE(26_0)
- (void)setBadgeFromConfig:(NSDictionary *)badgeObj
{
if (@available(iOS 26.0, *)) {
UIBarButtonItemBadge *badge = [UIBarButtonItemBadge badgeWithString:badgeObj[@"value"]];
NSDictionary *style = badgeObj[@"style"];
if (style) {
id colorObj = style[@"color"];
if (colorObj) {
badge.foregroundColor = [RCTConvert UIColor:colorObj];
}
id backgroundColorObj = style[@"backgroundColor"];
if (backgroundColorObj) {
badge.backgroundColor = [RCTConvert UIColor:backgroundColorObj];
}
NSString *fontFamily = style[@"fontFamily"];
NSNumber *fontSize = style[@"fontSize"];
NSString *fontWeight = style[@"fontWeight"];
if (fontSize || fontWeight) {
badge.font = [RCTFont updateFont:nil
withFamily:fontFamily
size:fontSize
weight:fontWeight
style:nil
variant:nil
scaleMultiplier:1.0];
} else {
CGFloat resolvedFontSize = fontSize ? [fontSize floatValue] : 0;
if (resolvedFontSize == 0) {
#if TARGET_OS_TV
resolvedFontSize = 17.0;
#else
resolvedFontSize = [UIFont labelFontSize];
#endif
}
badge.font = [UIFont systemFontOfSize:resolvedFontSize];
}
}
self.badge = badge;
}
}
#endif
@end
UIMenuOptions RNSMakeUIMenuOptionsFromConfig(NSDictionary *config)
{
UIMenuOptions options = 0;
NSNumber *singleSelection = config[@"singleSelection"];
NSNumber *displayAsPalette = config[@"displayAsPalette"];
NSNumber *displayInline = config[@"displayInline"];
NSNumber *destructive = config[@"destructive"];
if (singleSelection != nil && [singleSelection boolValue]) {
options |= UIMenuOptionsSingleSelection;
}
#if RNS_IPHONE_OS_VERSION_AVAILABLE(17_0)
if (@available(iOS 17.0, *)) {
if (displayAsPalette != nil && [displayAsPalette boolValue]) {
options |= UIMenuOptionsDisplayAsPalette;
}
}
#endif // RNS_IPHONE_OS_VERSION_AVAILABLE(17_0)
if (displayInline != nil && [displayInline boolValue]) {
options |= UIMenuOptionsDisplayInline;
}
if (destructive != nil && [destructive boolValue]) {
options |= UIMenuOptionsDestructive;
}
return options;
}