From e10cd978946fa2bd4de83709563bbfde58aac744 Mon Sep 17 00:00:00 2001 From: Fabrizio Bertoglio Date: Tue, 30 Jan 2024 17:19:09 +0800 Subject: [PATCH 1/5] remove RCTTextView patch --- .../files/Libraries/Text/Text/RCTTextView.mm | 279 ------------------ 1 file changed, 279 deletions(-) delete mode 100644 src/patches/0.73.0/files/Libraries/Text/Text/RCTTextView.mm diff --git a/src/patches/0.73.0/files/Libraries/Text/Text/RCTTextView.mm b/src/patches/0.73.0/files/Libraries/Text/Text/RCTTextView.mm deleted file mode 100644 index 758c41d..0000000 --- a/src/patches/0.73.0/files/Libraries/Text/Text/RCTTextView.mm +++ /dev/null @@ -1,279 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#import - -#import - -#import -#import - -#import - -#import - -@implementation RCTTextView { - CAShapeLayer *_highlightLayer; - UILongPressGestureRecognizer *_longPressGestureRecognizer; - - NSArray *_Nullable _descendantViews; - NSTextStorage *_Nullable _textStorage; - CGRect _contentFrame; -} - -- (instancetype)initWithFrame:(CGRect)frame -{ - NSLog(@"RCTTextView"); - if (self = [super initWithFrame:frame]) { - self.isAccessibilityElement = YES; - self.accessibilityTraits |= UIAccessibilityTraitStaticText; - self.opaque = NO; - self.contentMode = UIViewContentModeRedraw; - } - return self; -} - -- (NSString *)description -{ - NSString *stringToAppend = [NSString stringWithFormat:@" reactTag: %@; text: %@", self.reactTag, _textStorage.string]; - return [[super description] stringByAppendingString:stringToAppend]; -} - -- (void)setSelectable:(BOOL)selectable -{ - if (_selectable == selectable) { - return; - } - - _selectable = selectable; - - if (_selectable) { - [self enableContextMenu]; - } else { - [self disableContextMenu]; - } -} - -- (void)reactSetFrame:(CGRect)frame -{ - // Text looks super weird if its frame is animated. - // This disables the frame animation, without affecting opacity, etc. - [UIView performWithoutAnimation:^{ - [super reactSetFrame:frame]; - }]; -} - -- (void)didUpdateReactSubviews -{ - // Do nothing, as subviews are managed by `setTextStorage:` method -} - -- (void)setTextStorage:(NSTextStorage *)textStorage - contentFrame:(CGRect)contentFrame - descendantViews:(NSArray *)descendantViews -{ - _textStorage = textStorage; - _contentFrame = contentFrame; - - // FIXME: Optimize this. - for (UIView *view in _descendantViews) { - [view removeFromSuperview]; - } - - _descendantViews = descendantViews; - - for (UIView *view in descendantViews) { - [self addSubview:view]; - } - - [self setNeedsDisplay]; -} - -- (void)drawRect:(CGRect)rect -{ - [super drawRect:rect]; - if (!_textStorage) { - return; - } - - NSLayoutManager *layoutManager = _textStorage.layoutManagers.firstObject; - NSTextContainer *textContainer = layoutManager.textContainers.firstObject; - -#if TARGET_OS_MACCATALYST - CGContextRef context = UIGraphicsGetCurrentContext(); - CGContextSaveGState(context); - // NSLayoutManager tries to draw text with sub-pixel anti-aliasing by default on - // macOS, but rendering SPAA onto a transparent background produces poor results. - // CATextLayer disables font smoothing by default now on macOS; we follow suit. - CGContextSetShouldSmoothFonts(context, NO); -#endif - - NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer]; - [layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:_contentFrame.origin]; - [layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:_contentFrame.origin]; - - __block UIBezierPath *highlightPath = nil; - NSRange characterRange = [layoutManager characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL]; - [_textStorage - enumerateAttribute:RCTTextAttributesIsHighlightedAttributeName - inRange:characterRange - options:0 - usingBlock:^(NSNumber *value, NSRange range, __unused BOOL *stop) { - if (!value.boolValue) { - return; - } - - [layoutManager - enumerateEnclosingRectsForGlyphRange:range - withinSelectedGlyphRange:range - inTextContainer:textContainer - usingBlock:^(CGRect enclosingRect, __unused BOOL *anotherStop) { - UIBezierPath *path = [UIBezierPath - bezierPathWithRoundedRect:CGRectInset(enclosingRect, -2, -2) - cornerRadius:2]; - if (highlightPath) { - [highlightPath appendPath:path]; - } else { - highlightPath = path; - } - }]; - }]; - - if (highlightPath) { - if (!_highlightLayer) { - _highlightLayer = [CAShapeLayer layer]; - _highlightLayer.fillColor = [UIColor colorWithWhite:0 alpha:0.25].CGColor; - [self.layer addSublayer:_highlightLayer]; - } - _highlightLayer.position = _contentFrame.origin; - _highlightLayer.path = highlightPath.CGPath; - } else { - [_highlightLayer removeFromSuperlayer]; - _highlightLayer = nil; - } - -#if TARGET_OS_MACCATALYST - CGContextRestoreGState(context); -#endif -} - -- (NSNumber *)reactTagAtPoint:(CGPoint)point -{ - NSNumber *reactTag = self.reactTag; - - CGFloat fraction; - NSLayoutManager *layoutManager = _textStorage.layoutManagers.firstObject; - NSTextContainer *textContainer = layoutManager.textContainers.firstObject; - NSUInteger characterIndex = [layoutManager characterIndexForPoint:point - inTextContainer:textContainer - fractionOfDistanceBetweenInsertionPoints:&fraction]; - - // If the point is not before (fraction == 0.0) the first character and not - // after (fraction == 1.0) the last character, then the attribute is valid. - if (_textStorage.length > 0 && (fraction > 0 || characterIndex > 0) && - (fraction < 1 || characterIndex < _textStorage.length - 1)) { - reactTag = [_textStorage attribute:RCTTextAttributesTagAttributeName atIndex:characterIndex effectiveRange:NULL]; - } - - return reactTag; -} - -- (void)didMoveToWindow -{ - [super didMoveToWindow]; - - if (!self.window) { - self.layer.contents = nil; - if (_highlightLayer) { - [_highlightLayer removeFromSuperlayer]; - _highlightLayer = nil; - } - } else if (_textStorage) { - [self setNeedsDisplay]; - } -} - -#pragma mark - Accessibility - -- (NSString *)accessibilityLabel -{ - NSString *superAccessibilityLabel = [super accessibilityLabel]; - if (superAccessibilityLabel) { - return superAccessibilityLabel; - } - return _textStorage.string; -} - -#pragma mark - Context Menu - -- (void)enableContextMenu -{ - _longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self - action:@selector(handleLongPress:)]; - [self addGestureRecognizer:_longPressGestureRecognizer]; -} - -- (void)disableContextMenu -{ - [self removeGestureRecognizer:_longPressGestureRecognizer]; - _longPressGestureRecognizer = nil; -} - -- (void)handleLongPress:(UILongPressGestureRecognizer *)gesture -{ - // TODO: Adopt showMenuFromRect (necessary for UIKitForMac) -#if !TARGET_OS_UIKITFORMAC - UIMenuController *menuController = [UIMenuController sharedMenuController]; - - if (menuController.isMenuVisible) { - return; - } - - if (!self.isFirstResponder) { - [self becomeFirstResponder]; - } - - [menuController setTargetRect:self.bounds inView:self]; - [menuController setMenuVisible:YES animated:YES]; -#endif -} - -- (BOOL)canBecomeFirstResponder -{ - return _selectable; -} - -- (BOOL)canPerformAction:(SEL)action withSender:(id)sender -{ - if (_selectable && action == @selector(copy:)) { - return YES; - } - - return [self.nextResponder canPerformAction:action withSender:sender]; -} - -- (void)copy:(id)sender -{ - NSAttributedString *attributedText = _textStorage; - - NSMutableDictionary *item = [NSMutableDictionary new]; - - NSData *rtf = [attributedText dataFromRange:NSMakeRange(0, attributedText.length) - documentAttributes:@{NSDocumentTypeDocumentAttribute : NSRTFDTextDocumentType} - error:nil]; - - if (rtf) { - [item setObject:rtf forKey:(id)kUTTypeFlatRTFD]; - } - - [item setObject:attributedText.string forKey:(id)kUTTypeUTF8PlainText]; - - UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; - pasteboard.items = @[ item ]; -} - -@end From 62372b03fd0cd692e7fb6ea352b92f6c039a7e5c Mon Sep 17 00:00:00 2001 From: Fabrizio Bertoglio Date: Tue, 30 Jan 2024 17:30:54 +0800 Subject: [PATCH 2/5] rename RCTModalHostViewInteractor --- ios/Views/RCTModalHostViewImproved.h | 6 +++--- ios/Views/RCTModalHostViewImprovedManager.m | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ios/Views/RCTModalHostViewImproved.h b/ios/Views/RCTModalHostViewImproved.h index 09c44d9..a2306ac 100644 --- a/ios/Views/RCTModalHostViewImproved.h +++ b/ios/Views/RCTModalHostViewImproved.h @@ -11,18 +11,18 @@ NS_ASSUME_NONNULL_BEGIN -@protocol RCTModalHostViewInteractorImproved; +@protocol RCTModalHostViewImprovedInteractor; @interface RCTModalHostViewImproved : RCTModalHostView -@property (nonatomic, weak) id delegate; +@property (nonatomic, weak) id delegate; @property (nonatomic, strong) UIWindow *modalWindow; - (void)dismissModalViewControllerWithCompletion:(void (^)(void))completion; @end -@protocol RCTModalHostViewInteractorImproved +@protocol RCTModalHostViewImprovedInteractor - (void)presentModalHostView:(RCTModalHostView *)modalHostView withViewController:(RCTModalHostViewController *)viewController diff --git a/ios/Views/RCTModalHostViewImprovedManager.m b/ios/Views/RCTModalHostViewImprovedManager.m index 969b73e..42147ef 100644 --- a/ios/Views/RCTModalHostViewImprovedManager.m +++ b/ios/Views/RCTModalHostViewImprovedManager.m @@ -11,7 +11,7 @@ #import "RCTModalManager.h" #import "RCTModalHostViewManager.h" -@interface RCTModalHostViewImprovedManager () +@interface RCTModalHostViewImprovedManager () @end From 399d3a83a50109936256800719e2cffb23b91fdd Mon Sep 17 00:00:00 2001 From: Fabrizio Bertoglio Date: Tue, 30 Jan 2024 17:48:04 +0800 Subject: [PATCH 3/5] implementing pr 31498 diff (modal works) --- .../RCTModalHostViewControllerImproved.h | 2 + ios/Views/RCTModalHostViewImproved.h | 3 +- ios/Views/RCTModalHostViewImproved.m | 15 ++++- ios/Views/RCTModalHostViewImprovedManager.m | 55 ++++++++++++++++++- 4 files changed, 68 insertions(+), 7 deletions(-) diff --git a/ios/Views/RCTModalHostViewControllerImproved.h b/ios/Views/RCTModalHostViewControllerImproved.h index 6cda15e..a57971d 100644 --- a/ios/Views/RCTModalHostViewControllerImproved.h +++ b/ios/Views/RCTModalHostViewControllerImproved.h @@ -10,4 +10,6 @@ @interface RCTModalHostViewControllerImproved : RCTModalHostViewController +@property RCTModalHostViewImproved* modalHostView; + @end diff --git a/ios/Views/RCTModalHostViewImproved.h b/ios/Views/RCTModalHostViewImproved.h index a2306ac..6d5880d 100644 --- a/ios/Views/RCTModalHostViewImproved.h +++ b/ios/Views/RCTModalHostViewImproved.h @@ -32,7 +32,8 @@ NS_ASSUME_NONNULL_BEGIN animated:(BOOL)animated; - (void)dismissModalHostViewWithCompletion:(RCTModalHostViewImproved *)modalHostView withViewController:(RCTModalHostViewController *)viewController - animated:(BOOL)animated completion: (void (^)(void))completion; + animated:(BOOL)animated + completion: (void (^)(void))completion; @end diff --git a/ios/Views/RCTModalHostViewImproved.m b/ios/Views/RCTModalHostViewImproved.m index f2ae3e3..ddaea9f 100644 --- a/ios/Views/RCTModalHostViewImproved.m +++ b/ios/Views/RCTModalHostViewImproved.m @@ -18,7 +18,7 @@ @implementation RCTModalHostViewImproved { __weak RCTBridge *_bridge; BOOL _isPresented; - RCTModalHostViewController *_modalViewController; + RCTModalHostViewControllerImproved *_modalViewController; RCTTouchHandler *_touchHandler; UIView *_reactSubview; UIInterfaceOrientation _lastKnownOrientation; @@ -31,12 +31,13 @@ @implementation RCTModalHostViewImproved { - (instancetype)initWithBridge:(RCTBridge *)bridge { self = [super initWithBridge:bridge]; - _modalViewController = [RCTModalHostViewController new]; + _modalViewController = [RCTModalHostViewControllerImproved new]; UIView *containerView = [UIView new]; containerView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; _modalViewController.view = containerView; _touchHandler = [[RCTTouchHandler alloc] initWithBridge:bridge]; _isPresented = NO; + _modalViewController.modalHostView = self; return self; } @@ -101,9 +102,17 @@ - (UIInterfaceOrientationMask)supportedOrientationsMask } - (void)dismissModalViewController +{ + [self dismissModalViewControllerWithCompletion: nil]; +} + +- (void)dismissModalViewControllerWithCompletion:(void (^)(void))completion { if (_isPresented) { - [self.delegate dismissModalHostView:self withViewController:_modalViewController animated:[self hasAnimationType]]; + [self.delegate dismissModalHostViewWithCompletion:self + withViewController:_modalViewController + animated:[self hasAnimationType] + completion: completion]; _isPresented = NO; } } diff --git a/ios/Views/RCTModalHostViewImprovedManager.m b/ios/Views/RCTModalHostViewImprovedManager.m index 42147ef..631e94b 100644 --- a/ios/Views/RCTModalHostViewImprovedManager.m +++ b/ios/Views/RCTModalHostViewImprovedManager.m @@ -47,16 +47,39 @@ - (void)presentModalHostView:(RCTModalHostViewImproved *)modalHostView if (self.presentationBlock) { self.presentationBlock([modalHostView reactViewController], viewController, animated, completionBlock); } else { - [[modalHostView reactViewController] presentViewController:viewController + /* + [[modalHostView reactViewController] presentViewController:viewController animated:animated completion:completionBlock]; + */ + UIViewController* presentingViewController; + // pageSheet and formSheet presentation style animate the presented view so we need to use the last presented view controller + // For other presentation styles we use the new window + if (modalHostView.presentationStyle == UIModalPresentationPageSheet || modalHostView.presentationStyle == UIModalPresentationFormSheet) { + UIViewController *lastPresentedViewController = RCTKeyWindow().rootViewController; + UIViewController *presentedViewController = nil; + while (lastPresentedViewController != nil) { + presentedViewController = lastPresentedViewController; + lastPresentedViewController = lastPresentedViewController.presentedViewController; + } + presentingViewController = presentedViewController; + } else { + modalHostView.modalWindow = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds]; + modalHostView.modalWindow.windowLevel = UIWindowLevelAlert; + UIViewController *newViewController = [[UIViewController alloc] init]; + modalHostView.modalWindow.rootViewController = newViewController; + [modalHostView.modalWindow makeKeyAndVisible]; + presentingViewController = newViewController; + } + [presentingViewController presentViewController:viewController animated:animated completion:completionBlock]; } }); } -- (void)dismissModalHostView:(RCTModalHostViewImproved *)modalHostView +- (void)dismissModalHostViewWithCompletion:(RCTModalHostViewImproved *)modalHostView withViewController:(RCTModalHostViewControllerImproved *)viewController animated:(BOOL)animated + completion:(void (^)(void))completion { dispatch_block_t completionBlock = ^{ if (modalHostView.identifier) { @@ -64,15 +87,41 @@ - (void)dismissModalHostView:(RCTModalHostViewImproved *)modalHostView } }; + if (completion) { + completion(); + } + modalHostView.modalWindow = nil; + dispatch_async(dispatch_get_main_queue(), ^{ if (self.dismissalBlock) { self.dismissalBlock([modalHostView reactViewController], viewController, animated, completionBlock); } else { - [viewController.presentingViewController dismissViewControllerAnimated:animated completion:completionBlock]; + /* + [viewController.presentingViewController dismissViewControllerAnimated:animated completion:completionBlock]; + */ + // Will be true for pageSheet and formSheet presentation styles + // We dismiss the nested modal and then dismiss the current modal + if (viewController.presentedViewController != nil && [viewController.presentedViewController isKindOfClass:[RCTModalHostViewController class]]) { + RCTModalHostViewControllerImproved* presentedModalViewController = (RCTModalHostViewControllerImproved *)viewController.presentedViewController; + dispatch_block_t childModalCompletionBlock = ^{ + [viewController.presentingViewController dismissViewControllerAnimated:animated completion:completionBlock]; + }; + + [presentedModalViewController.modalHostView dismissModalViewControllerWithCompletion: childModalCompletionBlock]; + } else { + [viewController.presentingViewController dismissViewControllerAnimated:animated completion:completionBlock]; + } } }); } +- (void)dismissModalHostView:(RCTModalHostViewImproved *)modalHostView + withViewController:(RCTModalHostViewControllerImproved *)viewController + animated:(BOOL)animated +{ + [self dismissModalHostViewWithCompletion:modalHostView withViewController:viewController animated:animated completion:nil]; +} + - (void)invalidate { for (RCTModalHostView *hostView in _hostViews) { From 4167582d110f082ba59d0c501213acaccc12974f Mon Sep 17 00:00:00 2001 From: Fabrizio Bertoglio Date: Tue, 30 Jan 2024 18:01:34 +0800 Subject: [PATCH 4/5] nested modal example --- .../src/examples/Modal/ModalPresentation.js | 278 ++++-------------- 1 file changed, 55 insertions(+), 223 deletions(-) diff --git a/example/src/examples/Modal/ModalPresentation.js b/example/src/examples/Modal/ModalPresentation.js index 358da02..7da08d4 100644 --- a/example/src/examples/Modal/ModalPresentation.js +++ b/example/src/examples/Modal/ModalPresentation.js @@ -11,9 +11,18 @@ /* eslint-disable no-alert */ import * as React from 'react'; -import {Modal, Platform, StyleSheet, Switch, Text, View} from 'react-native'; -import type {RNTesterModuleExample} from '../../types/RNTesterTypes'; -import type {Props as ModalProps} from 'react-native/Libraries/Modal/Modal'; +import { + TouchableOpacity, + Button, + Modal, + Platform, + StyleSheet, + Switch, + Text, + View, +} from 'react-native'; +import type { RNTesterModuleExample } from '../../types/RNTesterTypes'; +import type { Props as ModalProps } from 'react-native/Libraries/Modal/Modal'; import RNTOption from '../../components/RNTOption'; const RNTesterButton = require('../../components/RNTesterButton'); @@ -33,229 +42,52 @@ const supportedOrientations = [ ]; function ModalPresentation() { - const onDismiss = React.useCallback(() => { - alert('onDismiss'); - }, []); - - const onShow = React.useCallback(() => { - alert('onShow'); - }, []); - - const onRequestClose = React.useCallback(() => { - console.log('onRequestClose'); - }, []); - - const [props, setProps] = React.useState({ - animationType: 'none', - transparent: false, - hardwareAccelerated: false, - statusBarTranslucent: false, - presentationStyle: Platform.select({ - ios: 'fullScreen', - default: undefined, - }), - supportedOrientations: Platform.select({ - ios: ['portrait'], - default: undefined, - }), - onDismiss: undefined, - onShow: undefined, - visible: false, - }); - const presentationStyle = props.presentationStyle; - const hardwareAccelerated = props.hardwareAccelerated; - const statusBarTranslucent = props.statusBarTranslucent; - - const [currentOrientation, setCurrentOrientation] = React.useState('unknown'); - - /* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's - * LTI update could not be added via codemod */ - const onOrientationChange = event => - setCurrentOrientation(event.nativeEvent.orientation); - - const controls = ( - <> - - Status Bar Translucent 🟢 - - setProps(prev => ({...prev, statusBarTranslucent: enabled})) - } - /> - - - Hardware Acceleration 🟢 - - setProps(prev => ({ - ...prev, - hardwareAccelerated: enabled, - })) - } - /> - - - Presentation Style ⚫️ - - {presentationStyles.map(type => ( - - setProps(prev => { - if (type === 'overFullScreen' && prev.transparent === true) { - return { - ...prev, - presentationStyle: type, - transparent: false, - }; - } - return { - ...prev, - presentationStyle: - type === prev.presentationStyle ? undefined : type, - }; - }) - } - selected={type === presentationStyle} - /> - ))} - - - - - Transparent - - setProps(prev => ({...prev, transparent: enabled})) - } - /> - - {Platform.OS === 'ios' && presentationStyle !== 'overFullScreen' ? ( - - iOS Modal can only be transparent with 'overFullScreen' Presentation - Style - - ) : null} - - - Supported Orientation ⚫️ - - {supportedOrientations.map(orientation => ( - - setProps(prev => { - if (prev.supportedOrientations?.includes(orientation)) { - return { - ...prev, - supportedOrientations: prev.supportedOrientations?.filter( - o => o !== orientation, - ), - }; - } - return { - ...prev, - supportedOrientations: [ - ...(prev.supportedOrientations ?? []), - orientation, - ], - }; - }) - } - selected={props.supportedOrientations?.includes(orientation)} - /> - ))} - - - - Actions - - - setProps(prev => ({ - ...prev, - onShow: prev.onShow ? undefined : onShow, - })) - } - selected={!!props.onShow} - /> - - setProps(prev => ({ - ...prev, - onDismiss: prev.onDismiss ? undefined : onDismiss, - })) - } - selected={!!props.onDismiss} - /> - - - - ); - + const [firstModalVisible, setFirstModalVisible] = React.useState(true); + const [secondModalVisible, setSecondModalVisible] = React.useState(true); return ( - setProps(prev => ({...prev, visible: true}))}> - Show Modal - - - - - - This modal was presented with animationType: ' - {props.animationType}' - - {Platform.OS === 'ios' ? ( - - It is currently displayed in {currentOrientation} mode. - - ) : null} - setProps(prev => ({...prev, visible: false}))}> - Close - - {controls} - - + + This is first modal +