From 8278027476fe76a5fae3dbed1136a7ecd1fd9307 Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Wed, 16 Dec 2020 13:17:02 -0600 Subject: [PATCH 01/52] [scribble+master] [scribble] Very rough proof of concept [scribble] Implement some more methods [scribble] Clean up some things, add some logging and forward touch event selectors to superview to try to fix this issue where the cursor cant be moved easily [scribble] Use UIScribbleInteractionDelegate to disable setMarkedText during scribble; add a reference to the viewController to send touch events to [scribble] Do not send showAutocorrectionPromptRectForStart from firstRectForRange while scribble is in progress - that's what was causing the last character to always be selected [scribble] Send 'showToolbar' message over the text input channel and add scribbleInProgress key to the editing state [scribble] Add UIIndirectScribbleInteraction and Delegate [scribble] Focuses, but not smoothly [scribble] Able to focus in 'correct' position [scribble] Track scribble rects on the framework side [scribble] Clean up before rebase --- .../ios/framework/Source/FlutterEngine.mm | 18 ++ .../Source/FlutterTextInputDelegate.h | 6 + .../framework/Source/FlutterTextInputPlugin.h | 71 ++++- .../Source/FlutterTextInputPlugin.mm | 271 +++++++++++++++++- 4 files changed, 360 insertions(+), 6 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index d5e3564ecffca..dda6d127ff66d 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -271,6 +271,8 @@ - (void)setViewController:(FlutterViewController*)viewController { viewController ? [viewController getWeakPtr] : fml::WeakPtr(); self.iosPlatformView->SetOwnerViewController(_viewController); [self maybeSetupPlatformViewChannels]; + _textInputPlugin.get().viewController = [self viewController]; + [_textInputPlugin.get() setupIndirectScribbleInteraction]; if (viewController) { __block FlutterEngine* blockSelf = self; @@ -471,6 +473,8 @@ - (void)setupChannels { _textInputPlugin.reset([[FlutterTextInputPlugin alloc] init]); _textInputPlugin.get().textInputDelegate = self; + _textInputPlugin.get().viewController = [self viewController]; + [_textInputPlugin.get() setupIndirectScribbleInteraction]; _platformPlugin.reset([[FlutterPlatformPlugin alloc] initWithEngine:[self getWeakPtr]]); @@ -760,6 +764,20 @@ - (void)showAutocorrectionPromptRectForStart:(NSUInteger)start #pragma mark - FlutterViewEngineDelegate +- (void)showToolbar:(int)client { + [_textInputChannel.get() invokeMethod:@"TextInputClient.showToolbar" arguments:@[@(client)]]; +} + +- (void)focusElement:(UIScribbleElementIdentifier)elementIdentifier atPoint:(CGPoint)referencePoint result:(id)callback { + [_textInputChannel.get() invokeMethod:@"TextInputClient.focusElement" arguments:@[elementIdentifier, @(referencePoint.x), @(referencePoint.y)] result:callback]; +} + +- (void)requestElementsInRect:(CGRect)rect result:(id)callback { + [_textInputChannel.get() invokeMethod:@"TextInputClient.requestElementsInRect" arguments:@[@(rect.origin.x), @(rect.origin.y), @(rect.size.width), @(rect.size.height)] result:callback]; +} + +#pragma mark - Screenshot Delegate + - (flutter::Rasterizer::Screenshot)takeScreenshot:(flutter::Rasterizer::ScreenshotType)type asBase64Encoded:(BOOL)base64Encode { FML_DCHECK(_shell) << "Cannot takeScreenshot without a shell"; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h index 033a196696127..ad41958cb6033 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h @@ -38,6 +38,12 @@ typedef NS_ENUM(NSInteger, FlutterFloatingCursorDragState) { - (void)showAutocorrectionPromptRectForStart:(NSUInteger)start end:(NSUInteger)end withClient:(int)client; +- (void)showToolbar:(int)client; +- (void)focusElement:(UIScribbleElementIdentifier)elementIdentifier + atPoint:(CGPoint)referencePoint + result:(id)callback; +- (void)requestElementsInRect:(CGRect)rect result:(id)callback; + @end #endif // SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERTEXTINPUTDELEGATE_H_ diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h index 7f9f6bcb1b142..3b41a52f415d0 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h @@ -9,10 +9,12 @@ #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h" @interface FlutterTextInputPlugin : NSObject @property(nonatomic, assign) id textInputDelegate; +@property(nonatomic, assign) FlutterViewController* viewController; - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result; /** @@ -23,6 +25,39 @@ */ - (UIView*)textInputView; +// UIIndirectScribbleInteractionDelegate +- (BOOL)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction + isElementFocused:(UIScribbleElementIdentifier)elementIdentifier + API_AVAILABLE(ios(14.0)); +- (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction + focusElementIfNeeded:(UIScribbleElementIdentifier)elementIdentifier + referencePoint:(CGPoint)focusReferencePoint + completion:(void (^)(UIResponder* focusedInput))completion + API_AVAILABLE(ios(14.0)); +- (BOOL)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction + shouldDelayFocusForElement:(UIScribbleElementIdentifier)elementIdentifier + API_AVAILABLE(ios(14.0)); +- (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction + willBeginWritingInElement:(UIScribbleElementIdentifier)elementIdentifier + API_AVAILABLE(ios(14.0)); +- (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction + didFinishWritingInElement:(UIScribbleElementIdentifier)elementIdentifier + API_AVAILABLE(ios(14.0)); +- (CGRect)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction + frameForElement:(UIScribbleElementIdentifier)elementIdentifier + API_AVAILABLE(ios(14.0)); +- (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction + requestElementsInRect:(CGRect)rect + completion: + (void (^)(NSArray* elements))completion + API_AVAILABLE(ios(14.0)); + +/** + * These are used by the UIIndirectScribbleInteractionDelegate methods to handle focusing on the + * correct element + */ +- (void)setupIndirectScribbleInteraction; + @end /** An indexed position in the buffer of a Flutter text editing widget. */ @@ -46,12 +81,33 @@ /** A tokenizer used by `FlutterTextInputView` to customize string parsing. */ @interface FlutterTokenizer : UITextInputStringTokenizer + +@interface FlutterTextSelectionRect : UITextSelectionRect + +@property(nonatomic, readonly) CGRect rect; +@property(nonatomic, readonly) NSWritingDirection writingDirection; +@property(nonatomic, readonly) BOOL containsStart; +@property(nonatomic, readonly) BOOL containsEnd; +@property(nonatomic, readonly) BOOL isVertical; + ++ (instancetype)selectionRectWithRectAndInfo:(CGRect)rect + writingDirection:(NSWritingDirection)writingDirection + containsStart:(BOOL)containsStart + containsEnd:(BOOL)containsEnd + isVertical:(BOOL)isVertical; + +- (instancetype)initWithRectAndInfo:(CGRect)rect + writingDirection:(NSWritingDirection)writingDirection + containsStart:(BOOL)containsStart + containsEnd:(BOOL)containsEnd + isVertical:(BOOL)isVertical; + @end #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG FLUTTER_DARWIN_EXPORT #endif -@interface FlutterTextInputView : UIView +@interface FlutterTextInputView : UIView // UITextInput @property(nonatomic, readonly) NSMutableString* text; @@ -74,8 +130,21 @@ FLUTTER_DARWIN_EXPORT @property(nonatomic) UITextSmartDashesType smartDashesType API_AVAILABLE(ios(11.0)); @property(nonatomic, copy) UITextContentType textContentType API_AVAILABLE(ios(10.0)); +// UIScribbleInteractionDelegate +- (void)scribbleInteractionWillBeginWriting:(UIScribbleInteraction*)interaction + API_AVAILABLE(ios(14.0)); +- (void)scribbleInteractionDidFinishWriting:(UIScribbleInteraction*)interaction + API_AVAILABLE(ios(14.0)); +- (BOOL)scribbleInteraction:(UIScribbleInteraction*)interaction + shouldBeginAtLocation:(CGPoint)location API_AVAILABLE(ios(14.0)); +- (BOOL)scribbleInteractionShouldDelayFocus:(UIScribbleInteraction*)interaction + API_AVAILABLE(ios(14.0)); + @property(nonatomic, assign) id textInputDelegate; @property(nonatomic, assign) UIAccessibilityElement* backingTextInputAccessibilityObject; +@property(nonatomic, assign) FlutterViewController* viewController; +@property(nonatomic) BOOL scribbleFocusing; +@property(nonatomic) BOOL scribbleFocused; @end #endif // SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERTEXTINPUTPLUGIN_H_ diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 854a3ff7f4c1e..f95d2af784a70 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -441,6 +441,31 @@ - (UITextRange*)lineEnclosingPosition:(UITextPosition*)position { offset:-offSetFromLineBreak]; return [_textInputView textRangeFromPosition:lineBreakBefore toPosition:lineBreakAfter]; +#pragma mark - FlutterTextSelectionRect + +@implementation FlutterTextSelectionRect + +@synthesize rect; +@synthesize writingDirection; +@synthesize containsStart; +@synthesize containsEnd; +@synthesize isVertical; + ++ (instancetype)selectionRectWithRectAndInfo:(CGRect)rect + writingDirection:(NSWritingDirection)writingDirection + containsStart:(BOOL)containsStart + containsEnd:(BOOL)containsEnd + isVertical:(BOOL)isVertical { + return [[[FlutterTextSelectionRect alloc] initWithRectAndInfo:rect writingDirection:writingDirection containsStart:containsStart containsEnd:containsEnd isVertical:isVertical] autorelease]; +} + +- (instancetype)initWithRectAndInfo:(CGRect)rect + writingDirection:(NSWritingDirection)writingDirection + containsStart:(BOOL)containsStart + containsEnd:(BOOL)containsEnd + isVertical:(BOOL)isVertical { + self = [super init]; + return self; } @end @@ -505,6 +530,8 @@ @implementation FlutterTextInputView { const char* _selectionAffinity; FlutterTextRange* _selectedTextRange; CGRect _cachedFirstRect; + NSArray* _selectionRects; + BOOL _scribbleInProgress; } @synthesize tokenizer = _tokenizer; @@ -539,6 +566,7 @@ - (instancetype)init { _smartQuotesType = UITextSmartQuotesTypeYes; _smartDashesType = UITextSmartDashesTypeYes; } + _selectionRects = @[]; } return self; @@ -647,6 +675,27 @@ - (void)setTextInputState:(NSDictionary*)state { } } +// Forward touches to the viewController to allow tapping inside the UITextField as normal +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { + [self.viewController touchesBegan:touches withEvent:event]; +} + +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { + [self.viewController touchesMoved:touches withEvent:event]; +} + +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { + [self.viewController touchesEnded:touches withEvent:event]; +} + +- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { + [self.viewController touchesCancelled:touches withEvent:event]; +} + +- (void)touchesEstimatedPropertiesUpdated:(NSSet *)touches { + [self.viewController touchesEstimatedPropertiesUpdated:touches]; +} + // Extracts the selection information from the editing state dictionary. // // The state may contain an invalid selection, such as when no selection was @@ -679,9 +728,33 @@ - (BOOL)isVisibleToAutofill { // their frames to CGRectZero prevents ios autofill from taking them into // account. - (void)setIsVisibleToAutofill:(BOOL)isVisibleToAutofill { + // This probably needs to change (think it is getting overwritten by the updateSizeAndTransform stuff for now) self.frame = isVisibleToAutofill ? CGRectMake(0, 0, 1, 1) : CGRectZero; } +#pragma mark UIScribbleInteractionDelegate + +- (void)scribbleInteractionWillBeginWriting:(UIScribbleInteraction *)interaction { + NSLog(@"[scribble] scribbleInteractionWillBeginWriting"); + _scribbleInProgress = true; +} + +- (void)scribbleInteractionDidFinishWriting:(UIScribbleInteraction *)interaction { + NSLog(@"[scribble] scribbleInteractionDidFinishWriting"); + _scribbleInProgress = false; +} + +- (BOOL)scribbleInteraction:(UIScribbleInteraction *)interaction + shouldBeginAtLocation:(CGPoint)location { + NSLog(@"[scribble] scribbleInteraction shouldBeginAtLocation: %@, %@", @(location.x), @(location.y)); + return true; +} + +- (BOOL)scribbleInteractionShouldDelayFocus:(UIScribbleInteraction *)interaction { + NSLog(@"[scribble] scribbleInteractionShouldDelayFocus"); + return false; +} + #pragma mark - UIResponder Overrides - (BOOL)canBecomeFirstResponder { @@ -721,8 +794,19 @@ - (void)setSelectedTextRangeLocal:(UITextRange*)selectedTextRange { } - (void)setSelectedTextRange:(UITextRange*)selectedTextRange { + if (_scribbleFocused) { + _scribbleFocused = false; + self.selectedTextRange = _selectedTextRange; + return; + } [self setSelectedTextRangeLocal:selectedTextRange]; [self updateEditingState]; + if (_scribbleInProgress) { + FlutterTextRange* flutterTextRange = (FlutterTextRange*)selectedTextRange; + if (flutterTextRange.range.length > 0) { + [_textInputDelegate showToolbar:_textInputClient]; + } + } } - (id)insertDictationResultPlaceholder { @@ -827,9 +911,12 @@ - (BOOL)shouldChangeTextInRange:(UITextRange*)range replacementText:(NSString*)t } - (void)setMarkedText:(NSString*)markedText selectedRange:(NSRange)markedSelectedRange { + NSLog(@"[scribble] setMarkedText %@", markedText); NSRange selectedRange = _selectedTextRange.range; NSRange markedTextRange = ((FlutterTextRange*)self.markedTextRange).range; + if (_scribbleInProgress) return; + if (markedText == nil) markedText = @""; @@ -1032,6 +1119,15 @@ - (void)setEditableTransform:(NSArray*)matrix { // candidates view for multi-stage input methods (e.g., Japanese) when using a // physical keyboard. +- (void) setSelectionRects:(NSArray*)rects { + NSMutableArray *rectsAsRect = [[NSMutableArray alloc] initWithCapacity:[rects count]]; + for (NSUInteger i = 0; i < [rects count]; i++) { + NSArray *rect = rects[i]; + [rectsAsRect addObject:@[[NSNumber numberWithInt:[rect[0] intValue]], [NSNumber numberWithInt:[rect[1] intValue]], [NSNumber numberWithInt:[rect[2] intValue]], [NSNumber numberWithInt:[rect[3] intValue]]]]; + } + _selectionRects = rectsAsRect; +} + - (CGRect)firstRectForRange:(UITextRange*)range { NSAssert([range.start isKindOfClass:[FlutterTextPosition class]], @"Expected a FlutterTextPosition for range.start (got %@).", [range.start class]); @@ -1062,32 +1158,95 @@ - (CGRect)firstRectForRange:(UITextRange*)range { return _cachedFirstRect; } - [_textInputDelegate showAutocorrectionPromptRectForStart:start - end:end - withClient:_textInputClient]; + if (!_scribbleInProgress) { + [_textInputDelegate showAutocorrectionPromptRectForStart:start + end:end + withClient:_textInputClient]; + } + + NSLog(@"[scribble] firstRectForRange %@ - %@", @(start), @(end)); + NSUInteger first = start; + if (end < start) { + first = end; + } + if ([_selectionRects count] > first) { + NSLog(@"[scribble] firstRectForRange -> %f, %f, %f, %f", [_selectionRects[first][0] floatValue], [_selectionRects[first][1] floatValue], [_selectionRects[first][2] floatValue], [_selectionRects[first][3] floatValue]); + return CGRectMake([_selectionRects[first][0] floatValue], [_selectionRects[first][1] floatValue], [_selectionRects[first][2] floatValue], [_selectionRects[first][3] floatValue]); + } + NSLog(@"[scribble] firstRectForRange -> CGRectZero"); // TODO(cbracken) Implement. return CGRectZero; } - (CGRect)caretRectForPosition:(UITextPosition*)position { // TODO(cbracken) Implement. + NSUInteger start = ((FlutterTextPosition*)position).index; + NSLog(@"[scribble][cursor] caretRectForPosition (%@)", @(start)); return CGRectZero; } - (UITextPosition*)closestPositionToPoint:(CGPoint)point { // TODO(cbracken) Implement. + NSLog(@"[scribble] closestPositionToPoint (%@, %@)", @(point.x), @(point.y)); + + NSUInteger _closestIndex = 0; + float _closestDistSq = 0; + for (NSUInteger i = 0; i < [_selectionRects count]; i++) { + CGRect rect = CGRectMake([_selectionRects[i][0] floatValue], [_selectionRects[i][1] floatValue], [_selectionRects[i][2] floatValue], [_selectionRects[i][3] floatValue]); + CGPoint pointForComparison = CGPointMake(rect.origin.x, rect.origin.y + rect.size.height * 0.5); + float distSq = pow(pointForComparison.x - point.x, 2) + pow(pointForComparison.y - point.y, 2); + if (_closestIndex == i || distSq < _closestDistSq) { + _closestDistSq = distSq; + _closestIndex = i; + } + } + + if (_closestIndex >= 0) { + NSLog(@"[scribble] closestPositionToPoint (%@, %@) -> (%@)", @(point.x), @(point.y), @(_closestIndex)); + return [FlutterTextPosition positionWithIndex:_closestIndex]; + } + NSUInteger currentIndex = ((FlutterTextPosition*)_selectedTextRange.start).index; return [FlutterTextPosition positionWithIndex:currentIndex]; } - (NSArray*)selectionRectsForRange:(UITextRange*)range { // TODO(cbracken) Implement. - return @[]; + NSUInteger start = ((FlutterTextPosition*)range.start).index; + NSUInteger end = ((FlutterTextPosition*)range.end).index; + NSLog(@"[scribble] selectionRectsForRange %@ -> %@", @(start), @(end)); + NSMutableArray* rects = [[NSMutableArray alloc] init]; + for (NSUInteger i = start; i <= end && i < [_selectionRects count]; i++) { + float width = [_selectionRects[start][2] floatValue]; + if (start == end) { width = 0; } + CGRect rect = CGRectMake([_selectionRects[start][0] floatValue], [_selectionRects[start][1] floatValue], width, [_selectionRects[start][3] floatValue]); + FlutterTextSelectionRect* selectionRect = [[FlutterTextSelectionRect alloc] initWithRectAndInfo:rect writingDirection:UITextWritingDirectionNatural containsStart:(start == 0) containsEnd:(end == self.text.length) isVertical:FALSE]; + + [rects addObject:selectionRect]; + } + return rects; } - (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRange*)range { // TODO(cbracken) Implement. - return range.start; + NSUInteger start = ((FlutterTextPosition*)range.start).index; + NSUInteger end = ((FlutterTextPosition*)range.end).index; + NSLog(@"[scribble] closestPositionToPoint (%@, %@) withinRange %@, %@", @(point.x), @(point.y), @(start), @(end)); + + NSUInteger _closestIndex = start; + float _closestDistSq = 0; + for (NSUInteger i = start; i <= end && i < [_selectionRects count]; i++) { + CGRect rect = CGRectMake([_selectionRects[i][0] floatValue], [_selectionRects[i][1] floatValue], [_selectionRects[i][2] floatValue], [_selectionRects[i][3] floatValue]); + CGPoint pointForComparison = CGPointMake(rect.origin.x, rect.origin.y + rect.size.height * 0.5); + float distSq = pow(pointForComparison.x - point.x, 2) + pow(pointForComparison.y - point.y, 2); + if (_closestIndex == i || distSq < _closestDistSq) { + _closestDistSq = distSq; + _closestIndex = i; + } + } + + NSLog(@"[scribble] closestPositionToPoint (%@, %@) withinRange %@, %@ -> %@", @(point.x), @(point.y), @(start), @(end), @(_closestIndex)); + return [FlutterTextPosition positionWithIndex:_closestIndex]; } - (UITextRange*)characterRangeAtPoint:(CGPoint)point { @@ -1136,6 +1295,7 @@ - (void)updateEditingState { @"composingBase" : @(composingBase), @"composingExtent" : @(composingExtent), @"text" : [NSString stringWithString:self.text], + @"scribbleInProgress" : @(_scribbleInProgress), }; if (_textInputClient == 0 && _autofillId != nil) { @@ -1150,6 +1310,7 @@ - (BOOL)hasText { } - (void)insertText:(NSString*)text { + NSLog(@"[scribble] insertText: %@", text); _selectionAffinity = _kTextAffinityDownstream; [self replaceRange:_selectedTextRange withText:text]; } @@ -1270,9 +1431,12 @@ @interface FlutterTextInputPlugin () @implementation FlutterTextInputPlugin { NSTimer* _enableFlutterTextInputViewAccessibilityTimer; + BOOL _hasScribbleInteraction; + NSMutableDictionary* _scribbleElements; } @synthesize textInputDelegate = _textInputDelegate; +@synthesize viewController = _viewController; - (instancetype)init { self = [super init]; @@ -1283,6 +1447,7 @@ - (instancetype)init { _autofillContext = [[NSMutableDictionary alloc] init]; _activeView = [_reusableInputView retain]; _inputHider = [[FlutterTextInputViewAccessibilityHider alloc] init]; + _scribbleElements = [[NSMutableDictionary alloc] init]; } return self; @@ -1312,6 +1477,7 @@ - (void)removeEnableFlutterTextInputViewAccessibilityTimer { - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { NSString* method = call.method; + NSLog(@"TextInputPlugin.handleMethodCall %@", method); id args = call.arguments; if ([method isEqualToString:@"TextInput.show"]) { [self showTextInput]; @@ -1337,6 +1503,9 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { } else if ([method isEqualToString:@"TextInput.finishAutofillContext"]) { [self triggerAutofillSave:[args boolValue]]; result(nil); + } else if ([method isEqualToString:@"TextInput.setSelectionRects"]) { + [self setSelectionRects:args]; + result(nil); } else { result(FlutterMethodNotImplemented); } @@ -1344,6 +1513,9 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { - (void)setEditableSizeAndTransform:(NSDictionary*)dictionary { [_activeView setEditableTransform:dictionary[@"transform"]]; + // TODO: only do this on iPadOS? + // This seems necessary to set up where the scribble interactable element will be + _activeView.frame = CGRectMake([dictionary[@"transform"][12] intValue], [dictionary[@"transform"][13] intValue], [dictionary[@"width"] intValue], [dictionary[@"height"] intValue]); } - (void)updateMarkedRect:(NSDictionary*)dictionary { @@ -1355,8 +1527,13 @@ - (void)updateMarkedRect:(NSDictionary*)dictionary { _activeView.markedRect = rect.size.width < 0 && rect.size.height < 0 ? kInvalidFirstRect : rect; } +- (void)setSelectionRects:(NSArray*)rects { + [_activeView setSelectionRects:rects]; +} + - (void)showTextInput { _activeView.textInputDelegate = _textInputDelegate; + _activeView.viewController = _viewController; [self addToInputParentViewIfNeeded:_activeView]; // Adds a delay to prevent the text view from receiving accessibility // focus in case it is activated during semantics updates. @@ -1599,6 +1776,10 @@ - (void)addToInputParentViewIfNeeded:(FlutterTextInputView*)inputView { UIView* parentView = self.keyWindow; if (_inputHider.superview != parentView) { [parentView addSubview:_inputHider]; + if (@available(iOS 14.0, *)) { + UIScribbleInteraction* interaction = [[[UIScribbleInteraction alloc] initWithDelegate:inputView] autorelease]; + [inputView addInteraction:interaction]; + } } } @@ -1610,4 +1791,84 @@ - (void)clearTextInputClient { [_activeView setTextInputClient:0]; } +#pragma mark UIIndirectScribbleInteractionDelegate + +- (BOOL)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction + isElementFocused:(UIScribbleElementIdentifier)elementIdentifier { + NSLog(@"[scribble][delegate] isElementFocused:%@", elementIdentifier); + return false; +} + +- (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction + focusElementIfNeeded:(UIScribbleElementIdentifier)elementIdentifier + referencePoint:(CGPoint)focusReferencePoint + completion:(void (^)(UIResponder* focusedInput))completion { + NSLog(@"[scribble][delegate] focusElementIfNeeded:%@ referencePoint:(%@, %@)", elementIdentifier, @(focusReferencePoint.x), @(focusReferencePoint.y)); + _reusableInputView.scribbleFocusing = true; + [_textInputDelegate focusElement:elementIdentifier atPoint:focusReferencePoint result:^(id _Nullable result){ + _reusableInputView.scribbleFocusing = false; + _reusableInputView.scribbleFocused = true; + completion(_reusableInputView); + }]; +} + +- (BOOL)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction + shouldDelayFocusForElement:(UIScribbleElementIdentifier)elementIdentifier { + NSLog(@"[scribble][delegate] shouldDelayFocusForElement:%@", elementIdentifier); + return true; +} + +- (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction + willBeginWritingInElement:(UIScribbleElementIdentifier)elementIdentifier { + NSLog(@"[scribble][delegate] willBeginWritingInElement:%@", elementIdentifier); +} + +- (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction + didFinishWritingInElement:(UIScribbleElementIdentifier)elementIdentifier { + NSLog(@"[scribble][delegate] didFinishWritingInElement:%@", elementIdentifier); +} + +- (CGRect)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction + frameForElement:(UIScribbleElementIdentifier)elementIdentifier { + NSLog(@"[scribble][delegate] frameForElement:%@", elementIdentifier); + NSValue* elementValue = [_scribbleElements objectForKey:elementIdentifier]; + if (elementValue == nil) { + return CGRectZero; + } + return [elementValue CGRectValue]; +} + +- (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction + requestElementsInRect:(CGRect)rect + completion: + (void (^)(NSArray* elements))completion { + NSLog(@"[scribble][delegate] requestElementsInRect:%@", @(rect)); + [_textInputDelegate requestElementsInRect:rect result:^(id _Nullable result){ + NSMutableArray* elements = [[NSMutableArray alloc] init]; + if ([result isKindOfClass:[NSArray class]]) { + for (NSArray* elementArray in result) { + [elements addObject:elementArray[0]]; + [_scribbleElements setObject:[NSValue valueWithCGRect:CGRectMake([elementArray[1] floatValue], [elementArray[2] floatValue], [elementArray[3] floatValue], [elementArray[4] floatValue])] forKey:elementArray[0]]; + } + } + completion(elements); + }]; +} + +#pragma mark - Methods related to Scribble support + +- (void) setupIndirectScribbleInteraction { + if (_hasScribbleInteraction) { + return; + } + if (@available(iOS 14.0, *)) { + UIView* parentView = _viewController.view; + if (parentView != nil) { + _hasScribbleInteraction = true; + UIIndirectScribbleInteraction* _scribbleInteraction = [[[UIIndirectScribbleInteraction alloc] initWithDelegate:(id)self] autorelease]; + [parentView addInteraction:_scribbleInteraction]; + } + } +} + @end From 9104fe57295888f673a967797d6530a6da6d011c Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Tue, 9 Feb 2021 15:43:32 -0600 Subject: [PATCH 02/52] [scribble+master] Addressing PR feedback: remove scribbleInProgress from TextInputState, update separately --- .../platform/darwin/ios/framework/Source/FlutterEngine.mm | 8 ++++++++ .../ios/framework/Source/FlutterTextInputDelegate.h | 2 ++ .../darwin/ios/framework/Source/FlutterTextInputPlugin.mm | 3 ++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index dda6d127ff66d..bb8a02a6dca9f 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -776,6 +776,14 @@ - (void)requestElementsInRect:(CGRect)rect result:(id)callback { [_textInputChannel.get() invokeMethod:@"TextInputClient.requestElementsInRect" arguments:@[@(rect.origin.x), @(rect.origin.y), @(rect.size.width), @(rect.size.height)] result:callback]; } +- (void)scribbleInteractionBegan { + [_textInputChannel.get() invokeMethod:@"TextInputClient.scribbleInteractionBegan" arguments:nil]; +} + +- (void)scribbleInteractionFinished { + [_textInputChannel.get() invokeMethod:@"TextInputClient.scribbleInteractionFinished" arguments:nil]; +} + #pragma mark - Screenshot Delegate - (flutter::Rasterizer::Screenshot)takeScreenshot:(flutter::Rasterizer::ScreenshotType)type diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h index ad41958cb6033..01f63647b5e2b 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h @@ -43,6 +43,8 @@ typedef NS_ENUM(NSInteger, FlutterFloatingCursorDragState) { atPoint:(CGPoint)referencePoint result:(id)callback; - (void)requestElementsInRect:(CGRect)rect result:(id)callback; +- (void)scribbleInteractionBegan; +- (void)scribbleInteractionFinished; @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index f95d2af784a70..8ba6ef3ef9d3c 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -737,11 +737,13 @@ - (void)setIsVisibleToAutofill:(BOOL)isVisibleToAutofill { - (void)scribbleInteractionWillBeginWriting:(UIScribbleInteraction *)interaction { NSLog(@"[scribble] scribbleInteractionWillBeginWriting"); _scribbleInProgress = true; + [_textInputDelegate scribbleInteractionBegan]; } - (void)scribbleInteractionDidFinishWriting:(UIScribbleInteraction *)interaction { NSLog(@"[scribble] scribbleInteractionDidFinishWriting"); _scribbleInProgress = false; + [_textInputDelegate scribbleInteractionFinished]; } - (BOOL)scribbleInteraction:(UIScribbleInteraction *)interaction @@ -1295,7 +1297,6 @@ - (void)updateEditingState { @"composingBase" : @(composingBase), @"composingExtent" : @(composingExtent), @"text" : [NSString stringWithString:self.text], - @"scribbleInProgress" : @(_scribbleInProgress), }; if (_textInputClient == 0 && _autofillId != nil) { From abbdbf9decf83a7475b6111774802b3a77470a23 Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Wed, 24 Feb 2021 13:28:55 -0600 Subject: [PATCH 03/52] [scribble+master] Improve closestPositionToPoint algorithm --- .../Source/FlutterTextInputPlugin.mm | 43 +++++++++++++++---- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 8ba6ef3ef9d3c..0e99d798e3dc2 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -1167,10 +1167,10 @@ - (CGRect)firstRectForRange:(UITextRange*)range { } NSLog(@"[scribble] firstRectForRange %@ - %@", @(start), @(end)); - NSUInteger first = start; - if (end < start) { - first = end; - } + NSUInteger first = start; + if (end < start) { + first = end; + } if ([_selectionRects count] > first) { NSLog(@"[scribble] firstRectForRange -> %f, %f, %f, %f", [_selectionRects[first][0] floatValue], [_selectionRects[first][1] floatValue], [_selectionRects[first][2] floatValue], [_selectionRects[first][3] floatValue]); return CGRectMake([_selectionRects[first][0] floatValue], [_selectionRects[first][1] floatValue], [_selectionRects[first][2] floatValue], [_selectionRects[first][3] floatValue]); @@ -1192,14 +1192,41 @@ - (UITextPosition*)closestPositionToPoint:(CGPoint)point { NSLog(@"[scribble] closestPositionToPoint (%@, %@)", @(point.x), @(point.y)); NSUInteger _closestIndex = 0; - float _closestDistSq = 0; + CGRect _closestRect = CGRectZero; + float _closestY = 0; + float _closestX = 0; for (NSUInteger i = 0; i < [_selectionRects count]; i++) { CGRect rect = CGRectMake([_selectionRects[i][0] floatValue], [_selectionRects[i][1] floatValue], [_selectionRects[i][2] floatValue], [_selectionRects[i][3] floatValue]); CGPoint pointForComparison = CGPointMake(rect.origin.x, rect.origin.y + rect.size.height * 0.5); - float distSq = pow(pointForComparison.x - point.x, 2) + pow(pointForComparison.y - point.y, 2); - if (_closestIndex == i || distSq < _closestDistSq) { - _closestDistSq = distSq; + float yDist = abs(pointForComparison.y - point.y); + float xDist = abs(pointForComparison.x - point.x); + if (_closestIndex == i || + (yDist < _closestY || + (yDist == _closestY && ( + (point.y <= rect.origin.y + rect.size.height && xDist < _closestX) || + (point.y > rect.origin.y + rect.size.height && rect.origin.x > _closestRect.origin.x) + )))) { + _closestY = yDist; + _closestX = xDist; _closestIndex = i; + _closestRect = rect; + } + } + + if ([_selectionRects count] > 0) { + NSUInteger i = [_selectionRects count] - 1; + CGRect rect = CGRectMake([_selectionRects[i][0] floatValue], [_selectionRects[i][1] floatValue], [_selectionRects[i][2] floatValue], [_selectionRects[i][3] floatValue]); + CGPoint pointForComparison = CGPointMake(rect.origin.x + rect.size.width, rect.origin.y + rect.size.height * 0.5); + float yDist = abs(pointForComparison.y - point.y); + float xDist = abs(pointForComparison.x - point.x); + if (yDist < _closestY || + (yDist == _closestY && ( + (point.y <= rect.origin.y + rect.size.height && xDist < _closestX) || + (point.y > rect.origin.y + rect.size.height && rect.origin.x + rect.size.width > _closestRect.origin.x) + ))) { + _closestY = yDist; + _closestX = xDist; + _closestIndex = [_selectionRects count]; } } From 92268b91745757c1b9f08de2be2b07aaf8948c66 Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Wed, 24 Feb 2021 21:52:36 -0600 Subject: [PATCH 04/52] [scribble+master] Add tests, refactor the closestPositionToPoint methods so that the withinRange version is called by the other --- .../ios/framework/Source/FlutterEngine.mm | 4 +- .../framework/Source/FlutterTextInputPlugin.h | 12 +- .../Source/FlutterTextInputPlugin.mm | 102 ++++++++-------- .../Source/FlutterTextInputPluginTest.m | 115 ++++++++++++++++++ 4 files changed, 171 insertions(+), 62 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index bb8a02a6dca9f..ded3db1fadc26 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -271,8 +271,6 @@ - (void)setViewController:(FlutterViewController*)viewController { viewController ? [viewController getWeakPtr] : fml::WeakPtr(); self.iosPlatformView->SetOwnerViewController(_viewController); [self maybeSetupPlatformViewChannels]; - _textInputPlugin.get().viewController = [self viewController]; - [_textInputPlugin.get() setupIndirectScribbleInteraction]; if (viewController) { __block FlutterEngine* blockSelf = self; @@ -291,6 +289,8 @@ - (void)setViewController:(FlutterViewController*)viewController { - (void)attachView { self.iosPlatformView->attachView(); + _textInputPlugin.get().viewController = [self viewController]; + [_textInputPlugin.get() setupIndirectScribbleInteraction]; } - (void)setFlutterViewControllerWillDeallocObserver:(id)observer { diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h index 3b41a52f415d0..2a452217ac302 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h @@ -8,8 +8,8 @@ #import #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" +#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h" -#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h" @interface FlutterTextInputPlugin : NSObject @@ -84,11 +84,11 @@ @interface FlutterTextSelectionRect : UITextSelectionRect -@property(nonatomic, readonly) CGRect rect; -@property(nonatomic, readonly) NSWritingDirection writingDirection; -@property(nonatomic, readonly) BOOL containsStart; -@property(nonatomic, readonly) BOOL containsEnd; -@property(nonatomic, readonly) BOOL isVertical; +@property(nonatomic, assign) CGRect rect; +@property(nonatomic, assign) NSWritingDirection writingDirection; +@property(nonatomic, assign) BOOL containsStart; +@property(nonatomic, assign) BOOL containsEnd; +@property(nonatomic, assign) BOOL isVertical; + (instancetype)selectionRectWithRectAndInfo:(CGRect)rect writingDirection:(NSWritingDirection)writingDirection diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 0e99d798e3dc2..987d1896d1aab 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -445,11 +445,11 @@ - (UITextRange*)lineEnclosingPosition:(UITextPosition*)position { @implementation FlutterTextSelectionRect -@synthesize rect; -@synthesize writingDirection; -@synthesize containsStart; -@synthesize containsEnd; -@synthesize isVertical; +@synthesize rect = _rect; +@synthesize writingDirection = _writingDirection; +@synthesize containsStart = _containsStart; +@synthesize containsEnd = _containsEnd; +@synthesize isVertical = _isVertical; + (instancetype)selectionRectWithRectAndInfo:(CGRect)rect writingDirection:(NSWritingDirection)writingDirection @@ -465,6 +465,13 @@ - (instancetype)initWithRectAndInfo:(CGRect)rect containsEnd:(BOOL)containsEnd isVertical:(BOOL)isVertical { self = [super init]; + if (self) { + _rect = rect; + _writingDirection = writingDirection; + _containsStart = containsStart; + _containsEnd = containsEnd; + _isVertical = isVertical; + } return self; } @@ -1191,11 +1198,42 @@ - (UITextPosition*)closestPositionToPoint:(CGPoint)point { // TODO(cbracken) Implement. NSLog(@"[scribble] closestPositionToPoint (%@, %@)", @(point.x), @(point.y)); - NSUInteger _closestIndex = 0; + if ([_selectionRects count] == 0) { + NSUInteger currentIndex = ((FlutterTextPosition*)_selectedTextRange.start).index; + return [FlutterTextPosition positionWithIndex:currentIndex]; + } + + FlutterTextRange* range = [[FlutterTextRange rangeWithNSRange:NSMakeRange(0, [_selectionRects count])] copy]; + return [self closestPositionToPoint:point withinRange:range]; +} + +- (NSArray*)selectionRectsForRange:(UITextRange*)range { + // TODO(cbracken) Implement. + NSUInteger start = ((FlutterTextPosition*)range.start).index; + NSUInteger end = ((FlutterTextPosition*)range.end).index; + NSLog(@"[scribble] selectionRectsForRange %@ -> %@", @(start), @(end)); + NSMutableArray* rects = [[NSMutableArray alloc] init]; + for (NSUInteger i = start; i <= end && i < [_selectionRects count]; i++) { + float width = [_selectionRects[i][2] floatValue]; + if (start == end) { width = 0; } + CGRect rect = CGRectMake([_selectionRects[i][0] floatValue], [_selectionRects[i][1] floatValue], width, [_selectionRects[i][3] floatValue]); + FlutterTextSelectionRect* selectionRect = [[FlutterTextSelectionRect alloc] initWithRectAndInfo:rect writingDirection:UITextWritingDirectionNatural containsStart:(i == 0) containsEnd:(i == self.text.length) isVertical:FALSE]; + [rects addObject:selectionRect]; + } + return rects; +} + +- (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRange*)range { + // TODO(cbracken) Implement. + NSUInteger start = ((FlutterTextPosition*)range.start).index; + NSUInteger end = ((FlutterTextPosition*)range.end).index; + NSLog(@"[scribble] closestPositionToPoint (%@, %@) withinRange %@, %@", @(point.x), @(point.y), @(start), @(end)); + + NSUInteger _closestIndex = start; CGRect _closestRect = CGRectZero; float _closestY = 0; float _closestX = 0; - for (NSUInteger i = 0; i < [_selectionRects count]; i++) { + for (NSUInteger i = start; i < [_selectionRects count] && i < end; i++) { CGRect rect = CGRectMake([_selectionRects[i][0] floatValue], [_selectionRects[i][1] floatValue], [_selectionRects[i][2] floatValue], [_selectionRects[i][3] floatValue]); CGPoint pointForComparison = CGPointMake(rect.origin.x, rect.origin.y + rect.size.height * 0.5); float yDist = abs(pointForComparison.y - point.y); @@ -1213,8 +1251,8 @@ - (UITextPosition*)closestPositionToPoint:(CGPoint)point { } } - if ([_selectionRects count] > 0) { - NSUInteger i = [_selectionRects count] - 1; + if ([_selectionRects count] > 0 && [_selectionRects count] >= end) { + NSUInteger i = end - 1; CGRect rect = CGRectMake([_selectionRects[i][0] floatValue], [_selectionRects[i][1] floatValue], [_selectionRects[i][2] floatValue], [_selectionRects[i][3] floatValue]); CGPoint pointForComparison = CGPointMake(rect.origin.x + rect.size.width, rect.origin.y + rect.size.height * 0.5); float yDist = abs(pointForComparison.y - point.y); @@ -1226,51 +1264,7 @@ - (UITextPosition*)closestPositionToPoint:(CGPoint)point { ))) { _closestY = yDist; _closestX = xDist; - _closestIndex = [_selectionRects count]; - } - } - - if (_closestIndex >= 0) { - NSLog(@"[scribble] closestPositionToPoint (%@, %@) -> (%@)", @(point.x), @(point.y), @(_closestIndex)); - return [FlutterTextPosition positionWithIndex:_closestIndex]; - } - - NSUInteger currentIndex = ((FlutterTextPosition*)_selectedTextRange.start).index; - return [FlutterTextPosition positionWithIndex:currentIndex]; -} - -- (NSArray*)selectionRectsForRange:(UITextRange*)range { - // TODO(cbracken) Implement. - NSUInteger start = ((FlutterTextPosition*)range.start).index; - NSUInteger end = ((FlutterTextPosition*)range.end).index; - NSLog(@"[scribble] selectionRectsForRange %@ -> %@", @(start), @(end)); - NSMutableArray* rects = [[NSMutableArray alloc] init]; - for (NSUInteger i = start; i <= end && i < [_selectionRects count]; i++) { - float width = [_selectionRects[start][2] floatValue]; - if (start == end) { width = 0; } - CGRect rect = CGRectMake([_selectionRects[start][0] floatValue], [_selectionRects[start][1] floatValue], width, [_selectionRects[start][3] floatValue]); - FlutterTextSelectionRect* selectionRect = [[FlutterTextSelectionRect alloc] initWithRectAndInfo:rect writingDirection:UITextWritingDirectionNatural containsStart:(start == 0) containsEnd:(end == self.text.length) isVertical:FALSE]; - - [rects addObject:selectionRect]; - } - return rects; -} - -- (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRange*)range { - // TODO(cbracken) Implement. - NSUInteger start = ((FlutterTextPosition*)range.start).index; - NSUInteger end = ((FlutterTextPosition*)range.end).index; - NSLog(@"[scribble] closestPositionToPoint (%@, %@) withinRange %@, %@", @(point.x), @(point.y), @(start), @(end)); - - NSUInteger _closestIndex = start; - float _closestDistSq = 0; - for (NSUInteger i = start; i <= end && i < [_selectionRects count]; i++) { - CGRect rect = CGRectMake([_selectionRects[i][0] floatValue], [_selectionRects[i][1] floatValue], [_selectionRects[i][2] floatValue], [_selectionRects[i][3] floatValue]); - CGPoint pointForComparison = CGPointMake(rect.origin.x, rect.origin.y + rect.size.height * 0.5); - float distSq = pow(pointForComparison.x - point.x, 2) + pow(pointForComparison.y - point.y, 2); - if (_closestIndex == i || distSq < _closestDistSq) { - _closestDistSq = distSq; - _closestIndex = i; + _closestIndex = end; } } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m index 997daa2a0d48a..910657ed4ec28 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m @@ -20,6 +20,7 @@ - (void)setTextInputState:(NSDictionary*)state; - (void)setMarkedRect:(CGRect)markedRect; - (void)updateEditingState; - (BOOL)isVisibleToAutofill; +- (void)setSelectionRects:(NSArray*)rects; @end @@ -498,6 +499,120 @@ - (void)testUpdateFirstRectForRange { XCTAssertTrue(CGRectEqualToRect(kInvalidFirstRect, [inputView firstRectForRange:range])); } +- (void)testFirstRectForRangeReturnsCorrectSelectionRect { + FlutterTextInputView* inputView = [[FlutterTextInputView alloc] init]; + [inputView + setTextInputState:@{@"text" : @"COMPOSING"}]; + + FlutterTextRange* range = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 1)]; + CGRect testRect = CGRectMake(100, 100, 100, 100); + [inputView setSelectionRects:@[ + @[@0, @0, @100, @100], + @[@(testRect.origin.x), @(testRect.origin.y), @(testRect.size.width), @(testRect.size.height)], + @[@200, @200, @100, @100]]]; + NSLog(@"testRect: %@, firstRect: %@", @(testRect), @([inputView firstRectForRange:range])); + XCTAssertTrue(CGRectEqualToRect(testRect, [inputView firstRectForRange:range])); + + FlutterTextRange* rangeOutsideBounds = [FlutterTextRange rangeWithNSRange:NSMakeRange(3, 1)]; + XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:rangeOutsideBounds])); +} + +- (void)testClosestPositionToPoint { + FlutterTextInputView* inputView = [[FlutterTextInputView alloc] init]; + [inputView + setTextInputState:@{@"text" : @"COMPOSING"}]; + + // Minimize the vertical distance from the center of the rects first + [inputView setSelectionRects:@[ + @[@0, @0, @100, @100], + @[@0, @100, @100, @100], + @[@0, @200, @100, @100], + ]]; + CGPoint point = CGPointMake(150, 150); + XCTAssertEqual(1, ((FlutterTextPosition*)[inputView closestPositionToPoint:point]).index); + + // Then, if the point is above the bottom of the closest rects vertically, get the closest x origin + [inputView setSelectionRects:@[ + @[@0, @0, @100, @100], + @[@0, @100, @100, @100], + @[@100, @100, @100, @100], + @[@200, @100, @100, @100], + @[@0, @200, @100, @100], + ]]; + point = CGPointMake(125, 150); + XCTAssertEqual(2, ((FlutterTextPosition*)[inputView closestPositionToPoint:point]).index); + + // However, if the point is below the bottom of the closest rects vertically, get the position farthest to the right + [inputView setSelectionRects:@[ + @[@0, @0, @100, @100], + @[@0, @100, @100, @100], + @[@100, @100, @100, @100], + @[@200, @100, @100, @100], + @[@0, @300, @100, @100], + ]]; + point = CGPointMake(125, 201); + XCTAssertEqual(3, ((FlutterTextPosition*)[inputView closestPositionToPoint:point]).index); + + // Also check a point at the right edge of the last selection rect + [inputView setSelectionRects:@[ + @[@0, @0, @100, @100], + @[@0, @100, @100, @100], + @[@100, @100, @100, @100], + @[@200, @100, @100, @100], + ]]; + point = CGPointMake(125, 250); + XCTAssertEqual(4, ((FlutterTextPosition*)[inputView closestPositionToPoint:point]).index); +} + +- (void)testSelectionRectsForRange { + FlutterTextInputView* inputView = [[FlutterTextInputView alloc] init]; + [inputView + setTextInputState:@{@"text" : @"COMPOSING"}]; + + FlutterTextRange* range = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 1)]; + + CGRect testRect0 = CGRectMake(100, 100, 100, 100); + CGRect testRect1 = CGRectMake(200, 200, 100, 100); + [inputView setSelectionRects:@[ + @[@0, @0, @100, @100], + @[@(testRect0.origin.x), @(testRect0.origin.y), @(testRect0.size.width), @(testRect0.size.height)], + @[@(testRect1.origin.x), @(testRect1.origin.y), @(testRect1.size.width), @(testRect1.size.height)], + @[@300, @300, @100, @100]]]; + XCTAssertTrue(CGRectEqualToRect(testRect0, [inputView selectionRectsForRange:range][0].rect)); + XCTAssertTrue(CGRectEqualToRect(testRect1, [inputView selectionRectsForRange:range][1].rect)); + XCTAssertEqual(2, [[inputView selectionRectsForRange:range] count]); +} + +- (void)testClosestPositionToPointWithinRange { + FlutterTextInputView* inputView = [[FlutterTextInputView alloc] init]; + [inputView + setTextInputState:@{@"text" : @"COMPOSING"}]; + + // Do not return a position before the start of the range + [inputView setSelectionRects:@[ + @[@0, @0, @100, @100], + @[@0, @100, @100, @100], + @[@100, @100, @100, @100], + @[@200, @100, @100, @100], + @[@0, @200, @100, @100], + ]]; + CGPoint point = CGPointMake(125, 150); + FlutterTextRange* range = [[FlutterTextRange rangeWithNSRange:NSMakeRange(3, 2)] copy]; + XCTAssertEqual(3, ((FlutterTextPosition*)[inputView closestPositionToPoint:point withinRange:range]).index); + + // Do not return a position after the end of the range + [inputView setSelectionRects:@[ + @[@0, @0, @100, @100], + @[@0, @100, @100, @100], + @[@100, @100, @100, @100], + @[@200, @100, @100, @100], + @[@0, @200, @100, @100], + ]]; + point = CGPointMake(125, 150); + range = [[FlutterTextRange rangeWithNSRange:NSMakeRange(0, 1)] copy]; + XCTAssertEqual(1, ((FlutterTextPosition*)[inputView closestPositionToPoint:point withinRange:range]).index); +} + #pragma mark - Autofill - Utilities - (NSMutableDictionary*)mutablePasswordTemplateCopy { From 2af5fe92b92c63c5e42a1c1e6820439a8b7fe577 Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Fri, 26 Feb 2021 11:57:46 -0600 Subject: [PATCH 05/52] [scribble+master] Run clang-format --- .../ios/framework/Source/FlutterEngine.mm | 19 +- .../Source/FlutterTextInputPlugin.mm | 181 +++++++++++------- .../Source/FlutterTextInputPluginTest.m | 102 +++++----- 3 files changed, 183 insertions(+), 119 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index ded3db1fadc26..247c15b8b798a 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -765,15 +765,23 @@ - (void)showAutocorrectionPromptRectForStart:(NSUInteger)start #pragma mark - FlutterViewEngineDelegate - (void)showToolbar:(int)client { - [_textInputChannel.get() invokeMethod:@"TextInputClient.showToolbar" arguments:@[@(client)]]; + [_textInputChannel.get() invokeMethod:@"TextInputClient.showToolbar" arguments:@[ @(client) ]]; } -- (void)focusElement:(UIScribbleElementIdentifier)elementIdentifier atPoint:(CGPoint)referencePoint result:(id)callback { - [_textInputChannel.get() invokeMethod:@"TextInputClient.focusElement" arguments:@[elementIdentifier, @(referencePoint.x), @(referencePoint.y)] result:callback]; +- (void)focusElement:(UIScribbleElementIdentifier)elementIdentifier + atPoint:(CGPoint)referencePoint + result:(id)callback { + [_textInputChannel.get() + invokeMethod:@"TextInputClient.focusElement" + arguments:@[ elementIdentifier, @(referencePoint.x), @(referencePoint.y) ] + result:callback]; } - (void)requestElementsInRect:(CGRect)rect result:(id)callback { - [_textInputChannel.get() invokeMethod:@"TextInputClient.requestElementsInRect" arguments:@[@(rect.origin.x), @(rect.origin.y), @(rect.size.width), @(rect.size.height)] result:callback]; + [_textInputChannel.get() + invokeMethod:@"TextInputClient.requestElementsInRect" + arguments:@[ @(rect.origin.x), @(rect.origin.y), @(rect.size.width), @(rect.size.height) ] + result:callback]; } - (void)scribbleInteractionBegan { @@ -781,7 +789,8 @@ - (void)scribbleInteractionBegan { } - (void)scribbleInteractionFinished { - [_textInputChannel.get() invokeMethod:@"TextInputClient.scribbleInteractionFinished" arguments:nil]; + [_textInputChannel.get() invokeMethod:@"TextInputClient.scribbleInteractionFinished" + arguments:nil]; } #pragma mark - Screenshot Delegate diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 987d1896d1aab..a8ad581b0fc65 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -456,7 +456,11 @@ + (instancetype)selectionRectWithRectAndInfo:(CGRect)rect containsStart:(BOOL)containsStart containsEnd:(BOOL)containsEnd isVertical:(BOOL)isVertical { - return [[[FlutterTextSelectionRect alloc] initWithRectAndInfo:rect writingDirection:writingDirection containsStart:containsStart containsEnd:containsEnd isVertical:isVertical] autorelease]; + return [[[FlutterTextSelectionRect alloc] initWithRectAndInfo:rect + writingDirection:writingDirection + containsStart:containsStart + containsEnd:containsEnd + isVertical:isVertical] autorelease]; } - (instancetype)initWithRectAndInfo:(CGRect)rect @@ -683,24 +687,24 @@ - (void)setTextInputState:(NSDictionary*)state { } // Forward touches to the viewController to allow tapping inside the UITextField as normal -- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { - [self.viewController touchesBegan:touches withEvent:event]; +- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { + [self.viewController touchesBegan:touches withEvent:event]; } -- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { - [self.viewController touchesMoved:touches withEvent:event]; +- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event { + [self.viewController touchesMoved:touches withEvent:event]; } -- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { - [self.viewController touchesEnded:touches withEvent:event]; +- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event { + [self.viewController touchesEnded:touches withEvent:event]; } -- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { - [self.viewController touchesCancelled:touches withEvent:event]; +- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event { + [self.viewController touchesCancelled:touches withEvent:event]; } -- (void)touchesEstimatedPropertiesUpdated:(NSSet *)touches { - [self.viewController touchesEstimatedPropertiesUpdated:touches]; +- (void)touchesEstimatedPropertiesUpdated:(NSSet*)touches { + [self.viewController touchesEstimatedPropertiesUpdated:touches]; } // Extracts the selection information from the editing state dictionary. @@ -735,31 +739,33 @@ - (BOOL)isVisibleToAutofill { // their frames to CGRectZero prevents ios autofill from taking them into // account. - (void)setIsVisibleToAutofill:(BOOL)isVisibleToAutofill { - // This probably needs to change (think it is getting overwritten by the updateSizeAndTransform stuff for now) + // This probably needs to change (think it is getting overwritten by the updateSizeAndTransform + // stuff for now) self.frame = isVisibleToAutofill ? CGRectMake(0, 0, 1, 1) : CGRectZero; } #pragma mark UIScribbleInteractionDelegate -- (void)scribbleInteractionWillBeginWriting:(UIScribbleInteraction *)interaction { +- (void)scribbleInteractionWillBeginWriting:(UIScribbleInteraction*)interaction { NSLog(@"[scribble] scribbleInteractionWillBeginWriting"); _scribbleInProgress = true; [_textInputDelegate scribbleInteractionBegan]; } -- (void)scribbleInteractionDidFinishWriting:(UIScribbleInteraction *)interaction { +- (void)scribbleInteractionDidFinishWriting:(UIScribbleInteraction*)interaction { NSLog(@"[scribble] scribbleInteractionDidFinishWriting"); _scribbleInProgress = false; [_textInputDelegate scribbleInteractionFinished]; } -- (BOOL)scribbleInteraction:(UIScribbleInteraction *)interaction +- (BOOL)scribbleInteraction:(UIScribbleInteraction*)interaction shouldBeginAtLocation:(CGPoint)location { - NSLog(@"[scribble] scribbleInteraction shouldBeginAtLocation: %@, %@", @(location.x), @(location.y)); + NSLog(@"[scribble] scribbleInteraction shouldBeginAtLocation: %@, %@", @(location.x), + @(location.y)); return true; } -- (BOOL)scribbleInteractionShouldDelayFocus:(UIScribbleInteraction *)interaction { +- (BOOL)scribbleInteractionShouldDelayFocus:(UIScribbleInteraction*)interaction { NSLog(@"[scribble] scribbleInteractionShouldDelayFocus"); return false; } @@ -924,7 +930,8 @@ - (void)setMarkedText:(NSString*)markedText selectedRange:(NSRange)markedSelecte NSRange selectedRange = _selectedTextRange.range; NSRange markedTextRange = ((FlutterTextRange*)self.markedTextRange).range; - if (_scribbleInProgress) return; + if (_scribbleInProgress) + return; if (markedText == nil) markedText = @""; @@ -1128,12 +1135,15 @@ - (void)setEditableTransform:(NSArray*)matrix { // candidates view for multi-stage input methods (e.g., Japanese) when using a // physical keyboard. -- (void) setSelectionRects:(NSArray*)rects { - NSMutableArray *rectsAsRect = [[NSMutableArray alloc] initWithCapacity:[rects count]]; - for (NSUInteger i = 0; i < [rects count]; i++) { - NSArray *rect = rects[i]; - [rectsAsRect addObject:@[[NSNumber numberWithInt:[rect[0] intValue]], [NSNumber numberWithInt:[rect[1] intValue]], [NSNumber numberWithInt:[rect[2] intValue]], [NSNumber numberWithInt:[rect[3] intValue]]]]; - } +- (void)setSelectionRects:(NSArray*)rects { + NSMutableArray* rectsAsRect = [[NSMutableArray alloc] initWithCapacity:[rects count]]; + for (NSUInteger i = 0; i < [rects count]; i++) { + NSArray* rect = rects[i]; + [rectsAsRect addObject:@[ + [NSNumber numberWithInt:[rect[0] intValue]], [NSNumber numberWithInt:[rect[1] intValue]], + [NSNumber numberWithInt:[rect[2] intValue]], [NSNumber numberWithInt:[rect[3] intValue]] + ]]; + } _selectionRects = rectsAsRect; } @@ -1169,18 +1179,22 @@ - (CGRect)firstRectForRange:(UITextRange*)range { if (!_scribbleInProgress) { [_textInputDelegate showAutocorrectionPromptRectForStart:start - end:end + end:end withClient:_textInputClient]; } NSLog(@"[scribble] firstRectForRange %@ - %@", @(start), @(end)); NSUInteger first = start; if (end < start) { - first = end; + first = end; } if ([_selectionRects count] > first) { - NSLog(@"[scribble] firstRectForRange -> %f, %f, %f, %f", [_selectionRects[first][0] floatValue], [_selectionRects[first][1] floatValue], [_selectionRects[first][2] floatValue], [_selectionRects[first][3] floatValue]); - return CGRectMake([_selectionRects[first][0] floatValue], [_selectionRects[first][1] floatValue], [_selectionRects[first][2] floatValue], [_selectionRects[first][3] floatValue]); + NSLog(@"[scribble] firstRectForRange -> %f, %f, %f, %f", [_selectionRects[first][0] floatValue], + [_selectionRects[first][1] floatValue], [_selectionRects[first][2] floatValue], + [_selectionRects[first][3] floatValue]); + return CGRectMake( + [_selectionRects[first][0] floatValue], [_selectionRects[first][1] floatValue], + [_selectionRects[first][2] floatValue], [_selectionRects[first][3] floatValue]); } NSLog(@"[scribble] firstRectForRange -> CGRectZero"); // TODO(cbracken) Implement. @@ -1203,7 +1217,8 @@ - (UITextPosition*)closestPositionToPoint:(CGPoint)point { return [FlutterTextPosition positionWithIndex:currentIndex]; } - FlutterTextRange* range = [[FlutterTextRange rangeWithNSRange:NSMakeRange(0, [_selectionRects count])] copy]; + FlutterTextRange* range = + [[FlutterTextRange rangeWithNSRange:NSMakeRange(0, [_selectionRects count])] copy]; return [self closestPositionToPoint:point withinRange:range]; } @@ -1214,10 +1229,18 @@ - (NSArray*)selectionRectsForRange:(UITextRange*)range { NSLog(@"[scribble] selectionRectsForRange %@ -> %@", @(start), @(end)); NSMutableArray* rects = [[NSMutableArray alloc] init]; for (NSUInteger i = start; i <= end && i < [_selectionRects count]; i++) { - float width = [_selectionRects[i][2] floatValue]; - if (start == end) { width = 0; } - CGRect rect = CGRectMake([_selectionRects[i][0] floatValue], [_selectionRects[i][1] floatValue], width, [_selectionRects[i][3] floatValue]); - FlutterTextSelectionRect* selectionRect = [[FlutterTextSelectionRect alloc] initWithRectAndInfo:rect writingDirection:UITextWritingDirectionNatural containsStart:(i == 0) containsEnd:(i == self.text.length) isVertical:FALSE]; + float width = [_selectionRects[i][2] floatValue]; + if (start == end) { + width = 0; + } + CGRect rect = CGRectMake([_selectionRects[i][0] floatValue], [_selectionRects[i][1] floatValue], + width, [_selectionRects[i][3] floatValue]); + FlutterTextSelectionRect* selectionRect = + [[FlutterTextSelectionRect alloc] initWithRectAndInfo:rect + writingDirection:UITextWritingDirectionNatural + containsStart:(i == 0) + containsEnd:(i == self.text.length) + isVertical:FALSE]; [rects addObject:selectionRect]; } return rects; @@ -1227,23 +1250,26 @@ - (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRang // TODO(cbracken) Implement. NSUInteger start = ((FlutterTextPosition*)range.start).index; NSUInteger end = ((FlutterTextPosition*)range.end).index; - NSLog(@"[scribble] closestPositionToPoint (%@, %@) withinRange %@, %@", @(point.x), @(point.y), @(start), @(end)); + NSLog(@"[scribble] closestPositionToPoint (%@, %@) withinRange %@, %@", @(point.x), @(point.y), + @(start), @(end)); NSUInteger _closestIndex = start; CGRect _closestRect = CGRectZero; float _closestY = 0; float _closestX = 0; for (NSUInteger i = start; i < [_selectionRects count] && i < end; i++) { - CGRect rect = CGRectMake([_selectionRects[i][0] floatValue], [_selectionRects[i][1] floatValue], [_selectionRects[i][2] floatValue], [_selectionRects[i][3] floatValue]); + CGRect rect = + CGRectMake([_selectionRects[i][0] floatValue], [_selectionRects[i][1] floatValue], + [_selectionRects[i][2] floatValue], [_selectionRects[i][3] floatValue]); CGPoint pointForComparison = CGPointMake(rect.origin.x, rect.origin.y + rect.size.height * 0.5); float yDist = abs(pointForComparison.y - point.y); float xDist = abs(pointForComparison.x - point.x); if (_closestIndex == i || - (yDist < _closestY || - (yDist == _closestY && ( - (point.y <= rect.origin.y + rect.size.height && xDist < _closestX) || - (point.y > rect.origin.y + rect.size.height && rect.origin.x > _closestRect.origin.x) - )))) { + (yDist < _closestY || + (yDist == _closestY && + ((point.y <= rect.origin.y + rect.size.height && xDist < _closestX) || + (point.y > rect.origin.y + rect.size.height && + rect.origin.x > _closestRect.origin.x))))) { _closestY = yDist; _closestX = xDist; _closestIndex = i; @@ -1253,22 +1279,25 @@ - (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRang if ([_selectionRects count] > 0 && [_selectionRects count] >= end) { NSUInteger i = end - 1; - CGRect rect = CGRectMake([_selectionRects[i][0] floatValue], [_selectionRects[i][1] floatValue], [_selectionRects[i][2] floatValue], [_selectionRects[i][3] floatValue]); - CGPoint pointForComparison = CGPointMake(rect.origin.x + rect.size.width, rect.origin.y + rect.size.height * 0.5); + CGRect rect = + CGRectMake([_selectionRects[i][0] floatValue], [_selectionRects[i][1] floatValue], + [_selectionRects[i][2] floatValue], [_selectionRects[i][3] floatValue]); + CGPoint pointForComparison = + CGPointMake(rect.origin.x + rect.size.width, rect.origin.y + rect.size.height * 0.5); float yDist = abs(pointForComparison.y - point.y); float xDist = abs(pointForComparison.x - point.x); - if (yDist < _closestY || - (yDist == _closestY && ( - (point.y <= rect.origin.y + rect.size.height && xDist < _closestX) || - (point.y > rect.origin.y + rect.size.height && rect.origin.x + rect.size.width > _closestRect.origin.x) - ))) { + if (yDist < _closestY || (yDist == _closestY && + ((point.y <= rect.origin.y + rect.size.height && xDist < _closestX) || + (point.y > rect.origin.y + rect.size.height && + rect.origin.x + rect.size.width > _closestRect.origin.x)))) { _closestY = yDist; _closestX = xDist; _closestIndex = end; } } - NSLog(@"[scribble] closestPositionToPoint (%@, %@) withinRange %@, %@ -> %@", @(point.x), @(point.y), @(start), @(end), @(_closestIndex)); + NSLog(@"[scribble] closestPositionToPoint (%@, %@) withinRange %@, %@ -> %@", @(point.x), + @(point.y), @(start), @(end), @(_closestIndex)); return [FlutterTextPosition positionWithIndex:_closestIndex]; } @@ -1537,7 +1566,9 @@ - (void)setEditableSizeAndTransform:(NSDictionary*)dictionary { [_activeView setEditableTransform:dictionary[@"transform"]]; // TODO: only do this on iPadOS? // This seems necessary to set up where the scribble interactable element will be - _activeView.frame = CGRectMake([dictionary[@"transform"][12] intValue], [dictionary[@"transform"][13] intValue], [dictionary[@"width"] intValue], [dictionary[@"height"] intValue]); + _activeView.frame = + CGRectMake([dictionary[@"transform"][12] intValue], [dictionary[@"transform"][13] intValue], + [dictionary[@"width"] intValue], [dictionary[@"height"] intValue]); } - (void)updateMarkedRect:(NSDictionary*)dictionary { @@ -1799,7 +1830,8 @@ - (void)addToInputParentViewIfNeeded:(FlutterTextInputView*)inputView { if (_inputHider.superview != parentView) { [parentView addSubview:_inputHider]; if (@available(iOS 14.0, *)) { - UIScribbleInteraction* interaction = [[[UIScribbleInteraction alloc] initWithDelegate:inputView] autorelease]; + UIScribbleInteraction* interaction = + [[[UIScribbleInteraction alloc] initWithDelegate:inputView] autorelease]; [inputView addInteraction:interaction]; } } @@ -1825,13 +1857,16 @@ - (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction focusElementIfNeeded:(UIScribbleElementIdentifier)elementIdentifier referencePoint:(CGPoint)focusReferencePoint completion:(void (^)(UIResponder* focusedInput))completion { - NSLog(@"[scribble][delegate] focusElementIfNeeded:%@ referencePoint:(%@, %@)", elementIdentifier, @(focusReferencePoint.x), @(focusReferencePoint.y)); + NSLog(@"[scribble][delegate] focusElementIfNeeded:%@ referencePoint:(%@, %@)", elementIdentifier, + @(focusReferencePoint.x), @(focusReferencePoint.y)); _reusableInputView.scribbleFocusing = true; - [_textInputDelegate focusElement:elementIdentifier atPoint:focusReferencePoint result:^(id _Nullable result){ - _reusableInputView.scribbleFocusing = false; - _reusableInputView.scribbleFocused = true; - completion(_reusableInputView); - }]; + [_textInputDelegate focusElement:elementIdentifier + atPoint:focusReferencePoint + result:^(id _Nullable result) { + _reusableInputView.scribbleFocusing = false; + _reusableInputView.scribbleFocused = true; + completion(_reusableInputView); + }]; } - (BOOL)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction @@ -1865,21 +1900,30 @@ - (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction completion: (void (^)(NSArray* elements))completion { NSLog(@"[scribble][delegate] requestElementsInRect:%@", @(rect)); - [_textInputDelegate requestElementsInRect:rect result:^(id _Nullable result){ - NSMutableArray* elements = [[NSMutableArray alloc] init]; - if ([result isKindOfClass:[NSArray class]]) { - for (NSArray* elementArray in result) { - [elements addObject:elementArray[0]]; - [_scribbleElements setObject:[NSValue valueWithCGRect:CGRectMake([elementArray[1] floatValue], [elementArray[2] floatValue], [elementArray[3] floatValue], [elementArray[4] floatValue])] forKey:elementArray[0]]; - } - } - completion(elements); - }]; + [_textInputDelegate + requestElementsInRect:rect + result:^(id _Nullable result) { + NSMutableArray* elements = + [[NSMutableArray alloc] init]; + if ([result isKindOfClass:[NSArray class]]) { + for (NSArray* elementArray in result) { + [elements addObject:elementArray[0]]; + [_scribbleElements + setObject:[NSValue valueWithCGRect:CGRectMake( + [elementArray[1] floatValue], + [elementArray[2] floatValue], + [elementArray[3] floatValue], + [elementArray[4] floatValue])] + forKey:elementArray[0]]; + } + } + completion(elements); + }]; } #pragma mark - Methods related to Scribble support -- (void) setupIndirectScribbleInteraction { +- (void)setupIndirectScribbleInteraction { if (_hasScribbleInteraction) { return; } @@ -1887,7 +1931,8 @@ - (void) setupIndirectScribbleInteraction { UIView* parentView = _viewController.view; if (parentView != nil) { _hasScribbleInteraction = true; - UIIndirectScribbleInteraction* _scribbleInteraction = [[[UIIndirectScribbleInteraction alloc] initWithDelegate:(id)self] autorelease]; + UIIndirectScribbleInteraction* _scribbleInteraction = [[[UIIndirectScribbleInteraction alloc] + initWithDelegate:(id)self] autorelease]; [parentView addInteraction:_scribbleInteraction]; } } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m index 910657ed4ec28..d859ab78b6d19 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m @@ -501,15 +501,17 @@ - (void)testUpdateFirstRectForRange { - (void)testFirstRectForRangeReturnsCorrectSelectionRect { FlutterTextInputView* inputView = [[FlutterTextInputView alloc] init]; - [inputView - setTextInputState:@{@"text" : @"COMPOSING"}]; + [inputView setTextInputState:@{@"text" : @"COMPOSING"}]; FlutterTextRange* range = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 1)]; CGRect testRect = CGRectMake(100, 100, 100, 100); [inputView setSelectionRects:@[ - @[@0, @0, @100, @100], - @[@(testRect.origin.x), @(testRect.origin.y), @(testRect.size.width), @(testRect.size.height)], - @[@200, @200, @100, @100]]]; + @[ @0, @0, @100, @100 ], + @[ + @(testRect.origin.x), @(testRect.origin.y), @(testRect.size.width), @(testRect.size.height) + ], + @[ @200, @200, @100, @100 ] + ]]; NSLog(@"testRect: %@, firstRect: %@", @(testRect), @([inputView firstRectForRange:range])); XCTAssertTrue(CGRectEqualToRect(testRect, [inputView firstRectForRange:range])); @@ -519,46 +521,47 @@ - (void)testFirstRectForRangeReturnsCorrectSelectionRect { - (void)testClosestPositionToPoint { FlutterTextInputView* inputView = [[FlutterTextInputView alloc] init]; - [inputView - setTextInputState:@{@"text" : @"COMPOSING"}]; + [inputView setTextInputState:@{@"text" : @"COMPOSING"}]; // Minimize the vertical distance from the center of the rects first [inputView setSelectionRects:@[ - @[@0, @0, @100, @100], - @[@0, @100, @100, @100], - @[@0, @200, @100, @100], + @[ @0, @0, @100, @100 ], + @[ @0, @100, @100, @100 ], + @[ @0, @200, @100, @100 ], ]]; CGPoint point = CGPointMake(150, 150); XCTAssertEqual(1, ((FlutterTextPosition*)[inputView closestPositionToPoint:point]).index); - // Then, if the point is above the bottom of the closest rects vertically, get the closest x origin + // Then, if the point is above the bottom of the closest rects vertically, get the closest x + // origin [inputView setSelectionRects:@[ - @[@0, @0, @100, @100], - @[@0, @100, @100, @100], - @[@100, @100, @100, @100], - @[@200, @100, @100, @100], - @[@0, @200, @100, @100], + @[ @0, @0, @100, @100 ], + @[ @0, @100, @100, @100 ], + @[ @100, @100, @100, @100 ], + @[ @200, @100, @100, @100 ], + @[ @0, @200, @100, @100 ], ]]; point = CGPointMake(125, 150); XCTAssertEqual(2, ((FlutterTextPosition*)[inputView closestPositionToPoint:point]).index); - // However, if the point is below the bottom of the closest rects vertically, get the position farthest to the right + // However, if the point is below the bottom of the closest rects vertically, get the position + // farthest to the right [inputView setSelectionRects:@[ - @[@0, @0, @100, @100], - @[@0, @100, @100, @100], - @[@100, @100, @100, @100], - @[@200, @100, @100, @100], - @[@0, @300, @100, @100], + @[ @0, @0, @100, @100 ], + @[ @0, @100, @100, @100 ], + @[ @100, @100, @100, @100 ], + @[ @200, @100, @100, @100 ], + @[ @0, @300, @100, @100 ], ]]; point = CGPointMake(125, 201); XCTAssertEqual(3, ((FlutterTextPosition*)[inputView closestPositionToPoint:point]).index); // Also check a point at the right edge of the last selection rect [inputView setSelectionRects:@[ - @[@0, @0, @100, @100], - @[@0, @100, @100, @100], - @[@100, @100, @100, @100], - @[@200, @100, @100, @100], + @[ @0, @0, @100, @100 ], + @[ @0, @100, @100, @100 ], + @[ @100, @100, @100, @100 ], + @[ @200, @100, @100, @100 ], ]]; point = CGPointMake(125, 250); XCTAssertEqual(4, ((FlutterTextPosition*)[inputView closestPositionToPoint:point]).index); @@ -566,18 +569,24 @@ - (void)testClosestPositionToPoint { - (void)testSelectionRectsForRange { FlutterTextInputView* inputView = [[FlutterTextInputView alloc] init]; - [inputView - setTextInputState:@{@"text" : @"COMPOSING"}]; + [inputView setTextInputState:@{@"text" : @"COMPOSING"}]; FlutterTextRange* range = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 1)]; CGRect testRect0 = CGRectMake(100, 100, 100, 100); CGRect testRect1 = CGRectMake(200, 200, 100, 100); [inputView setSelectionRects:@[ - @[@0, @0, @100, @100], - @[@(testRect0.origin.x), @(testRect0.origin.y), @(testRect0.size.width), @(testRect0.size.height)], - @[@(testRect1.origin.x), @(testRect1.origin.y), @(testRect1.size.width), @(testRect1.size.height)], - @[@300, @300, @100, @100]]]; + @[ @0, @0, @100, @100 ], + @[ + @(testRect0.origin.x), @(testRect0.origin.y), @(testRect0.size.width), + @(testRect0.size.height) + ], + @[ + @(testRect1.origin.x), @(testRect1.origin.y), @(testRect1.size.width), + @(testRect1.size.height) + ], + @[ @300, @300, @100, @100 ] + ]]; XCTAssertTrue(CGRectEqualToRect(testRect0, [inputView selectionRectsForRange:range][0].rect)); XCTAssertTrue(CGRectEqualToRect(testRect1, [inputView selectionRectsForRange:range][1].rect)); XCTAssertEqual(2, [[inputView selectionRectsForRange:range] count]); @@ -585,32 +594,33 @@ - (void)testSelectionRectsForRange { - (void)testClosestPositionToPointWithinRange { FlutterTextInputView* inputView = [[FlutterTextInputView alloc] init]; - [inputView - setTextInputState:@{@"text" : @"COMPOSING"}]; + [inputView setTextInputState:@{@"text" : @"COMPOSING"}]; // Do not return a position before the start of the range [inputView setSelectionRects:@[ - @[@0, @0, @100, @100], - @[@0, @100, @100, @100], - @[@100, @100, @100, @100], - @[@200, @100, @100, @100], - @[@0, @200, @100, @100], + @[ @0, @0, @100, @100 ], + @[ @0, @100, @100, @100 ], + @[ @100, @100, @100, @100 ], + @[ @200, @100, @100, @100 ], + @[ @0, @200, @100, @100 ], ]]; CGPoint point = CGPointMake(125, 150); FlutterTextRange* range = [[FlutterTextRange rangeWithNSRange:NSMakeRange(3, 2)] copy]; - XCTAssertEqual(3, ((FlutterTextPosition*)[inputView closestPositionToPoint:point withinRange:range]).index); + XCTAssertEqual( + 3, ((FlutterTextPosition*)[inputView closestPositionToPoint:point withinRange:range]).index); // Do not return a position after the end of the range [inputView setSelectionRects:@[ - @[@0, @0, @100, @100], - @[@0, @100, @100, @100], - @[@100, @100, @100, @100], - @[@200, @100, @100, @100], - @[@0, @200, @100, @100], + @[ @0, @0, @100, @100 ], + @[ @0, @100, @100, @100 ], + @[ @100, @100, @100, @100 ], + @[ @200, @100, @100, @100 ], + @[ @0, @200, @100, @100 ], ]]; point = CGPointMake(125, 150); range = [[FlutterTextRange rangeWithNSRange:NSMakeRange(0, 1)] copy]; - XCTAssertEqual(1, ((FlutterTextPosition*)[inputView closestPositionToPoint:point withinRange:range]).index); + XCTAssertEqual( + 1, ((FlutterTextPosition*)[inputView closestPositionToPoint:point withinRange:range]).index); } #pragma mark - Autofill - Utilities From 0844910f56e6799b0982ffb11641799a4e02246f Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Fri, 5 Mar 2021 16:05:36 -0600 Subject: [PATCH 06/52] [scribble+master] Pass calls to insertTextPlaceholderWithSize and removeTextPlaceholder to the framework --- .../ios/framework/Source/FlutterEngine.mm | 10 +++++ .../Source/FlutterTextInputDelegate.h | 2 + .../framework/Source/FlutterTextInputPlugin.h | 6 +++ .../Source/FlutterTextInputPlugin.mm | 40 ++++++++++++++----- 4 files changed, 48 insertions(+), 10 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index 247c15b8b798a..92f551dc738d3 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -793,6 +793,16 @@ - (void)scribbleInteractionFinished { arguments:nil]; } +- (void)insertTextPlaceholderWithSize:(CGSize)size withClient:(int)client { + [_textInputChannel.get() invokeMethod:@"TextInputClient.insertTextPlaceholder" + arguments:@[ @(client), @(size.width), @(size.height) ]]; +} + +- (void)removeTextPlaceholder:(int)client { + [_textInputChannel.get() invokeMethod:@"TextInputClient.removeTextPlaceholder" + arguments:@[ @(client) ]]; +} + #pragma mark - Screenshot Delegate - (flutter::Rasterizer::Screenshot)takeScreenshot:(flutter::Rasterizer::ScreenshotType)type diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h index 01f63647b5e2b..fd73059999fb4 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h @@ -45,6 +45,8 @@ typedef NS_ENUM(NSInteger, FlutterFloatingCursorDragState) { - (void)requestElementsInRect:(CGRect)rect result:(id)callback; - (void)scribbleInteractionBegan; - (void)scribbleInteractionFinished; +- (void)insertTextPlaceholderWithSize:(CGSize)size withClient:(int)client; +- (void)removeTextPlaceholder:(int)client; @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h index 2a452217ac302..7c001001aaeeb 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h @@ -104,6 +104,9 @@ @end +API_AVAILABLE(ios(13.0)) @interface FlutterTextPlaceholder : UITextPlaceholder +@end + #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG FLUTTER_DARWIN_EXPORT #endif @@ -130,6 +133,9 @@ FLUTTER_DARWIN_EXPORT @property(nonatomic) UITextSmartDashesType smartDashesType API_AVAILABLE(ios(11.0)); @property(nonatomic, copy) UITextContentType textContentType API_AVAILABLE(ios(10.0)); +- (UITextPlaceholder*)insertTextPlaceholderWithSize:(CGSize)size API_AVAILABLE(ios(13.0)); +- (void)removeTextPlaceholder:(UITextPlaceholder*)textPlaceholder API_AVAILABLE(ios(13.0)); + // UIScribbleInteractionDelegate - (void)scribbleInteractionWillBeginWriting:(UIScribbleInteraction*)interaction API_AVAILABLE(ios(14.0)); diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index a8ad581b0fc65..8dd47af5063dd 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -456,11 +456,11 @@ + (instancetype)selectionRectWithRectAndInfo:(CGRect)rect containsStart:(BOOL)containsStart containsEnd:(BOOL)containsEnd isVertical:(BOOL)isVertical { - return [[[FlutterTextSelectionRect alloc] initWithRectAndInfo:rect - writingDirection:writingDirection - containsStart:containsStart - containsEnd:containsEnd - isVertical:isVertical] autorelease]; + return [[FlutterTextSelectionRect alloc] initWithRectAndInfo:rect + writingDirection:writingDirection + containsStart:containsStart + containsEnd:containsEnd + isVertical:isVertical]; } - (instancetype)initWithRectAndInfo:(CGRect)rect @@ -481,6 +481,20 @@ - (instancetype)initWithRectAndInfo:(CGRect)rect @end +#pragma mark - FlutterTextPlaceholder + +@implementation FlutterTextPlaceholder { + NSArray* _notRects; +} + +- (NSArray*)rects { + // Returning anything other than an empty array here seems to cause PencilKit to enter an infinite + // loop of allocating placeholders until the app crashes + return @[]; +} + +@end + // A FlutterTextInputView that masquerades as a UITextField, and forwards // selectors it can't respond to to a shared UITextField instance. // @@ -1183,20 +1197,15 @@ - (CGRect)firstRectForRange:(UITextRange*)range { withClient:_textInputClient]; } - NSLog(@"[scribble] firstRectForRange %@ - %@", @(start), @(end)); NSUInteger first = start; if (end < start) { first = end; } if ([_selectionRects count] > first) { - NSLog(@"[scribble] firstRectForRange -> %f, %f, %f, %f", [_selectionRects[first][0] floatValue], - [_selectionRects[first][1] floatValue], [_selectionRects[first][2] floatValue], - [_selectionRects[first][3] floatValue]); return CGRectMake( [_selectionRects[first][0] floatValue], [_selectionRects[first][1] floatValue], [_selectionRects[first][2] floatValue], [_selectionRects[first][3] floatValue]); } - NSLog(@"[scribble] firstRectForRange -> CGRectZero"); // TODO(cbracken) Implement. return CGRectZero; } @@ -1366,6 +1375,17 @@ - (void)insertText:(NSString*)text { [self replaceRange:_selectedTextRange withText:text]; } +- (UITextPlaceholder*)insertTextPlaceholderWithSize:(CGSize)size { + NSLog(@"[scribble] insertTextPlaceholderWithSize"); + [_textInputDelegate insertTextPlaceholderWithSize:size withClient:_textInputClient]; + return [[FlutterTextPlaceholder alloc] init]; +} + +- (void)removeTextPlaceholder:(UITextPlaceholder*)textPlaceholder { + NSLog(@"[scribble] removeTextPlaceholder"); + [_textInputDelegate removeTextPlaceholder:_textInputClient]; +} + - (void)deleteBackward { _selectionAffinity = _kTextAffinityDownstream; From 3e870273503787777f360a96ae18825050fcc6e1 Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Tue, 6 Apr 2021 16:45:21 -0500 Subject: [PATCH 07/52] [scribble+master] Update scribble support to handle multi-byte characters like emoji --- .../Source/FlutterTextInputPlugin.mm | 139 +++++++++--------- 1 file changed, 68 insertions(+), 71 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 8dd47af5063dd..67b9e703aa641 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -483,9 +483,7 @@ - (instancetype)initWithRectAndInfo:(CGRect)rect #pragma mark - FlutterTextPlaceholder -@implementation FlutterTextPlaceholder { - NSArray* _notRects; -} +@implementation FlutterTextPlaceholder - (NSArray*)rects { // Returning anything other than an empty array here seems to cause PencilKit to enter an infinite @@ -761,26 +759,21 @@ - (void)setIsVisibleToAutofill:(BOOL)isVisibleToAutofill { #pragma mark UIScribbleInteractionDelegate - (void)scribbleInteractionWillBeginWriting:(UIScribbleInteraction*)interaction { - NSLog(@"[scribble] scribbleInteractionWillBeginWriting"); _scribbleInProgress = true; [_textInputDelegate scribbleInteractionBegan]; } - (void)scribbleInteractionDidFinishWriting:(UIScribbleInteraction*)interaction { - NSLog(@"[scribble] scribbleInteractionDidFinishWriting"); _scribbleInProgress = false; [_textInputDelegate scribbleInteractionFinished]; } - (BOOL)scribbleInteraction:(UIScribbleInteraction*)interaction shouldBeginAtLocation:(CGPoint)location { - NSLog(@"[scribble] scribbleInteraction shouldBeginAtLocation: %@, %@", @(location.x), - @(location.y)); return true; } - (BOOL)scribbleInteractionShouldDelayFocus:(UIScribbleInteraction*)interaction { - NSLog(@"[scribble] scribbleInteractionShouldDelayFocus"); return false; } @@ -940,7 +933,6 @@ - (BOOL)shouldChangeTextInRange:(UITextRange*)range replacementText:(NSString*)t } - (void)setMarkedText:(NSString*)markedText selectedRange:(NSRange)markedSelectedRange { - NSLog(@"[scribble] setMarkedText %@", markedText); NSRange selectedRange = _selectedTextRange.range; NSRange markedTextRange = ((FlutterTextRange*)self.markedTextRange).range; @@ -1012,6 +1004,10 @@ - (UITextPosition*)positionFromPosition:(UITextPosition*)position offset:(NSInte return nil; } + if (_scribbleInProgress) { + return [FlutterTextPosition positionWithIndex:newLocation]; + } + if (offset >= 0) { for (NSInteger i = 0; i < offset && offsetPosition < self.text.length; ++i) offsetPosition = [self incrementOffsetPosition:offsetPosition]; @@ -1155,7 +1151,8 @@ - (void)setSelectionRects:(NSArray*)rects { NSArray* rect = rects[i]; [rectsAsRect addObject:@[ [NSNumber numberWithInt:[rect[0] intValue]], [NSNumber numberWithInt:[rect[1] intValue]], - [NSNumber numberWithInt:[rect[2] intValue]], [NSNumber numberWithInt:[rect[3] intValue]] + [NSNumber numberWithInt:[rect[2] intValue]], [NSNumber numberWithInt:[rect[3] intValue]], + [NSNumber numberWithInt:[rect[4] intValue]] ]]; } _selectionRects = rectsAsRect; @@ -1201,11 +1198,17 @@ - (CGRect)firstRectForRange:(UITextRange*)range { if (end < start) { first = end; } - if ([_selectionRects count] > first) { - return CGRectMake( - [_selectionRects[first][0] floatValue], [_selectionRects[first][1] floatValue], - [_selectionRects[first][2] floatValue], [_selectionRects[first][3] floatValue]); + for (NSUInteger i = 0; i < [_selectionRects count]; i++) { + if ([_selectionRects[i][4] unsignedIntegerValue] <= first && + (i + 1 == [_selectionRects count] || + [_selectionRects[i + 1][4] unsignedIntegerValue] > first)) { + CGRect rect = + CGRectMake([_selectionRects[i][0] floatValue], [_selectionRects[i][1] floatValue], + [_selectionRects[i][2] floatValue], [_selectionRects[i][3] floatValue]); + return rect; + } } + // TODO(cbracken) Implement. return CGRectZero; } @@ -1213,21 +1216,19 @@ - (CGRect)firstRectForRange:(UITextRange*)range { - (CGRect)caretRectForPosition:(UITextPosition*)position { // TODO(cbracken) Implement. NSUInteger start = ((FlutterTextPosition*)position).index; - NSLog(@"[scribble][cursor] caretRectForPosition (%@)", @(start)); return CGRectZero; } - (UITextPosition*)closestPositionToPoint:(CGPoint)point { // TODO(cbracken) Implement. - NSLog(@"[scribble] closestPositionToPoint (%@, %@)", @(point.x), @(point.y)); - if ([_selectionRects count] == 0) { NSUInteger currentIndex = ((FlutterTextPosition*)_selectedTextRange.start).index; return [FlutterTextPosition positionWithIndex:currentIndex]; } - FlutterTextRange* range = - [[FlutterTextRange rangeWithNSRange:NSMakeRange(0, [_selectionRects count])] copy]; + FlutterTextRange* range = [[FlutterTextRange + rangeWithNSRange:fml::RangeForCharactersInRange(self.text, NSMakeRange(0, self.text.length))] + copy]; return [self closestPositionToPoint:point withinRange:range]; } @@ -1235,22 +1236,27 @@ - (NSArray*)selectionRectsForRange:(UITextRange*)range { // TODO(cbracken) Implement. NSUInteger start = ((FlutterTextPosition*)range.start).index; NSUInteger end = ((FlutterTextPosition*)range.end).index; - NSLog(@"[scribble] selectionRectsForRange %@ -> %@", @(start), @(end)); NSMutableArray* rects = [[NSMutableArray alloc] init]; - for (NSUInteger i = start; i <= end && i < [_selectionRects count]; i++) { - float width = [_selectionRects[i][2] floatValue]; - if (start == end) { - width = 0; + for (NSUInteger i = 0; i < [_selectionRects count]; i++) { + if ([_selectionRects[i][4] unsignedIntegerValue] >= start && + [_selectionRects[i][4] unsignedIntegerValue] <= end) { + float width = [_selectionRects[i][2] floatValue]; + if (start == end) { + width = 0; + } + CGRect rect = + CGRectMake([_selectionRects[i][0] floatValue], [_selectionRects[i][1] floatValue], width, + [_selectionRects[i][3] floatValue]); + FlutterTextSelectionRect* selectionRect = [[FlutterTextSelectionRect alloc] + initWithRectAndInfo:rect + writingDirection:UITextWritingDirectionNatural + containsStart:(i == 0) + containsEnd:(i == fml::RangeForCharactersInRange(self.text, + NSMakeRange(0, self.text.length)) + .length) + isVertical:FALSE]; + [rects addObject:selectionRect]; } - CGRect rect = CGRectMake([_selectionRects[i][0] floatValue], [_selectionRects[i][1] floatValue], - width, [_selectionRects[i][3] floatValue]); - FlutterTextSelectionRect* selectionRect = - [[FlutterTextSelectionRect alloc] initWithRectAndInfo:rect - writingDirection:UITextWritingDirectionNatural - containsStart:(i == 0) - containsEnd:(i == self.text.length) - isVertical:FALSE]; - [rects addObject:selectionRect]; } return rects; } @@ -1259,35 +1265,39 @@ - (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRang // TODO(cbracken) Implement. NSUInteger start = ((FlutterTextPosition*)range.start).index; NSUInteger end = ((FlutterTextPosition*)range.end).index; - NSLog(@"[scribble] closestPositionToPoint (%@, %@) withinRange %@, %@", @(point.x), @(point.y), - @(start), @(end)); - NSUInteger _closestIndex = start; + NSUInteger _closestIndex = 0; CGRect _closestRect = CGRectZero; + NSUInteger _closestPosition = 0; float _closestY = 0; float _closestX = 0; - for (NSUInteger i = start; i < [_selectionRects count] && i < end; i++) { - CGRect rect = - CGRectMake([_selectionRects[i][0] floatValue], [_selectionRects[i][1] floatValue], - [_selectionRects[i][2] floatValue], [_selectionRects[i][3] floatValue]); - CGPoint pointForComparison = CGPointMake(rect.origin.x, rect.origin.y + rect.size.height * 0.5); - float yDist = abs(pointForComparison.y - point.y); - float xDist = abs(pointForComparison.x - point.x); - if (_closestIndex == i || - (yDist < _closestY || - (yDist == _closestY && - ((point.y <= rect.origin.y + rect.size.height && xDist < _closestX) || - (point.y > rect.origin.y + rect.size.height && - rect.origin.x > _closestRect.origin.x))))) { - _closestY = yDist; - _closestX = xDist; - _closestIndex = i; - _closestRect = rect; + for (NSUInteger i = 0; i < [_selectionRects count] && i < end; i++) { + NSUInteger position = [_selectionRects[i][4] unsignedIntegerValue]; + if (position >= start && position <= end) { + CGRect rect = + CGRectMake([_selectionRects[i][0] floatValue], [_selectionRects[i][1] floatValue], + [_selectionRects[i][2] floatValue], [_selectionRects[i][3] floatValue]); + CGPoint pointForComparison = + CGPointMake(rect.origin.x, rect.origin.y + rect.size.height * 0.5); + float yDist = abs(pointForComparison.y - point.y); + float xDist = abs(pointForComparison.x - point.x); + if (_closestIndex == 0 || + (yDist < _closestY || + (yDist == _closestY && + ((point.y <= rect.origin.y + rect.size.height && xDist < _closestX) || + (point.y > rect.origin.y + rect.size.height && + rect.origin.x > _closestRect.origin.x))))) { + _closestY = yDist; + _closestX = xDist; + _closestIndex = i; + _closestRect = rect; + _closestPosition = position; + } } } - if ([_selectionRects count] > 0 && [_selectionRects count] >= end) { - NSUInteger i = end - 1; + if ([_selectionRects count] > 0 && self.text.length >= end) { + NSUInteger i = [_selectionRects count] - 1; CGRect rect = CGRectMake([_selectionRects[i][0] floatValue], [_selectionRects[i][1] floatValue], [_selectionRects[i][2] floatValue], [_selectionRects[i][3] floatValue]); @@ -1301,13 +1311,12 @@ - (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRang rect.origin.x + rect.size.width > _closestRect.origin.x)))) { _closestY = yDist; _closestX = xDist; - _closestIndex = end; + _closestIndex = [_selectionRects count]; + _closestPosition = end; } } - NSLog(@"[scribble] closestPositionToPoint (%@, %@) withinRange %@, %@ -> %@", @(point.x), - @(point.y), @(start), @(end), @(_closestIndex)); - return [FlutterTextPosition positionWithIndex:_closestIndex]; + return [FlutterTextPosition positionWithIndex:_closestPosition]; } - (UITextRange*)characterRangeAtPoint:(CGPoint)point { @@ -1370,19 +1379,16 @@ - (BOOL)hasText { } - (void)insertText:(NSString*)text { - NSLog(@"[scribble] insertText: %@", text); _selectionAffinity = _kTextAffinityDownstream; [self replaceRange:_selectedTextRange withText:text]; } - (UITextPlaceholder*)insertTextPlaceholderWithSize:(CGSize)size { - NSLog(@"[scribble] insertTextPlaceholderWithSize"); [_textInputDelegate insertTextPlaceholderWithSize:size withClient:_textInputClient]; return [[FlutterTextPlaceholder alloc] init]; } - (void)removeTextPlaceholder:(UITextPlaceholder*)textPlaceholder { - NSLog(@"[scribble] removeTextPlaceholder"); [_textInputDelegate removeTextPlaceholder:_textInputClient]; } @@ -1548,7 +1554,6 @@ - (void)removeEnableFlutterTextInputViewAccessibilityTimer { - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { NSString* method = call.method; - NSLog(@"TextInputPlugin.handleMethodCall %@", method); id args = call.arguments; if ([method isEqualToString:@"TextInput.show"]) { [self showTextInput]; @@ -1869,7 +1874,6 @@ - (void)clearTextInputClient { - (BOOL)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction isElementFocused:(UIScribbleElementIdentifier)elementIdentifier { - NSLog(@"[scribble][delegate] isElementFocused:%@", elementIdentifier); return false; } @@ -1877,8 +1881,6 @@ - (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction focusElementIfNeeded:(UIScribbleElementIdentifier)elementIdentifier referencePoint:(CGPoint)focusReferencePoint completion:(void (^)(UIResponder* focusedInput))completion { - NSLog(@"[scribble][delegate] focusElementIfNeeded:%@ referencePoint:(%@, %@)", elementIdentifier, - @(focusReferencePoint.x), @(focusReferencePoint.y)); _reusableInputView.scribbleFocusing = true; [_textInputDelegate focusElement:elementIdentifier atPoint:focusReferencePoint @@ -1891,23 +1893,19 @@ - (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction - (BOOL)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction shouldDelayFocusForElement:(UIScribbleElementIdentifier)elementIdentifier { - NSLog(@"[scribble][delegate] shouldDelayFocusForElement:%@", elementIdentifier); return true; } - (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction willBeginWritingInElement:(UIScribbleElementIdentifier)elementIdentifier { - NSLog(@"[scribble][delegate] willBeginWritingInElement:%@", elementIdentifier); } - (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction didFinishWritingInElement:(UIScribbleElementIdentifier)elementIdentifier { - NSLog(@"[scribble][delegate] didFinishWritingInElement:%@", elementIdentifier); } - (CGRect)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction frameForElement:(UIScribbleElementIdentifier)elementIdentifier { - NSLog(@"[scribble][delegate] frameForElement:%@", elementIdentifier); NSValue* elementValue = [_scribbleElements objectForKey:elementIdentifier]; if (elementValue == nil) { return CGRectZero; @@ -1919,7 +1917,6 @@ - (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction requestElementsInRect:(CGRect)rect completion: (void (^)(NSArray* elements))completion { - NSLog(@"[scribble][delegate] requestElementsInRect:%@", @(rect)); [_textInputDelegate requestElementsInRect:rect result:^(id _Nullable result) { From d230e1bc877b8415bb714e7d01c4cb1b3ce3adf0 Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Thu, 8 Apr 2021 12:04:50 -0500 Subject: [PATCH 08/52] [scribble+master-updated] Clean up some misses during rebasing, remove FlutterTextInputViewAccessibilityHider from views, as it prevents direct scribble interactions --- .../framework/Source/FlutterTextInputPlugin.h | 1 + .../Source/FlutterTextInputPlugin.mm | 26 ++++++++++++++----- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h index 7c001001aaeeb..a401342515b18 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h @@ -81,6 +81,7 @@ /** A tokenizer used by `FlutterTextInputView` to customize string parsing. */ @interface FlutterTokenizer : UITextInputStringTokenizer +@end @interface FlutterTextSelectionRect : UITextSelectionRect diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 67b9e703aa641..ce28e9772e2d0 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -441,6 +441,10 @@ - (UITextRange*)lineEnclosingPosition:(UITextPosition*)position { offset:-offSetFromLineBreak]; return [_textInputView textRangeFromPosition:lineBreakBefore toPosition:lineBreakAfter]; +} + +@end + #pragma mark - FlutterTextSelectionRect @implementation FlutterTextSelectionRect @@ -759,11 +763,13 @@ - (void)setIsVisibleToAutofill:(BOOL)isVisibleToAutofill { #pragma mark UIScribbleInteractionDelegate - (void)scribbleInteractionWillBeginWriting:(UIScribbleInteraction*)interaction { + NSLog(@"[scribble] scribbleInteractionWillBeginWriting"); _scribbleInProgress = true; [_textInputDelegate scribbleInteractionBegan]; } - (void)scribbleInteractionDidFinishWriting:(UIScribbleInteraction*)interaction { + NSLog(@"[scribble] scribbleInteractionDidFinishWriting"); _scribbleInProgress = false; [_textInputDelegate scribbleInteractionFinished]; } @@ -1146,6 +1152,7 @@ - (void)setEditableTransform:(NSArray*)matrix { // physical keyboard. - (void)setSelectionRects:(NSArray*)rects { + NSLog(@"[scribble] setSelectionRects"); NSMutableArray* rectsAsRect = [[NSMutableArray alloc] initWithCapacity:[rects count]]; for (NSUInteger i = 0; i < [rects count]; i++) { NSArray* rect = rects[i]; @@ -1163,7 +1170,7 @@ - (CGRect)firstRectForRange:(UITextRange*)range { @"Expected a FlutterTextPosition for range.start (got %@).", [range.start class]); NSAssert([range.end isKindOfClass:[FlutterTextPosition class]], @"Expected a FlutterTextPosition for range.end (got %@).", [range.end class]); - + NSLog(@"[scribble] firstRectForRange"); NSUInteger start = ((FlutterTextPosition*)range.start).index; NSUInteger end = ((FlutterTextPosition*)range.end).index; if (_markedTextRange != nil) { @@ -1205,22 +1212,26 @@ - (CGRect)firstRectForRange:(UITextRange*)range { CGRect rect = CGRectMake([_selectionRects[i][0] floatValue], [_selectionRects[i][1] floatValue], [_selectionRects[i][2] floatValue], [_selectionRects[i][3] floatValue]); + NSLog(@"[scribble] firstRectForRange (%@ - %@) -> %@, %@, %@, %@", @(start), @(end), + @(rect.origin.x), @(rect.origin.y), @(rect.size.width), @(rect.size.height)); return rect; } } + NSLog(@"[scribble] firstRectForRange (%@ - %@) -> CGRectZero", @(start), @(end)); + // TODO(cbracken) Implement. return CGRectZero; } - (CGRect)caretRectForPosition:(UITextPosition*)position { // TODO(cbracken) Implement. - NSUInteger start = ((FlutterTextPosition*)position).index; return CGRectZero; } - (UITextPosition*)closestPositionToPoint:(CGPoint)point { // TODO(cbracken) Implement. + NSLog(@"[scribble] closestPositionToPoint"); if ([_selectionRects count] == 0) { NSUInteger currentIndex = ((FlutterTextPosition*)_selectedTextRange.start).index; return [FlutterTextPosition positionWithIndex:currentIndex]; @@ -1234,6 +1245,7 @@ - (UITextPosition*)closestPositionToPoint:(CGPoint)point { - (NSArray*)selectionRectsForRange:(UITextRange*)range { // TODO(cbracken) Implement. + NSLog(@"[scribble] selectionRectsForRange"); NSUInteger start = ((FlutterTextPosition*)range.start).index; NSUInteger end = ((FlutterTextPosition*)range.end).index; NSMutableArray* rects = [[NSMutableArray alloc] init]; @@ -1315,6 +1327,8 @@ - (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRang _closestPosition = end; } } + NSLog(@"[scribble] closestPositionToPoint (%@, %@) within range (%@ - %@) -> %@", @(point.x), + @(point.y), @(start), @(end), @(_closestPosition)); return [FlutterTextPosition positionWithIndex:_closestPosition]; } @@ -1848,17 +1862,15 @@ - (void)resetAllClientIds { } - (void)addToInputParentViewIfNeeded:(FlutterTextInputView*)inputView { - if (![inputView isDescendantOfView:_inputHider]) { - [_inputHider addSubview:inputView]; - } + // TODO: Find a different way to do what FlutterTextInputViewAccessibilityHider was doing UIView* parentView = self.keyWindow; - if (_inputHider.superview != parentView) { - [parentView addSubview:_inputHider]; + if (inputView.superview != parentView) { if (@available(iOS 14.0, *)) { UIScribbleInteraction* interaction = [[[UIScribbleInteraction alloc] initWithDelegate:inputView] autorelease]; [inputView addInteraction:interaction]; } + [parentView addSubview:inputView]; } } From 6035793e3428ff1e300b845395b8924aa0ac3b92 Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Thu, 8 Apr 2021 15:24:48 -0500 Subject: [PATCH 09/52] [scribble+master-updated] Fixup my tests --- .../Source/FlutterTextInputPlugin.mm | 21 ++++-- .../Source/FlutterTextInputPluginTest.m | 70 ++++++++++--------- 2 files changed, 52 insertions(+), 39 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index ce28e9772e2d0..e870094743b52 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -1205,10 +1205,16 @@ - (CGRect)firstRectForRange:(UITextRange*)range { if (end < start) { first = end; } + FlutterTextRange* textRange = [[FlutterTextRange + rangeWithNSRange:fml::RangeForCharactersInRange(self.text, NSMakeRange(0, self.text.length))] + copy]; + NSLog(@"[scribble] firstRectForRange (%@ - %@), textRange.range.length: %@", @(start), @(end), + @(textRange.range.length)); for (NSUInteger i = 0; i < [_selectionRects count]; i++) { if ([_selectionRects[i][4] unsignedIntegerValue] <= first && - (i + 1 == [_selectionRects count] || - [_selectionRects[i + 1][4] unsignedIntegerValue] > first)) { + ((i + 1 == [_selectionRects count] && textRange.range.length > first) || + (i + 1 < [_selectionRects count] && + [_selectionRects[i + 1][4] unsignedIntegerValue] > first))) { CGRect rect = CGRectMake([_selectionRects[i][0] floatValue], [_selectionRects[i][1] floatValue], [_selectionRects[i][2] floatValue], [_selectionRects[i][3] floatValue]); @@ -1283,7 +1289,7 @@ - (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRang NSUInteger _closestPosition = 0; float _closestY = 0; float _closestX = 0; - for (NSUInteger i = 0; i < [_selectionRects count] && i < end; i++) { + for (NSUInteger i = 0; i < [_selectionRects count]; i++) { NSUInteger position = [_selectionRects[i][4] unsignedIntegerValue]; if (position >= start && position <= end) { CGRect rect = @@ -1308,8 +1314,13 @@ - (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRang } } - if ([_selectionRects count] > 0 && self.text.length >= end) { + FlutterTextRange* textRange = [[FlutterTextRange + rangeWithNSRange:fml::RangeForCharactersInRange(self.text, NSMakeRange(0, self.text.length))] + copy]; + + if ([_selectionRects count] > 0 && textRange.range.length == end) { NSUInteger i = [_selectionRects count] - 1; + NSUInteger position = [_selectionRects[i][4] unsignedIntegerValue] + 1; CGRect rect = CGRectMake([_selectionRects[i][0] floatValue], [_selectionRects[i][1] floatValue], [_selectionRects[i][2] floatValue], [_selectionRects[i][3] floatValue]); @@ -1324,7 +1335,7 @@ - (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRang _closestY = yDist; _closestX = xDist; _closestIndex = [_selectionRects count]; - _closestPosition = end; + _closestPosition = position; } } NSLog(@"[scribble] closestPositionToPoint (%@, %@) within range (%@ - %@) -> %@", @(point.x), diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m index d859ab78b6d19..58063776436a6 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m @@ -506,15 +506,17 @@ - (void)testFirstRectForRangeReturnsCorrectSelectionRect { FlutterTextRange* range = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 1)]; CGRect testRect = CGRectMake(100, 100, 100, 100); [inputView setSelectionRects:@[ - @[ @0, @0, @100, @100 ], + @[ @0, @0, @100, @100, @0 ], @[ - @(testRect.origin.x), @(testRect.origin.y), @(testRect.size.width), @(testRect.size.height) + @(testRect.origin.x), @(testRect.origin.y), @(testRect.size.width), @(testRect.size.height), + @1 ], - @[ @200, @200, @100, @100 ] + @[ @200, @200, @100, @100, @2 ] ]]; NSLog(@"testRect: %@, firstRect: %@", @(testRect), @([inputView firstRectForRange:range])); XCTAssertTrue(CGRectEqualToRect(testRect, [inputView firstRectForRange:range])); + [inputView setTextInputState:@{@"text" : @"COM"}]; FlutterTextRange* rangeOutsideBounds = [FlutterTextRange rangeWithNSRange:NSMakeRange(3, 1)]; XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:rangeOutsideBounds])); } @@ -525,9 +527,9 @@ - (void)testClosestPositionToPoint { // Minimize the vertical distance from the center of the rects first [inputView setSelectionRects:@[ - @[ @0, @0, @100, @100 ], - @[ @0, @100, @100, @100 ], - @[ @0, @200, @100, @100 ], + @[ @0, @0, @100, @100, @0 ], + @[ @0, @100, @100, @100, @1 ], + @[ @0, @200, @100, @100, @2 ], ]]; CGPoint point = CGPointMake(150, 150); XCTAssertEqual(1, ((FlutterTextPosition*)[inputView closestPositionToPoint:point]).index); @@ -535,11 +537,11 @@ - (void)testClosestPositionToPoint { // Then, if the point is above the bottom of the closest rects vertically, get the closest x // origin [inputView setSelectionRects:@[ - @[ @0, @0, @100, @100 ], - @[ @0, @100, @100, @100 ], - @[ @100, @100, @100, @100 ], - @[ @200, @100, @100, @100 ], - @[ @0, @200, @100, @100 ], + @[ @0, @0, @100, @100, @0 ], + @[ @0, @100, @100, @100, @1 ], + @[ @100, @100, @100, @100, @2 ], + @[ @200, @100, @100, @100, @3 ], + @[ @0, @200, @100, @100, @4 ], ]]; point = CGPointMake(125, 150); XCTAssertEqual(2, ((FlutterTextPosition*)[inputView closestPositionToPoint:point]).index); @@ -547,21 +549,21 @@ - (void)testClosestPositionToPoint { // However, if the point is below the bottom of the closest rects vertically, get the position // farthest to the right [inputView setSelectionRects:@[ - @[ @0, @0, @100, @100 ], - @[ @0, @100, @100, @100 ], - @[ @100, @100, @100, @100 ], - @[ @200, @100, @100, @100 ], - @[ @0, @300, @100, @100 ], + @[ @0, @0, @100, @100, @0 ], + @[ @0, @100, @100, @100, @1 ], + @[ @100, @100, @100, @100, @2 ], + @[ @200, @100, @100, @100, @3 ], + @[ @0, @300, @100, @100, @4 ], ]]; point = CGPointMake(125, 201); XCTAssertEqual(3, ((FlutterTextPosition*)[inputView closestPositionToPoint:point]).index); // Also check a point at the right edge of the last selection rect [inputView setSelectionRects:@[ - @[ @0, @0, @100, @100 ], - @[ @0, @100, @100, @100 ], - @[ @100, @100, @100, @100 ], - @[ @200, @100, @100, @100 ], + @[ @0, @0, @100, @100, @0 ], + @[ @0, @100, @100, @100, @1 ], + @[ @100, @100, @100, @100, @2 ], + @[ @200, @100, @100, @100, @3 ], ]]; point = CGPointMake(125, 250); XCTAssertEqual(4, ((FlutterTextPosition*)[inputView closestPositionToPoint:point]).index); @@ -576,16 +578,16 @@ - (void)testSelectionRectsForRange { CGRect testRect0 = CGRectMake(100, 100, 100, 100); CGRect testRect1 = CGRectMake(200, 200, 100, 100); [inputView setSelectionRects:@[ - @[ @0, @0, @100, @100 ], + @[ @0, @0, @100, @100, @0 ], @[ @(testRect0.origin.x), @(testRect0.origin.y), @(testRect0.size.width), - @(testRect0.size.height) + @(testRect0.size.height), @1 ], @[ @(testRect1.origin.x), @(testRect1.origin.y), @(testRect1.size.width), - @(testRect1.size.height) + @(testRect1.size.height), @2 ], - @[ @300, @300, @100, @100 ] + @[ @300, @300, @100, @100, @3 ] ]]; XCTAssertTrue(CGRectEqualToRect(testRect0, [inputView selectionRectsForRange:range][0].rect)); XCTAssertTrue(CGRectEqualToRect(testRect1, [inputView selectionRectsForRange:range][1].rect)); @@ -598,11 +600,11 @@ - (void)testClosestPositionToPointWithinRange { // Do not return a position before the start of the range [inputView setSelectionRects:@[ - @[ @0, @0, @100, @100 ], - @[ @0, @100, @100, @100 ], - @[ @100, @100, @100, @100 ], - @[ @200, @100, @100, @100 ], - @[ @0, @200, @100, @100 ], + @[ @0, @0, @100, @100, @0 ], + @[ @0, @100, @100, @100, @1 ], + @[ @100, @100, @100, @100, @2 ], + @[ @200, @100, @100, @100, @3 ], + @[ @0, @200, @100, @100, @4 ], ]]; CGPoint point = CGPointMake(125, 150); FlutterTextRange* range = [[FlutterTextRange rangeWithNSRange:NSMakeRange(3, 2)] copy]; @@ -611,11 +613,11 @@ - (void)testClosestPositionToPointWithinRange { // Do not return a position after the end of the range [inputView setSelectionRects:@[ - @[ @0, @0, @100, @100 ], - @[ @0, @100, @100, @100 ], - @[ @100, @100, @100, @100 ], - @[ @200, @100, @100, @100 ], - @[ @0, @200, @100, @100 ], + @[ @0, @0, @100, @100, @0 ], + @[ @0, @100, @100, @100, @1 ], + @[ @100, @100, @100, @100, @2 ], + @[ @200, @100, @100, @100, @3 ], + @[ @0, @200, @100, @100, @4 ], ]]; point = CGPointMake(125, 150); range = [[FlutterTextRange rangeWithNSRange:NSMakeRange(0, 1)] copy]; From 9105c216d0b44aa542d2ffaead1db41f43c09db2 Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Wed, 21 Apr 2021 17:07:11 -0500 Subject: [PATCH 10/52] [scribble+master] Clear selection rects on insert text with placeholder so text doesn't flyaway to the end of the string for some reason --- .../Source/FlutterTextInputPlugin.mm | 40 ++++++++++++------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index e870094743b52..009add3b33517 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -559,6 +559,7 @@ @implementation FlutterTextInputView { CGRect _cachedFirstRect; NSArray* _selectionRects; BOOL _scribbleInProgress; + BOOL _hasPlaceholder; } @synthesize tokenizer = _tokenizer; @@ -663,6 +664,7 @@ - (void)dealloc { - (void)setTextInputClient:(int)client { _textInputClient = client; + _hasPlaceholder = NO; } - (void)setTextInputState:(NSDictionary*)state { @@ -1321,21 +1323,24 @@ - (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRang if ([_selectionRects count] > 0 && textRange.range.length == end) { NSUInteger i = [_selectionRects count] - 1; NSUInteger position = [_selectionRects[i][4] unsignedIntegerValue] + 1; - CGRect rect = - CGRectMake([_selectionRects[i][0] floatValue], [_selectionRects[i][1] floatValue], - [_selectionRects[i][2] floatValue], [_selectionRects[i][3] floatValue]); - CGPoint pointForComparison = - CGPointMake(rect.origin.x + rect.size.width, rect.origin.y + rect.size.height * 0.5); - float yDist = abs(pointForComparison.y - point.y); - float xDist = abs(pointForComparison.x - point.x); - if (yDist < _closestY || (yDist == _closestY && - ((point.y <= rect.origin.y + rect.size.height && xDist < _closestX) || - (point.y > rect.origin.y + rect.size.height && - rect.origin.x + rect.size.width > _closestRect.origin.x)))) { - _closestY = yDist; - _closestX = xDist; - _closestIndex = [_selectionRects count]; - _closestPosition = position; + if (position <= end) { + CGRect rect = + CGRectMake([_selectionRects[i][0] floatValue], [_selectionRects[i][1] floatValue], + [_selectionRects[i][2] floatValue], [_selectionRects[i][3] floatValue]); + CGPoint pointForComparison = + CGPointMake(rect.origin.x + rect.size.width, rect.origin.y + rect.size.height * 0.5); + float yDist = abs(pointForComparison.y - point.y); + float xDist = abs(pointForComparison.x - point.x); + if (yDist < _closestY || + (yDist == _closestY && + ((point.y <= rect.origin.y + rect.size.height && xDist < _closestX) || + (point.y > rect.origin.y + rect.size.height && + rect.origin.x + rect.size.width > _closestRect.origin.x)))) { + _closestY = yDist; + _closestX = xDist; + _closestIndex = [_selectionRects count]; + _closestPosition = position; + } } } NSLog(@"[scribble] closestPositionToPoint (%@, %@) within range (%@ - %@) -> %@", @(point.x), @@ -1404,16 +1409,21 @@ - (BOOL)hasText { } - (void)insertText:(NSString*)text { + if (_hasPlaceholder) { + _selectionRects = @[]; + } _selectionAffinity = _kTextAffinityDownstream; [self replaceRange:_selectedTextRange withText:text]; } - (UITextPlaceholder*)insertTextPlaceholderWithSize:(CGSize)size { [_textInputDelegate insertTextPlaceholderWithSize:size withClient:_textInputClient]; + _hasPlaceholder = YES; return [[FlutterTextPlaceholder alloc] init]; } - (void)removeTextPlaceholder:(UITextPlaceholder*)textPlaceholder { + _hasPlaceholder = NO; [_textInputDelegate removeTextPlaceholder:_textInputClient]; } From 487bab4716f5ebd51b7448211392dc19ecf55b77 Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Fri, 30 Apr 2021 12:58:26 -0500 Subject: [PATCH 11/52] [scribble+master] Add placeholder selection rects when inserting text; support focusing immediately --- .../Source/FlutterTextInputPlugin.mm | 58 ++++++++++++++----- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 009add3b33517..60cac3408e295 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -824,14 +824,10 @@ - (void)setSelectedTextRangeLocal:(UITextRange*)selectedTextRange { } - (void)setSelectedTextRange:(UITextRange*)selectedTextRange { - if (_scribbleFocused) { - _scribbleFocused = false; - self.selectedTextRange = _selectedTextRange; - return; - } [self setSelectedTextRangeLocal:selectedTextRange]; [self updateEditingState]; - if (_scribbleInProgress) { + if (_scribbleInProgress || _scribbleFocused) { + _scribbleFocused = false; FlutterTextRange* flutterTextRange = (FlutterTextRange*)selectedTextRange; if (flutterTextRange.range.length > 0) { [_textInputDelegate showToolbar:_textInputClient]; @@ -944,7 +940,7 @@ - (void)setMarkedText:(NSString*)markedText selectedRange:(NSRange)markedSelecte NSRange selectedRange = _selectedTextRange.range; NSRange markedTextRange = ((FlutterTextRange*)self.markedTextRange).range; - if (_scribbleInProgress) + if (_scribbleInProgress || _scribbleFocusing || _scribbleFocused) return; if (markedText == nil) @@ -1197,7 +1193,7 @@ - (CGRect)firstRectForRange:(UITextRange*)range { return _cachedFirstRect; } - if (!_scribbleInProgress) { + if (!_scribbleInProgress && !_scribbleFocusing && !_scribbleFocused) { [_textInputDelegate showAutocorrectionPromptRectForStart:start end:end withClient:_textInputClient]; @@ -1253,9 +1249,9 @@ - (UITextPosition*)closestPositionToPoint:(CGPoint)point { - (NSArray*)selectionRectsForRange:(UITextRange*)range { // TODO(cbracken) Implement. - NSLog(@"[scribble] selectionRectsForRange"); NSUInteger start = ((FlutterTextPosition*)range.start).index; NSUInteger end = ((FlutterTextPosition*)range.end).index; + NSLog(@"[scribble] selectionRectsForRange %@ - %@", @(start), @(end)); NSMutableArray* rects = [[NSMutableArray alloc] init]; for (NSUInteger i = 0; i < [_selectionRects count]; i++) { if ([_selectionRects[i][4] unsignedIntegerValue] >= start && @@ -1275,9 +1271,12 @@ - (NSArray*)selectionRectsForRange:(UITextRange*)range { NSMakeRange(0, self.text.length)) .length) isVertical:FALSE]; + // NSLog(@"[scribble] selectionRect(%@) %@, %@, %@, %@", @(_selectionRects[i][4]), + // @(_selectionRects[i][0]), @(_selectionRects[i][1]), width, @(_selectionRects[i][3])); [rects addObject:selectionRect]; } } + NSLog(@"[scribble] selectionRectsForRange %@ - %@ -> %@", @(start), @(end), @([rects count])); return rects; } @@ -1302,7 +1301,7 @@ - (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRang float yDist = abs(pointForComparison.y - point.y); float xDist = abs(pointForComparison.x - point.x); if (_closestIndex == 0 || - (yDist < _closestY || + (yDist < _closestY - 1 || (yDist == _closestY && ((point.y <= rect.origin.y + rect.size.height && xDist < _closestX) || (point.y > rect.origin.y + rect.size.height && @@ -1331,7 +1330,7 @@ - (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRang CGPointMake(rect.origin.x + rect.size.width, rect.origin.y + rect.size.height * 0.5); float yDist = abs(pointForComparison.y - point.y); float xDist = abs(pointForComparison.x - point.x); - if (yDist < _closestY || + if (yDist < _closestY - 1 || (yDist == _closestY && ((point.y <= rect.origin.y + rect.size.height && xDist < _closestX) || (point.y > rect.origin.y + rect.size.height && @@ -1409,9 +1408,37 @@ - (BOOL)hasText { } - (void)insertText:(NSString*)text { - if (_hasPlaceholder) { - _selectionRects = @[]; + NSMutableArray* copiedRects = [[NSMutableArray alloc] initWithCapacity:[_selectionRects count]]; + NSUInteger insertPosition = ((FlutterTextPosition*)_selectedTextRange.start).index - 1; + NSUInteger insertIndex = 0; + for (NSUInteger i = 0; i < [_selectionRects count]; i++) { + NSUInteger rectPosition = [_selectionRects[i][4] unsignedIntegerValue]; + if (rectPosition == insertPosition) { + insertIndex = i; + for (NSUInteger j = 0; j <= text.length; j++) { + [copiedRects addObject:@[ + _selectionRects[i][0], + _selectionRects[i][1], + _selectionRects[i][2], + _selectionRects[i][3], + [NSNumber numberWithInt:rectPosition + j], + ]]; + } + } else { + if (rectPosition > insertPosition) { + rectPosition = rectPosition + text.length; + } + [copiedRects addObject:@[ + _selectionRects[i][0], + _selectionRects[i][1], + _selectionRects[i][2], + _selectionRects[i][3], + [NSNumber numberWithInt:rectPosition], + ]]; + } } + + _selectionRects = copiedRects; _selectionAffinity = _kTextAffinityDownstream; [self replaceRange:_selectedTextRange withText:text]; } @@ -1901,13 +1928,14 @@ - (void)setTextInputEditingState:(NSDictionary*)state { - (void)clearTextInputClient { [_activeView setTextInputClient:0]; + _activeView.frame = CGRectZero; } #pragma mark UIIndirectScribbleInteractionDelegate - (BOOL)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction isElementFocused:(UIScribbleElementIdentifier)elementIdentifier { - return false; + return _reusableInputView.scribbleFocused; } - (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction @@ -1926,7 +1954,7 @@ - (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction - (BOOL)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction shouldDelayFocusForElement:(UIScribbleElementIdentifier)elementIdentifier { - return true; + return NO; } - (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction From 7c784c36c3d4cacb71d5ad6cd90d980ed4076fd6 Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Fri, 30 Apr 2021 15:03:52 -0500 Subject: [PATCH 12/52] [scribble+master] Add back autofill hider stuff, which seems to work now...not sure how that's the case, but OK; also fix some things so that the weird issue with zero width joiner characters seems to be fixed now as well --- .../Source/FlutterTextInputPlugin.mm | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 60cac3408e295..8d9693d092456 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -992,12 +992,11 @@ - (UITextRange*)textRangeFromPosition:(UITextPosition*)fromPosition } - (NSUInteger)decrementOffsetPosition:(NSUInteger)position { - return fml::RangeForCharacterAtIndex(self.text, MAX(0, position - 1)).location; + return MAX(0, position - 1); } - (NSUInteger)incrementOffsetPosition:(NSUInteger)position { - NSRange charRange = fml::RangeForCharacterAtIndex(self.text, position); - return MIN(position + charRange.length, self.text.length); + return MIN(position + 1, self.text.length); } - (UITextPosition*)positionFromPosition:(UITextPosition*)position offset:(NSInteger)offset { @@ -1155,9 +1154,10 @@ - (void)setSelectionRects:(NSArray*)rects { for (NSUInteger i = 0; i < [rects count]; i++) { NSArray* rect = rects[i]; [rectsAsRect addObject:@[ - [NSNumber numberWithInt:[rect[0] intValue]], [NSNumber numberWithInt:[rect[1] intValue]], - [NSNumber numberWithInt:[rect[2] intValue]], [NSNumber numberWithInt:[rect[3] intValue]], - [NSNumber numberWithInt:[rect[4] intValue]] + [NSNumber numberWithFloat:[rect[0] floatValue]], + [NSNumber numberWithFloat:[rect[1] floatValue]], + [NSNumber numberWithFloat:[rect[2] floatValue]], + [NSNumber numberWithFloat:[rect[3] floatValue]], [NSNumber numberWithInt:[rect[4] intValue]] ]]; } _selectionRects = rectsAsRect; @@ -1216,8 +1216,9 @@ - (CGRect)firstRectForRange:(UITextRange*)range { CGRect rect = CGRectMake([_selectionRects[i][0] floatValue], [_selectionRects[i][1] floatValue], [_selectionRects[i][2] floatValue], [_selectionRects[i][3] floatValue]); - NSLog(@"[scribble] firstRectForRange (%@ - %@) -> %@, %@, %@, %@", @(start), @(end), - @(rect.origin.x), @(rect.origin.y), @(rect.size.width), @(rect.size.height)); + NSLog(@"[scribble] firstRectForRange (%@ - %@) -> (%@) %@, %@, %@, %@", @(start), @(end), + @([_selectionRects[i][4] unsignedIntegerValue]), @(rect.origin.x), @(rect.origin.y), + @(rect.size.width), @(rect.size.height)); return rect; } } @@ -1302,7 +1303,7 @@ - (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRang float xDist = abs(pointForComparison.x - point.x); if (_closestIndex == 0 || (yDist < _closestY - 1 || - (yDist == _closestY && + (yDist >= _closestY - 1 && yDist <= _closestY + 1 && ((point.y <= rect.origin.y + rect.size.height && xDist < _closestX) || (point.y > rect.origin.y + rect.size.height && rect.origin.x > _closestRect.origin.x))))) { @@ -1331,7 +1332,7 @@ - (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRang float yDist = abs(pointForComparison.y - point.y); float xDist = abs(pointForComparison.x - point.x); if (yDist < _closestY - 1 || - (yDist == _closestY && + (yDist >= _closestY - 1 && yDist <= _closestY + 1 && ((point.y <= rect.origin.y + rect.size.height && xDist < _closestX) || (point.y > rect.origin.y + rect.size.height && rect.origin.x + rect.size.width > _closestRect.origin.x)))) { @@ -1910,15 +1911,18 @@ - (void)resetAllClientIds { } - (void)addToInputParentViewIfNeeded:(FlutterTextInputView*)inputView { - // TODO: Find a different way to do what FlutterTextInputViewAccessibilityHider was doing + if (![inputView isDescendantOfView:_inputHider]) { + [_inputHider addSubview:inputView]; + } UIView* parentView = self.keyWindow; - if (inputView.superview != parentView) { + if (_inputHider.superview != parentView) { + [parentView addSubview:_inputHider]; if (@available(iOS 14.0, *)) { + NSLog(@"[scribble] addToInputParentViewIfNeeded - setup UIScribbleInteraction"); UIScribbleInteraction* interaction = [[[UIScribbleInteraction alloc] initWithDelegate:inputView] autorelease]; [inputView addInteraction:interaction]; } - [parentView addSubview:inputView]; } } From b57c05ae1274c1140f7a244ca06e2b6dfbdd2463 Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Fri, 30 Apr 2021 15:16:46 -0500 Subject: [PATCH 13/52] [scribble+master] Add Twin Sun, LLC to AUTHORS --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index f9021efd7e70e..99ab2b33ef76f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -18,3 +18,4 @@ shoryukenn SOTEC GmbH & Co. KG Hidenori Matsubayashi Sarbagya Dhaubanjar +Twin Sun, LLC From ed6d050080b8524ef48299111b1abd0fa83c207b Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Wed, 5 May 2021 20:12:48 -0500 Subject: [PATCH 14/52] [scribble+master] Remove NSLogs and some TODOs --- .../Source/FlutterTextInputPlugin.mm | 27 ++----------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 8d9693d092456..762eb268fe318 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -765,13 +765,11 @@ - (void)setIsVisibleToAutofill:(BOOL)isVisibleToAutofill { #pragma mark UIScribbleInteractionDelegate - (void)scribbleInteractionWillBeginWriting:(UIScribbleInteraction*)interaction { - NSLog(@"[scribble] scribbleInteractionWillBeginWriting"); _scribbleInProgress = true; [_textInputDelegate scribbleInteractionBegan]; } - (void)scribbleInteractionDidFinishWriting:(UIScribbleInteraction*)interaction { - NSLog(@"[scribble] scribbleInteractionDidFinishWriting"); _scribbleInProgress = false; [_textInputDelegate scribbleInteractionFinished]; } @@ -1149,7 +1147,6 @@ - (void)setEditableTransform:(NSArray*)matrix { // physical keyboard. - (void)setSelectionRects:(NSArray*)rects { - NSLog(@"[scribble] setSelectionRects"); NSMutableArray* rectsAsRect = [[NSMutableArray alloc] initWithCapacity:[rects count]]; for (NSUInteger i = 0; i < [rects count]; i++) { NSArray* rect = rects[i]; @@ -1168,7 +1165,6 @@ - (CGRect)firstRectForRange:(UITextRange*)range { @"Expected a FlutterTextPosition for range.start (got %@).", [range.start class]); NSAssert([range.end isKindOfClass:[FlutterTextPosition class]], @"Expected a FlutterTextPosition for range.end (got %@).", [range.end class]); - NSLog(@"[scribble] firstRectForRange"); NSUInteger start = ((FlutterTextPosition*)range.start).index; NSUInteger end = ((FlutterTextPosition*)range.end).index; if (_markedTextRange != nil) { @@ -1206,8 +1202,6 @@ - (CGRect)firstRectForRange:(UITextRange*)range { FlutterTextRange* textRange = [[FlutterTextRange rangeWithNSRange:fml::RangeForCharactersInRange(self.text, NSMakeRange(0, self.text.length))] copy]; - NSLog(@"[scribble] firstRectForRange (%@ - %@), textRange.range.length: %@", @(start), @(end), - @(textRange.range.length)); for (NSUInteger i = 0; i < [_selectionRects count]; i++) { if ([_selectionRects[i][4] unsignedIntegerValue] <= first && ((i + 1 == [_selectionRects count] && textRange.range.length > first) || @@ -1216,16 +1210,10 @@ - (CGRect)firstRectForRange:(UITextRange*)range { CGRect rect = CGRectMake([_selectionRects[i][0] floatValue], [_selectionRects[i][1] floatValue], [_selectionRects[i][2] floatValue], [_selectionRects[i][3] floatValue]); - NSLog(@"[scribble] firstRectForRange (%@ - %@) -> (%@) %@, %@, %@, %@", @(start), @(end), - @([_selectionRects[i][4] unsignedIntegerValue]), @(rect.origin.x), @(rect.origin.y), - @(rect.size.width), @(rect.size.height)); return rect; } } - NSLog(@"[scribble] firstRectForRange (%@ - %@) -> CGRectZero", @(start), @(end)); - - // TODO(cbracken) Implement. return CGRectZero; } @@ -1235,8 +1223,6 @@ - (CGRect)caretRectForPosition:(UITextPosition*)position { } - (UITextPosition*)closestPositionToPoint:(CGPoint)point { - // TODO(cbracken) Implement. - NSLog(@"[scribble] closestPositionToPoint"); if ([_selectionRects count] == 0) { NSUInteger currentIndex = ((FlutterTextPosition*)_selectedTextRange.start).index; return [FlutterTextPosition positionWithIndex:currentIndex]; @@ -1249,10 +1235,8 @@ - (UITextPosition*)closestPositionToPoint:(CGPoint)point { } - (NSArray*)selectionRectsForRange:(UITextRange*)range { - // TODO(cbracken) Implement. NSUInteger start = ((FlutterTextPosition*)range.start).index; NSUInteger end = ((FlutterTextPosition*)range.end).index; - NSLog(@"[scribble] selectionRectsForRange %@ - %@", @(start), @(end)); NSMutableArray* rects = [[NSMutableArray alloc] init]; for (NSUInteger i = 0; i < [_selectionRects count]; i++) { if ([_selectionRects[i][4] unsignedIntegerValue] >= start && @@ -1272,17 +1256,13 @@ - (NSArray*)selectionRectsForRange:(UITextRange*)range { NSMakeRange(0, self.text.length)) .length) isVertical:FALSE]; - // NSLog(@"[scribble] selectionRect(%@) %@, %@, %@, %@", @(_selectionRects[i][4]), - // @(_selectionRects[i][0]), @(_selectionRects[i][1]), width, @(_selectionRects[i][3])); [rects addObject:selectionRect]; } } - NSLog(@"[scribble] selectionRectsForRange %@ - %@ -> %@", @(start), @(end), @([rects count])); return rects; } - (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRange*)range { - // TODO(cbracken) Implement. NSUInteger start = ((FlutterTextPosition*)range.start).index; NSUInteger end = ((FlutterTextPosition*)range.end).index; @@ -1343,8 +1323,6 @@ - (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRang } } } - NSLog(@"[scribble] closestPositionToPoint (%@, %@) within range (%@ - %@) -> %@", @(point.x), - @(point.y), @(start), @(end), @(_closestPosition)); return [FlutterTextPosition positionWithIndex:_closestPosition]; } @@ -1652,8 +1630,8 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { - (void)setEditableSizeAndTransform:(NSDictionary*)dictionary { [_activeView setEditableTransform:dictionary[@"transform"]]; - // TODO: only do this on iPadOS? - // This seems necessary to set up where the scribble interactable element will be + // TODO(fbcouch): only do this on iPadOS? + // This is necessary to set up where the scribble interactable element will be _activeView.frame = CGRectMake([dictionary[@"transform"][12] intValue], [dictionary[@"transform"][13] intValue], [dictionary[@"width"] intValue], [dictionary[@"height"] intValue]); @@ -1918,7 +1896,6 @@ - (void)addToInputParentViewIfNeeded:(FlutterTextInputView*)inputView { if (_inputHider.superview != parentView) { [parentView addSubview:_inputHider]; if (@available(iOS 14.0, *)) { - NSLog(@"[scribble] addToInputParentViewIfNeeded - setup UIScribbleInteraction"); UIScribbleInteraction* interaction = [[[UIScribbleInteraction alloc] initWithDelegate:inputView] autorelease]; [inputView addInteraction:interaction]; From 5746e60d186b4ad27f8eaba768e23971653fffa8 Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Thu, 20 May 2021 17:07:51 -0500 Subject: [PATCH 15/52] [scribble+master] Add 'resetViewController' method to FlutterTextInputPlugin to nil and set _hasScribbleInteraction to false when the view controller becomes nil --- shell/platform/darwin/ios/framework/Source/FlutterEngine.mm | 1 + .../darwin/ios/framework/Source/FlutterTextInputPlugin.h | 1 + .../darwin/ios/framework/Source/FlutterTextInputPlugin.mm | 5 +++++ 3 files changed, 7 insertions(+) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index 92f551dc738d3..e1b8b31fc410d 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -314,6 +314,7 @@ - (void)notifyViewControllerDeallocated { platform_view->SetOwnerViewController({}); } } + [_textInputPlugin.get() resetViewController]; _viewController.reset(); } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h index a401342515b18..35469770b4d29 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h @@ -57,6 +57,7 @@ * correct element */ - (void)setupIndirectScribbleInteraction; +- (void)resetViewController; @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 762eb268fe318..75074e1c4ae23 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -1997,4 +1997,9 @@ - (void)setupIndirectScribbleInteraction { } } +- (void)resetViewController { + _viewController = nil; + _hasScribbleInteraction = false; +} + @end From 97c67b477710af12a2d91fe7efd63f43a62f702d Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Fri, 21 May 2021 09:44:04 -0500 Subject: [PATCH 16/52] [scribble+master] Pass FlutterViewController into setupIndirectScribbleInteraction method and set up the interaction every time the viewController changes --- .../ios/framework/Source/FlutterEngine.mm | 6 ++--- .../framework/Source/FlutterTextInputPlugin.h | 2 +- .../Source/FlutterTextInputPlugin.mm | 24 +++++++++---------- 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index e1b8b31fc410d..c16d6686532ec 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -289,8 +289,7 @@ - (void)setViewController:(FlutterViewController*)viewController { - (void)attachView { self.iosPlatformView->attachView(); - _textInputPlugin.get().viewController = [self viewController]; - [_textInputPlugin.get() setupIndirectScribbleInteraction]; + [_textInputPlugin.get() setupIndirectScribbleInteraction:[self viewController]]; } - (void)setFlutterViewControllerWillDeallocObserver:(id)observer { @@ -474,8 +473,7 @@ - (void)setupChannels { _textInputPlugin.reset([[FlutterTextInputPlugin alloc] init]); _textInputPlugin.get().textInputDelegate = self; - _textInputPlugin.get().viewController = [self viewController]; - [_textInputPlugin.get() setupIndirectScribbleInteraction]; + [_textInputPlugin.get() setupIndirectScribbleInteraction:[self viewController]]; _platformPlugin.reset([[FlutterPlatformPlugin alloc] initWithEngine:[self getWeakPtr]]); diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h index 35469770b4d29..aee81a45da16b 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h @@ -56,7 +56,7 @@ * These are used by the UIIndirectScribbleInteractionDelegate methods to handle focusing on the * correct element */ -- (void)setupIndirectScribbleInteraction; +- (void)setupIndirectScribbleInteraction:(FlutterViewController*)viewController; - (void)resetViewController; @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 75074e1c4ae23..fb5272ce4683e 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -1549,7 +1549,6 @@ @interface FlutterTextInputPlugin () @implementation FlutterTextInputPlugin { NSTimer* _enableFlutterTextInputViewAccessibilityTimer; - BOOL _hasScribbleInteraction; NSMutableDictionary* _scribbleElements; } @@ -1982,24 +1981,23 @@ - (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction #pragma mark - Methods related to Scribble support -- (void)setupIndirectScribbleInteraction { - if (_hasScribbleInteraction) { - return; - } - if (@available(iOS 14.0, *)) { - UIView* parentView = _viewController.view; - if (parentView != nil) { - _hasScribbleInteraction = true; - UIIndirectScribbleInteraction* _scribbleInteraction = [[[UIIndirectScribbleInteraction alloc] - initWithDelegate:(id)self] autorelease]; - [parentView addInteraction:_scribbleInteraction]; +- (void)setupIndirectScribbleInteraction:(FlutterViewController*)viewController { + if (_viewController != viewController) { + if (@available(iOS 14.0, *)) { + UIView* parentView = viewController.view; + if (parentView != nil) { + UIIndirectScribbleInteraction* _scribbleInteraction = + [[[UIIndirectScribbleInteraction alloc] + initWithDelegate:(id)self] autorelease]; + [parentView addInteraction:_scribbleInteraction]; + } } } + _viewController = viewController; } - (void)resetViewController { _viewController = nil; - _hasScribbleInteraction = false; } @end From deb3ae90185a9b455246761a9385bec1fd2adbd8 Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Fri, 4 Jun 2021 13:43:57 -0500 Subject: [PATCH 17/52] =?UTF-8?q?[scribble+master]=20Address=20PR=20feedba?= =?UTF-8?q?ck=20=E2=80=93=20memory=20management,=20extract=20common=20logi?= =?UTF-8?q?c=20for=20closestPositionToPoint=20into=20a=20static=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ios/framework/Source/FlutterEngine.mm | 6 +- .../Source/FlutterTextInputDelegate.h | 2 +- .../framework/Source/FlutterTextInputPlugin.h | 4 +- .../Source/FlutterTextInputPlugin.mm | 98 +++++++++++-------- 4 files changed, 63 insertions(+), 47 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index c16d6686532ec..0586e7be8cdd2 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -289,7 +289,7 @@ - (void)setViewController:(FlutterViewController*)viewController { - (void)attachView { self.iosPlatformView->attachView(); - [_textInputPlugin.get() setupIndirectScribbleInteraction:[self viewController]]; + [_textInputPlugin.get() setupIndirectScribbleInteraction:self.viewController]; } - (void)setFlutterViewControllerWillDeallocObserver:(id)observer { @@ -473,7 +473,7 @@ - (void)setupChannels { _textInputPlugin.reset([[FlutterTextInputPlugin alloc] init]); _textInputPlugin.get().textInputDelegate = self; - [_textInputPlugin.get() setupIndirectScribbleInteraction:[self viewController]]; + [_textInputPlugin.get() setupIndirectScribbleInteraction:self.viewController]; _platformPlugin.reset([[FlutterPlatformPlugin alloc] initWithEngine:[self getWeakPtr]]); @@ -776,7 +776,7 @@ - (void)focusElement:(UIScribbleElementIdentifier)elementIdentifier result:callback]; } -- (void)requestElementsInRect:(CGRect)rect result:(id)callback { +- (void)requestElementsInRect:(CGRect)rect result:(FlutterResult)callback { [_textInputChannel.get() invokeMethod:@"TextInputClient.requestElementsInRect" arguments:@[ @(rect.origin.x), @(rect.origin.y), @(rect.size.width), @(rect.size.height) ] diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h index fd73059999fb4..4a3842364aebb 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h @@ -42,7 +42,7 @@ typedef NS_ENUM(NSInteger, FlutterFloatingCursorDragState) { - (void)focusElement:(UIScribbleElementIdentifier)elementIdentifier atPoint:(CGPoint)referencePoint result:(id)callback; -- (void)requestElementsInRect:(CGRect)rect result:(id)callback; +- (void)requestElementsInRect:(CGRect)rect result:(FlutterResult)callback; - (void)scribbleInteractionBegan; - (void)scribbleInteractionFinished; - (void)insertTextPlaceholderWithSize:(CGSize)size withClient:(int)client; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h index aee81a45da16b..719dff49b8e85 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h @@ -14,7 +14,8 @@ @interface FlutterTextInputPlugin : NSObject @property(nonatomic, assign) id textInputDelegate; -@property(nonatomic, assign) FlutterViewController* viewController; +@property(nonatomic, readonly) FlutterViewController* viewController; +@property(nonatomic, assign) NSMutableDictionary* scribbleElements; - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result; /** @@ -153,6 +154,7 @@ FLUTTER_DARWIN_EXPORT @property(nonatomic, assign) FlutterViewController* viewController; @property(nonatomic) BOOL scribbleFocusing; @property(nonatomic) BOOL scribbleFocused; +@property(nonatomic, assign) NSArray* selectionRects; @end #endif // SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERTEXTINPUTPLUGIN_H_ diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index fb5272ce4683e..cd002d4a1cd24 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -323,6 +323,34 @@ static FlutterAutofillType autofillTypeOf(NSDictionary* configuration) { return FlutterAutofillTypeNone; } +static BOOL isPositionCloserToPoint(CGPoint point, + CGRect position, + CGRect otherPosition, + BOOL checkRightBoundary) { + CGPoint pointForPosition = + CGPointMake(position.origin.x + (checkRightBoundary ? position.size.width : 0), + position.origin.y + position.size.height * 0.5); + float yDist = fabs(pointForPosition.y - point.y); + float xDist = fabs(pointForPosition.x - point.x); + + CGPoint pointForOtherPosition = + CGPointMake(otherPosition.origin.x + (checkRightBoundary ? position.size.width : 0), + otherPosition.origin.y + otherPosition.size.height * 0.5); + float yDistOther = fabs(pointForOtherPosition.y - point.y); + float xDistOther = fabs(pointForOtherPosition.x - point.x); + + BOOL isCloserVertically = yDist < yDistOther - 1; + BOOL isEqualVertically = yDist >= yDistOther - 1 && yDist <= yDistOther + 1; + BOOL isAboveBottomOfLine = point.y <= position.origin.y + position.size.height; + BOOL isCloserHorizontally = xDist < xDistOther; + BOOL isBelowBottomOfLine = point.y > position.origin.y + position.size.height; + BOOL isFartherToRight = + position.origin.x + (checkRightBoundary ? position.size.width : 0) > otherPosition.origin.x; + return (isCloserVertically || + (isEqualVertically && ((isAboveBottomOfLine && isCloserHorizontally) || + (isBelowBottomOfLine && isFartherToRight)))); +} + #pragma mark - FlutterTextPosition @implementation FlutterTextPosition @@ -490,8 +518,8 @@ - (instancetype)initWithRectAndInfo:(CGRect)rect @implementation FlutterTextPlaceholder - (NSArray*)rects { - // Returning anything other than an empty array here seems to cause PencilKit to enter an infinite - // loop of allocating placeholders until the app crashes + // Returning anything other than an empty array here seems to cause PencilKit to enter an + // infinite loop of allocating placeholders until the app crashes return @[]; } @@ -557,12 +585,12 @@ @implementation FlutterTextInputView { const char* _selectionAffinity; FlutterTextRange* _selectedTextRange; CGRect _cachedFirstRect; - NSArray* _selectionRects; BOOL _scribbleInProgress; BOOL _hasPlaceholder; } @synthesize tokenizer = _tokenizer; +@synthesize selectionRects = _selectionRects; - (instancetype)init { self = [super init]; @@ -1150,12 +1178,7 @@ - (void)setSelectionRects:(NSArray*)rects { NSMutableArray* rectsAsRect = [[NSMutableArray alloc] initWithCapacity:[rects count]]; for (NSUInteger i = 0; i < [rects count]; i++) { NSArray* rect = rects[i]; - [rectsAsRect addObject:@[ - [NSNumber numberWithFloat:[rect[0] floatValue]], - [NSNumber numberWithFloat:[rect[1] floatValue]], - [NSNumber numberWithFloat:[rect[2] floatValue]], - [NSNumber numberWithFloat:[rect[3] floatValue]], [NSNumber numberWithInt:[rect[4] intValue]] - ]]; + [rectsAsRect addObject:rect]; } _selectionRects = rectsAsRect; } @@ -1224,6 +1247,9 @@ - (CGRect)caretRectForPosition:(UITextPosition*)position { - (UITextPosition*)closestPositionToPoint:(CGPoint)point { if ([_selectionRects count] == 0) { + NSAssert([_selectedTextRange.start isKindOfClass:[FlutterTextPosition class]], + @"Expected a FlutterTextPosition for position (got %@).", + [_selectedTextRange.start class]); NSUInteger currentIndex = ((FlutterTextPosition*)_selectedTextRange.start).index; return [FlutterTextPosition positionWithIndex:currentIndex]; } @@ -1235,6 +1261,10 @@ - (UITextPosition*)closestPositionToPoint:(CGPoint)point { } - (NSArray*)selectionRectsForRange:(UITextRange*)range { + NSAssert([range.start isKindOfClass:[FlutterTextPosition class]], + @"Expected a FlutterTextPosition for range.start (got %@).", [range.start class]); + NSAssert([range.end isKindOfClass:[FlutterTextPosition class]], + @"Expected a FlutterTextPosition for range.end (got %@).", [range.end class]); NSUInteger start = ((FlutterTextPosition*)range.start).index; NSUInteger end = ((FlutterTextPosition*)range.end).index; NSMutableArray* rects = [[NSMutableArray alloc] init]; @@ -1263,32 +1293,24 @@ - (NSArray*)selectionRectsForRange:(UITextRange*)range { } - (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRange*)range { + NSAssert([range.start isKindOfClass:[FlutterTextPosition class]], + @"Expected a FlutterTextPosition for range.start (got %@).", [range.start class]); + NSAssert([range.end isKindOfClass:[FlutterTextPosition class]], + @"Expected a FlutterTextPosition for range.end (got %@).", [range.end class]); NSUInteger start = ((FlutterTextPosition*)range.start).index; NSUInteger end = ((FlutterTextPosition*)range.end).index; NSUInteger _closestIndex = 0; CGRect _closestRect = CGRectZero; NSUInteger _closestPosition = 0; - float _closestY = 0; - float _closestX = 0; for (NSUInteger i = 0; i < [_selectionRects count]; i++) { NSUInteger position = [_selectionRects[i][4] unsignedIntegerValue]; if (position >= start && position <= end) { CGRect rect = CGRectMake([_selectionRects[i][0] floatValue], [_selectionRects[i][1] floatValue], [_selectionRects[i][2] floatValue], [_selectionRects[i][3] floatValue]); - CGPoint pointForComparison = - CGPointMake(rect.origin.x, rect.origin.y + rect.size.height * 0.5); - float yDist = abs(pointForComparison.y - point.y); - float xDist = abs(pointForComparison.x - point.x); - if (_closestIndex == 0 || - (yDist < _closestY - 1 || - (yDist >= _closestY - 1 && yDist <= _closestY + 1 && - ((point.y <= rect.origin.y + rect.size.height && xDist < _closestX) || - (point.y > rect.origin.y + rect.size.height && - rect.origin.x > _closestRect.origin.x))))) { - _closestY = yDist; - _closestX = xDist; + BOOL isFirst = _closestIndex == 0; + if (isFirst || isPositionCloserToPoint(point, rect, _closestRect, NO)) { _closestIndex = i; _closestRect = rect; _closestPosition = position; @@ -1296,9 +1318,8 @@ - (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRang } } - FlutterTextRange* textRange = [[FlutterTextRange - rangeWithNSRange:fml::RangeForCharactersInRange(self.text, NSMakeRange(0, self.text.length))] - copy]; + FlutterTextRange* textRange = [FlutterTextRange + rangeWithNSRange:fml::RangeForCharactersInRange(self.text, NSMakeRange(0, self.text.length))]; if ([_selectionRects count] > 0 && textRange.range.length == end) { NSUInteger i = [_selectionRects count] - 1; @@ -1307,17 +1328,7 @@ - (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRang CGRect rect = CGRectMake([_selectionRects[i][0] floatValue], [_selectionRects[i][1] floatValue], [_selectionRects[i][2] floatValue], [_selectionRects[i][3] floatValue]); - CGPoint pointForComparison = - CGPointMake(rect.origin.x + rect.size.width, rect.origin.y + rect.size.height * 0.5); - float yDist = abs(pointForComparison.y - point.y); - float xDist = abs(pointForComparison.x - point.x); - if (yDist < _closestY - 1 || - (yDist >= _closestY - 1 && yDist <= _closestY + 1 && - ((point.y <= rect.origin.y + rect.size.height && xDist < _closestX) || - (point.y > rect.origin.y + rect.size.height && - rect.origin.x + rect.size.width > _closestRect.origin.x)))) { - _closestY = yDist; - _closestX = xDist; + if (isPositionCloserToPoint(point, rect, _closestRect, YES)) { _closestIndex = [_selectionRects count]; _closestPosition = position; } @@ -1425,7 +1436,7 @@ - (void)insertText:(NSString*)text { - (UITextPlaceholder*)insertTextPlaceholderWithSize:(CGSize)size { [_textInputDelegate insertTextPlaceholderWithSize:size withClient:_textInputClient]; _hasPlaceholder = YES; - return [[FlutterTextPlaceholder alloc] init]; + return [[[FlutterTextPlaceholder alloc] init] autorelease]; } - (void)removeTextPlaceholder:(UITextPlaceholder*)textPlaceholder { @@ -1549,11 +1560,11 @@ @interface FlutterTextInputPlugin () @implementation FlutterTextInputPlugin { NSTimer* _enableFlutterTextInputViewAccessibilityTimer; - NSMutableDictionary* _scribbleElements; } @synthesize textInputDelegate = _textInputDelegate; @synthesize viewController = _viewController; +@synthesize scribbleElements = _scribbleElements; - (instancetype)init { self = [super init]; @@ -1577,6 +1588,7 @@ - (void)dealloc { [_activeView release]; [_inputHider release]; [_autofillContext release]; + [_scribbleElements release]; [super dealloc]; } @@ -1631,9 +1643,11 @@ - (void)setEditableSizeAndTransform:(NSDictionary*)dictionary { [_activeView setEditableTransform:dictionary[@"transform"]]; // TODO(fbcouch): only do this on iPadOS? // This is necessary to set up where the scribble interactable element will be - _activeView.frame = - CGRectMake([dictionary[@"transform"][12] intValue], [dictionary[@"transform"][13] intValue], - [dictionary[@"width"] intValue], [dictionary[@"height"] intValue]); + int leftIndex = 12; + int topIndex = 13; + _activeView.frame = CGRectMake([dictionary[@"transform"][leftIndex] intValue], + [dictionary[@"transform"][topIndex] intValue], + [dictionary[@"width"] intValue], [dictionary[@"height"] intValue]); } - (void)updateMarkedRect:(NSDictionary*)dictionary { From 9d2e5016662dafd4bc891675b2cddd4c4be6c0aa Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Wed, 30 Jun 2021 21:40:13 -0500 Subject: [PATCH 18/52] [scribble+master] Remove FlutterViewController import, remove unnecessary protocol method declarations in header file, move API_AVAILABLE macro declarations into the implementations --- .../framework/Source/FlutterTextInputPlugin.h | 50 ++----------------- .../Source/FlutterTextInputPlugin.mm | 38 ++++++++------ 2 files changed, 29 insertions(+), 59 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h index 719dff49b8e85..c654eaef9416b 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h @@ -8,13 +8,12 @@ #import #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" -#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h" -@interface FlutterTextInputPlugin : NSObject +@interface FlutterTextInputPlugin : NSObject @property(nonatomic, assign) id textInputDelegate; -@property(nonatomic, readonly) FlutterViewController* viewController; +@property(nonatomic, readonly) UIViewController* viewController; @property(nonatomic, assign) NSMutableDictionary* scribbleElements; - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result; @@ -26,38 +25,11 @@ */ - (UIView*)textInputView; -// UIIndirectScribbleInteractionDelegate -- (BOOL)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction - isElementFocused:(UIScribbleElementIdentifier)elementIdentifier - API_AVAILABLE(ios(14.0)); -- (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction - focusElementIfNeeded:(UIScribbleElementIdentifier)elementIdentifier - referencePoint:(CGPoint)focusReferencePoint - completion:(void (^)(UIResponder* focusedInput))completion - API_AVAILABLE(ios(14.0)); -- (BOOL)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction - shouldDelayFocusForElement:(UIScribbleElementIdentifier)elementIdentifier - API_AVAILABLE(ios(14.0)); -- (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction - willBeginWritingInElement:(UIScribbleElementIdentifier)elementIdentifier - API_AVAILABLE(ios(14.0)); -- (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction - didFinishWritingInElement:(UIScribbleElementIdentifier)elementIdentifier - API_AVAILABLE(ios(14.0)); -- (CGRect)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction - frameForElement:(UIScribbleElementIdentifier)elementIdentifier - API_AVAILABLE(ios(14.0)); -- (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction - requestElementsInRect:(CGRect)rect - completion: - (void (^)(NSArray* elements))completion - API_AVAILABLE(ios(14.0)); - /** * These are used by the UIIndirectScribbleInteractionDelegate methods to handle focusing on the * correct element */ -- (void)setupIndirectScribbleInteraction:(FlutterViewController*)viewController; +- (void)setupIndirectScribbleInteraction:(UIViewController*)viewController; - (void)resetViewController; @end @@ -136,22 +108,10 @@ FLUTTER_DARWIN_EXPORT @property(nonatomic) UITextSmartDashesType smartDashesType API_AVAILABLE(ios(11.0)); @property(nonatomic, copy) UITextContentType textContentType API_AVAILABLE(ios(10.0)); -- (UITextPlaceholder*)insertTextPlaceholderWithSize:(CGSize)size API_AVAILABLE(ios(13.0)); -- (void)removeTextPlaceholder:(UITextPlaceholder*)textPlaceholder API_AVAILABLE(ios(13.0)); - -// UIScribbleInteractionDelegate -- (void)scribbleInteractionWillBeginWriting:(UIScribbleInteraction*)interaction - API_AVAILABLE(ios(14.0)); -- (void)scribbleInteractionDidFinishWriting:(UIScribbleInteraction*)interaction - API_AVAILABLE(ios(14.0)); -- (BOOL)scribbleInteraction:(UIScribbleInteraction*)interaction - shouldBeginAtLocation:(CGPoint)location API_AVAILABLE(ios(14.0)); -- (BOOL)scribbleInteractionShouldDelayFocus:(UIScribbleInteraction*)interaction - API_AVAILABLE(ios(14.0)); - +// Scribble Support @property(nonatomic, assign) id textInputDelegate; @property(nonatomic, assign) UIAccessibilityElement* backingTextInputAccessibilityObject; -@property(nonatomic, assign) FlutterViewController* viewController; +@property(nonatomic, assign) UIViewController* viewController; @property(nonatomic) BOOL scribbleFocusing; @property(nonatomic) BOOL scribbleFocused; @property(nonatomic, assign) NSArray* selectionRects; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index cd002d4a1cd24..0e117169369e5 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -792,22 +792,25 @@ - (void)setIsVisibleToAutofill:(BOOL)isVisibleToAutofill { #pragma mark UIScribbleInteractionDelegate -- (void)scribbleInteractionWillBeginWriting:(UIScribbleInteraction*)interaction { +- (void)scribbleInteractionWillBeginWriting:(UIScribbleInteraction*)interaction + API_AVAILABLE(ios(14.0)) { _scribbleInProgress = true; [_textInputDelegate scribbleInteractionBegan]; } -- (void)scribbleInteractionDidFinishWriting:(UIScribbleInteraction*)interaction { +- (void)scribbleInteractionDidFinishWriting:(UIScribbleInteraction*)interaction + API_AVAILABLE(ios(14.0)) { _scribbleInProgress = false; [_textInputDelegate scribbleInteractionFinished]; } - (BOOL)scribbleInteraction:(UIScribbleInteraction*)interaction - shouldBeginAtLocation:(CGPoint)location { + shouldBeginAtLocation:(CGPoint)location API_AVAILABLE(ios(14.0)) { return true; } -- (BOOL)scribbleInteractionShouldDelayFocus:(UIScribbleInteraction*)interaction { +- (BOOL)scribbleInteractionShouldDelayFocus:(UIScribbleInteraction*)interaction + API_AVAILABLE(ios(14.0)) { return false; } @@ -1433,13 +1436,13 @@ - (void)insertText:(NSString*)text { [self replaceRange:_selectedTextRange withText:text]; } -- (UITextPlaceholder*)insertTextPlaceholderWithSize:(CGSize)size { +- (UITextPlaceholder*)insertTextPlaceholderWithSize:(CGSize)size API_AVAILABLE(ios(13.0)) { [_textInputDelegate insertTextPlaceholderWithSize:size withClient:_textInputClient]; _hasPlaceholder = YES; return [[[FlutterTextPlaceholder alloc] init] autorelease]; } -- (void)removeTextPlaceholder:(UITextPlaceholder*)textPlaceholder { +- (void)removeTextPlaceholder:(UITextPlaceholder*)textPlaceholder API_AVAILABLE(ios(13.0)) { _hasPlaceholder = NO; [_textInputDelegate removeTextPlaceholder:_textInputClient]; } @@ -1928,14 +1931,16 @@ - (void)clearTextInputClient { #pragma mark UIIndirectScribbleInteractionDelegate - (BOOL)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction - isElementFocused:(UIScribbleElementIdentifier)elementIdentifier { + isElementFocused:(UIScribbleElementIdentifier)elementIdentifier + API_AVAILABLE(ios(14.0)) { return _reusableInputView.scribbleFocused; } - (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction focusElementIfNeeded:(UIScribbleElementIdentifier)elementIdentifier referencePoint:(CGPoint)focusReferencePoint - completion:(void (^)(UIResponder* focusedInput))completion { + completion:(void (^)(UIResponder* focusedInput))completion + API_AVAILABLE(ios(14.0)) { _reusableInputView.scribbleFocusing = true; [_textInputDelegate focusElement:elementIdentifier atPoint:focusReferencePoint @@ -1947,20 +1952,24 @@ - (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction } - (BOOL)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction - shouldDelayFocusForElement:(UIScribbleElementIdentifier)elementIdentifier { + shouldDelayFocusForElement:(UIScribbleElementIdentifier)elementIdentifier + API_AVAILABLE(ios(14.0)) { return NO; } - (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction - willBeginWritingInElement:(UIScribbleElementIdentifier)elementIdentifier { + willBeginWritingInElement:(UIScribbleElementIdentifier)elementIdentifier + API_AVAILABLE(ios(14.0)) { } - (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction - didFinishWritingInElement:(UIScribbleElementIdentifier)elementIdentifier { + didFinishWritingInElement:(UIScribbleElementIdentifier)elementIdentifier + API_AVAILABLE(ios(14.0)) { } - (CGRect)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction - frameForElement:(UIScribbleElementIdentifier)elementIdentifier { + frameForElement:(UIScribbleElementIdentifier)elementIdentifier + API_AVAILABLE(ios(14.0)) { NSValue* elementValue = [_scribbleElements objectForKey:elementIdentifier]; if (elementValue == nil) { return CGRectZero; @@ -1971,7 +1980,8 @@ - (CGRect)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interactio - (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction requestElementsInRect:(CGRect)rect completion: - (void (^)(NSArray* elements))completion { + (void (^)(NSArray* elements))completion + API_AVAILABLE(ios(14.0)) { [_textInputDelegate requestElementsInRect:rect result:^(id _Nullable result) { @@ -1995,7 +2005,7 @@ - (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction #pragma mark - Methods related to Scribble support -- (void)setupIndirectScribbleInteraction:(FlutterViewController*)viewController { +- (void)setupIndirectScribbleInteraction:(UIViewController*)viewController { if (_viewController != viewController) { if (@available(iOS 14.0, *)) { UIView* parentView = viewController.view; From 67d471a180a0dd43921c70b7c35e022ac7379b38 Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Wed, 30 Jun 2021 21:59:30 -0500 Subject: [PATCH 19/52] [scribble+master] Use dynamic instead of synthesize for inherited properties --- .../framework/Source/FlutterTextInputPlugin.h | 6 +++--- .../Source/FlutterTextInputPlugin.mm | 20 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h index c654eaef9416b..ad8472490003f 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h @@ -61,9 +61,9 @@ @property(nonatomic, assign) CGRect rect; @property(nonatomic, assign) NSWritingDirection writingDirection; -@property(nonatomic, assign) BOOL containsStart; -@property(nonatomic, assign) BOOL containsEnd; -@property(nonatomic, assign) BOOL isVertical; +@property(nonatomic) BOOL containsStart; +@property(nonatomic) BOOL containsEnd; +@property(nonatomic) BOOL isVertical; + (instancetype)selectionRectWithRectAndInfo:(CGRect)rect writingDirection:(NSWritingDirection)writingDirection diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 0e117169369e5..666954b45dae9 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -477,11 +477,11 @@ - (UITextRange*)lineEnclosingPosition:(UITextPosition*)position { @implementation FlutterTextSelectionRect -@synthesize rect = _rect; -@synthesize writingDirection = _writingDirection; -@synthesize containsStart = _containsStart; -@synthesize containsEnd = _containsEnd; -@synthesize isVertical = _isVertical; +@dynamic rect; +@dynamic writingDirection; +@dynamic containsStart; +@dynamic containsEnd; +@dynamic isVertical; + (instancetype)selectionRectWithRectAndInfo:(CGRect)rect writingDirection:(NSWritingDirection)writingDirection @@ -502,11 +502,11 @@ - (instancetype)initWithRectAndInfo:(CGRect)rect isVertical:(BOOL)isVertical { self = [super init]; if (self) { - _rect = rect; - _writingDirection = writingDirection; - _containsStart = containsStart; - _containsEnd = containsEnd; - _isVertical = isVertical; + self.rect = rect; + self.writingDirection = writingDirection; + self.containsStart = containsStart; + self.containsEnd = containsEnd; + self.isVertical = isVertical; } return self; } From d10c767fb8a9e03f6c87008b81c8954a077f8b5f Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Thu, 1 Jul 2021 09:46:57 -0500 Subject: [PATCH 20/52] [scribble+master] Remove some useless copy calls, extract logic of firstRectForRange test into named bools --- .../framework/Source/FlutterTextInputPlugin.h | 2 +- .../Source/FlutterTextInputPlugin.mm | 33 ++++++++++--------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h index ad8472490003f..c97fb16b62601 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h @@ -114,7 +114,7 @@ FLUTTER_DARWIN_EXPORT @property(nonatomic, assign) UIViewController* viewController; @property(nonatomic) BOOL scribbleFocusing; @property(nonatomic) BOOL scribbleFocused; -@property(nonatomic, assign) NSArray* selectionRects; +@property(nonatomic, strong) NSArray* selectionRects; @end #endif // SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERTEXTINPUTPLUGIN_H_ diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 666954b45dae9..d6ad59a61282c 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -488,11 +488,11 @@ + (instancetype)selectionRectWithRectAndInfo:(CGRect)rect containsStart:(BOOL)containsStart containsEnd:(BOOL)containsEnd isVertical:(BOOL)isVertical { - return [[FlutterTextSelectionRect alloc] initWithRectAndInfo:rect - writingDirection:writingDirection - containsStart:containsStart - containsEnd:containsEnd - isVertical:isVertical]; + return [[[FlutterTextSelectionRect alloc] initWithRectAndInfo:rect + writingDirection:writingDirection + containsStart:containsStart + containsEnd:containsEnd + isVertical:isVertical] autorelease]; } - (instancetype)initWithRectAndInfo:(CGRect)rect @@ -857,6 +857,8 @@ - (void)setSelectedTextRange:(UITextRange*)selectedTextRange { [self updateEditingState]; if (_scribbleInProgress || _scribbleFocused) { _scribbleFocused = false; + NSAssert([selectedTextRange isKindOfClass:[FlutterTextRange class]], + @"Expected a FlutterTextRange for range (got %@).", [selectedTextRange class]); FlutterTextRange* flutterTextRange = (FlutterTextRange*)selectedTextRange; if (flutterTextRange.range.length > 0) { [_textInputDelegate showToolbar:_textInputClient]; @@ -1225,14 +1227,16 @@ - (CGRect)firstRectForRange:(UITextRange*)range { if (end < start) { first = end; } - FlutterTextRange* textRange = [[FlutterTextRange - rangeWithNSRange:fml::RangeForCharactersInRange(self.text, NSMakeRange(0, self.text.length))] - copy]; + FlutterTextRange* textRange = [FlutterTextRange + rangeWithNSRange:fml::RangeForCharactersInRange(self.text, NSMakeRange(0, self.text.length))]; for (NSUInteger i = 0; i < [_selectionRects count]; i++) { - if ([_selectionRects[i][4] unsignedIntegerValue] <= first && - ((i + 1 == [_selectionRects count] && textRange.range.length > first) || - (i + 1 < [_selectionRects count] && - [_selectionRects[i + 1][4] unsignedIntegerValue] > first))) { + BOOL startsOnOrBeforeStartOfRange = [_selectionRects[i][4] unsignedIntegerValue] <= first; + BOOL isLastSelectionRect = i + 1 == [_selectionRects count]; + BOOL endOfTextIsAfterStartOfRange = isLastSelectionRect && textRange.range.length > first; + BOOL nextSelectionRectIsAfterStartOfRange = + !isLastSelectionRect && [_selectionRects[i + 1][4] unsignedIntegerValue] > first; + if (startsOnOrBeforeStartOfRange && + (endOfTextIsAfterStartOfRange || nextSelectionRectIsAfterStartOfRange)) { CGRect rect = CGRectMake([_selectionRects[i][0] floatValue], [_selectionRects[i][1] floatValue], [_selectionRects[i][2] floatValue], [_selectionRects[i][3] floatValue]); @@ -1257,9 +1261,8 @@ - (UITextPosition*)closestPositionToPoint:(CGPoint)point { return [FlutterTextPosition positionWithIndex:currentIndex]; } - FlutterTextRange* range = [[FlutterTextRange - rangeWithNSRange:fml::RangeForCharactersInRange(self.text, NSMakeRange(0, self.text.length))] - copy]; + FlutterTextRange* range = [FlutterTextRange + rangeWithNSRange:fml::RangeForCharactersInRange(self.text, NSMakeRange(0, self.text.length))]; return [self closestPositionToPoint:point withinRange:range]; } From 561935f136dc81fdd6d94662fdfa34a02bfdc7d0 Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Thu, 1 Jul 2021 10:12:12 -0500 Subject: [PATCH 21/52] [scribble+master] Adding some (I think) missing autoreleases and cleaning up memory management for setSelectionRects --- .../Source/FlutterTextInputPlugin.mm | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index d6ad59a61282c..45343a6c13945 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -590,7 +590,6 @@ @implementation FlutterTextInputView { } @synthesize tokenizer = _tokenizer; -@synthesize selectionRects = _selectionRects; - (instancetype)init { self = [super init]; @@ -1185,7 +1184,8 @@ - (void)setSelectionRects:(NSArray*)rects { NSArray* rect = rects[i]; [rectsAsRect addObject:rect]; } - _selectionRects = rectsAsRect; + [_selectionRects autorelease]; + _selectionRects = [rectsAsRect retain]; } - (CGRect)firstRectForRange:(UITextRange*)range { @@ -1273,7 +1273,7 @@ - (NSArray*)selectionRectsForRange:(UITextRange*)range { @"Expected a FlutterTextPosition for range.end (got %@).", [range.end class]); NSUInteger start = ((FlutterTextPosition*)range.start).index; NSUInteger end = ((FlutterTextPosition*)range.end).index; - NSMutableArray* rects = [[NSMutableArray alloc] init]; + NSMutableArray* rects = [[[NSMutableArray alloc] init] autorelease]; for (NSUInteger i = 0; i < [_selectionRects count]; i++) { if ([_selectionRects[i][4] unsignedIntegerValue] >= start && [_selectionRects[i][4] unsignedIntegerValue] <= end) { @@ -1284,14 +1284,14 @@ - (NSArray*)selectionRectsForRange:(UITextRange*)range { CGRect rect = CGRectMake([_selectionRects[i][0] floatValue], [_selectionRects[i][1] floatValue], width, [_selectionRects[i][3] floatValue]); - FlutterTextSelectionRect* selectionRect = [[FlutterTextSelectionRect alloc] - initWithRectAndInfo:rect - writingDirection:UITextWritingDirectionNatural - containsStart:(i == 0) - containsEnd:(i == fml::RangeForCharactersInRange(self.text, - NSMakeRange(0, self.text.length)) - .length) - isVertical:FALSE]; + FlutterTextSelectionRect* selectionRect = [FlutterTextSelectionRect + selectionRectWithRectAndInfo:rect + writingDirection:UITextWritingDirectionNatural + containsStart:(i == 0) + containsEnd:(i == fml::RangeForCharactersInRange( + self.text, NSMakeRange(0, self.text.length)) + .length) + isVertical:FALSE]; [rects addObject:selectionRect]; } } @@ -1989,7 +1989,7 @@ - (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction requestElementsInRect:rect result:^(id _Nullable result) { NSMutableArray* elements = - [[NSMutableArray alloc] init]; + [[[NSMutableArray alloc] init] autorelease]; if ([result isKindOfClass:[NSArray class]]) { for (NSArray* elementArray in result) { [elements addObject:elementArray[0]]; From 4be3dd21435583c7ebe5a67586a66fed2083e48a Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Thu, 1 Jul 2021 10:44:05 -0500 Subject: [PATCH 22/52] [scribble+master] Remove a couple more unnecessary synthesize calls, use selectionRects as a prop --- .../Source/FlutterTextInputPlugin.mm | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 45343a6c13945..bae522a4dcbbf 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -1178,16 +1178,6 @@ - (void)setEditableTransform:(NSArray*)matrix { // candidates view for multi-stage input methods (e.g., Japanese) when using a // physical keyboard. -- (void)setSelectionRects:(NSArray*)rects { - NSMutableArray* rectsAsRect = [[NSMutableArray alloc] initWithCapacity:[rects count]]; - for (NSUInteger i = 0; i < [rects count]; i++) { - NSArray* rect = rects[i]; - [rectsAsRect addObject:rect]; - } - [_selectionRects autorelease]; - _selectionRects = [rectsAsRect retain]; -} - - (CGRect)firstRectForRange:(UITextRange*)range { NSAssert([range.start isKindOfClass:[FlutterTextPosition class]], @"Expected a FlutterTextPosition for range.start (got %@).", [range.start class]); @@ -1405,6 +1395,9 @@ - (BOOL)hasText { - (void)insertText:(NSString*)text { NSMutableArray* copiedRects = [[NSMutableArray alloc] initWithCapacity:[_selectionRects count]]; + NSAssert([_selectedTextRange.start isKindOfClass:[FlutterTextPosition class]], + @"Expected a FlutterTextPosition for position (got %@).", + [_selectedTextRange.start class]); NSUInteger insertPosition = ((FlutterTextPosition*)_selectedTextRange.start).index - 1; NSUInteger insertIndex = 0; for (NSUInteger i = 0; i < [_selectionRects count]; i++) { @@ -1569,8 +1562,6 @@ @implementation FlutterTextInputPlugin { } @synthesize textInputDelegate = _textInputDelegate; -@synthesize viewController = _viewController; -@synthesize scribbleElements = _scribbleElements; - (instancetype)init { self = [super init]; @@ -1666,7 +1657,13 @@ - (void)updateMarkedRect:(NSDictionary*)dictionary { } - (void)setSelectionRects:(NSArray*)rects { - [_activeView setSelectionRects:rects]; + NSMutableArray* rectsAsRect = + [[[NSMutableArray alloc] initWithCapacity:[rects count]] autorelease]; + for (NSUInteger i = 0; i < [rects count]; i++) { + NSArray* rect = rects[i]; + [rectsAsRect addObject:rect]; + } + _activeView.selectionRects = rects; } - (void)showTextInput { From 25f4100fd9e195e3f98406e14d2401158c7cb548 Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Thu, 1 Jul 2021 11:21:32 -0500 Subject: [PATCH 23/52] [scribble+master] Swap back to synthesize for FlutterTextSelectionRect - dynamic causes issues with missing selectors in the tests --- .../ios/framework/Source/FlutterTextInputPlugin.mm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index bae522a4dcbbf..6022acbc88fd0 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -477,11 +477,11 @@ - (UITextRange*)lineEnclosingPosition:(UITextPosition*)position { @implementation FlutterTextSelectionRect -@dynamic rect; -@dynamic writingDirection; -@dynamic containsStart; -@dynamic containsEnd; -@dynamic isVertical; +@synthesize rect = _rect; +@synthesize writingDirection = _writingDirection; +@synthesize containsStart = _containsStart; +@synthesize containsEnd = _containsEnd; +@synthesize isVertical = _isVertical; + (instancetype)selectionRectWithRectAndInfo:(CGRect)rect writingDirection:(NSWritingDirection)writingDirection From 34db3dd1c1e44f0339bcfbdfba9adcc9f4de2872 Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Thu, 1 Jul 2021 11:44:11 -0500 Subject: [PATCH 24/52] [scribble+master] Genericize selectionRects NSArray --- .../darwin/ios/framework/Source/FlutterTextInputPlugin.h | 2 +- .../darwin/ios/framework/Source/FlutterTextInputPlugin.mm | 4 ++-- .../darwin/ios/framework/Source/FlutterTextInputPluginTest.m | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h index c97fb16b62601..4891da652ed50 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h @@ -114,7 +114,7 @@ FLUTTER_DARWIN_EXPORT @property(nonatomic, assign) UIViewController* viewController; @property(nonatomic) BOOL scribbleFocusing; @property(nonatomic) BOOL scribbleFocused; -@property(nonatomic, strong) NSArray* selectionRects; +@property(nonatomic, strong) NSArray*>* selectionRects; @end #endif // SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERTEXTINPUTPLUGIN_H_ diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 6022acbc88fd0..96a1034ccc569 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -1657,10 +1657,10 @@ - (void)updateMarkedRect:(NSDictionary*)dictionary { } - (void)setSelectionRects:(NSArray*)rects { - NSMutableArray* rectsAsRect = + NSMutableArray*>* rectsAsRect = [[[NSMutableArray alloc] initWithCapacity:[rects count]] autorelease]; for (NSUInteger i = 0; i < [rects count]; i++) { - NSArray* rect = rects[i]; + NSArray* rect = rects[i]; [rectsAsRect addObject:rect]; } _activeView.selectionRects = rects; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m index 58063776436a6..ea8eb4c2ab28c 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m @@ -20,7 +20,6 @@ - (void)setTextInputState:(NSDictionary*)state; - (void)setMarkedRect:(CGRect)markedRect; - (void)updateEditingState; - (BOOL)isVisibleToAutofill; -- (void)setSelectionRects:(NSArray*)rects; @end From 82f9ce651ab72af2dcd4c86d043f6f61ffe52d98 Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Thu, 1 Jul 2021 14:24:51 -0500 Subject: [PATCH 25/52] [scribble+master] Fix formatting issues and swap from _reusableInputView to _activeView --- .../ios/framework/Source/FlutterTextInputPlugin.h | 5 +++-- .../ios/framework/Source/FlutterTextInputPlugin.mm | 10 +++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h index 4891da652ed50..6167898dffcfc 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h @@ -10,11 +10,12 @@ #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h" -@interface FlutterTextInputPlugin : NSObject +@interface FlutterTextInputPlugin : NSObject @property(nonatomic, assign) id textInputDelegate; @property(nonatomic, readonly) UIViewController* viewController; -@property(nonatomic, assign) NSMutableDictionary* scribbleElements; +@property(nonatomic, assign) + NSMutableDictionary* scribbleElements; - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result; /** diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 82c0a978d73ae..19524bc23f49c 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -2074,7 +2074,7 @@ - (void)clearTextInputClient { - (BOOL)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction isElementFocused:(UIScribbleElementIdentifier)elementIdentifier API_AVAILABLE(ios(14.0)) { - return _reusableInputView.scribbleFocused; + return _activeView.scribbleFocused; } - (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction @@ -2082,13 +2082,13 @@ - (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction referencePoint:(CGPoint)focusReferencePoint completion:(void (^)(UIResponder* focusedInput))completion API_AVAILABLE(ios(14.0)) { - _reusableInputView.scribbleFocusing = true; + _activeView.scribbleFocusing = true; [_textInputDelegate focusElement:elementIdentifier atPoint:focusReferencePoint result:^(id _Nullable result) { - _reusableInputView.scribbleFocusing = false; - _reusableInputView.scribbleFocused = true; - completion(_reusableInputView); + _activeView.scribbleFocusing = false; + _activeView.scribbleFocused = true; + completion(_activeView); }]; } From 85f86652e7b7aeef7759cce8c28a4cddf11ff34f Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Thu, 1 Jul 2021 17:15:47 -0500 Subject: [PATCH 26/52] Couple more memory things --- .../darwin/ios/framework/Source/FlutterTextInputPlugin.mm | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 19524bc23f49c..7bf0a56aab2cb 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -759,6 +759,7 @@ - (void)dealloc { [_tokenizer release]; [_autofillId release]; [_inputViewController release]; + [_selectionRects release]; [super dealloc]; } @@ -1282,6 +1283,7 @@ - (CGRect)firstRectForRange:(UITextRange*)range { } if (!_scribbleInProgress && !_scribbleFocusing && !_scribbleFocused) { + NSLog(@"showAutocorrectionPromptRectForStart"); [_textInputDelegate showAutocorrectionPromptRectForStart:start end:end withClient:_textInputClient]; @@ -1513,7 +1515,8 @@ - (BOOL)hasText { } - (void)insertText:(NSString*)text { - NSMutableArray* copiedRects = [[NSMutableArray alloc] initWithCapacity:[_selectionRects count]]; + NSMutableArray*>* copiedRects = + [[NSMutableArray alloc] initWithCapacity:[_selectionRects count]]; NSAssert([_selectedTextRange.start isKindOfClass:[FlutterTextPosition class]], @"Expected a FlutterTextPosition for position (got %@).", [_selectedTextRange.start class]); @@ -1785,7 +1788,7 @@ - (void)setSelectionRects:(NSArray*)rects { NSArray* rect = rects[i]; [rectsAsRect addObject:rect]; } - _activeView.selectionRects = rects; + _activeView.selectionRects = rectsAsRect; } - (void)showTextInput { From ea37dfac64e4aeb2293a7ae3f34613d50a905f98 Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Thu, 1 Jul 2021 21:01:36 -0500 Subject: [PATCH 27/52] Clear scribbleFocused flag in touchesBegan, insert/delete text rather than in setSelectedTextRange, as that seems to be more reliable about preventing the autocorrection prompt rect call from going out --- .../Source/FlutterTextInputPlugin.mm | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 7bf0a56aab2cb..50cb38a807cbb 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -807,6 +807,7 @@ - (void)setTextInputState:(NSDictionary*)state { // Forward touches to the viewController to allow tapping inside the UITextField as normal - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { + _scribbleFocused = NO; [self.viewController touchesBegan:touches withEvent:event]; } @@ -867,24 +868,24 @@ - (void)setIsVisibleToAutofill:(BOOL)isVisibleToAutofill { - (void)scribbleInteractionWillBeginWriting:(UIScribbleInteraction*)interaction API_AVAILABLE(ios(14.0)) { - _scribbleInProgress = true; + _scribbleInProgress = YES; [_textInputDelegate scribbleInteractionBegan]; } - (void)scribbleInteractionDidFinishWriting:(UIScribbleInteraction*)interaction API_AVAILABLE(ios(14.0)) { - _scribbleInProgress = false; + _scribbleInProgress = NO; [_textInputDelegate scribbleInteractionFinished]; } - (BOOL)scribbleInteraction:(UIScribbleInteraction*)interaction shouldBeginAtLocation:(CGPoint)location API_AVAILABLE(ios(14.0)) { - return true; + return YES; } - (BOOL)scribbleInteractionShouldDelayFocus:(UIScribbleInteraction*)interaction API_AVAILABLE(ios(14.0)) { - return false; + return NO; } #pragma mark - UIResponder Overrides @@ -929,7 +930,6 @@ - (void)setSelectedTextRange:(UITextRange*)selectedTextRange { [self setSelectedTextRangeLocal:selectedTextRange]; [self updateEditingState]; if (_scribbleInProgress || _scribbleFocused) { - _scribbleFocused = false; NSAssert([selectedTextRange isKindOfClass:[FlutterTextRange class]], @"Expected a FlutterTextRange for range (got %@).", [selectedTextRange class]); FlutterTextRange* flutterTextRange = (FlutterTextRange*)selectedTextRange; @@ -1283,7 +1283,6 @@ - (CGRect)firstRectForRange:(UITextRange*)range { } if (!_scribbleInProgress && !_scribbleFocusing && !_scribbleFocused) { - NSLog(@"showAutocorrectionPromptRectForStart"); [_textInputDelegate showAutocorrectionPromptRectForStart:start end:end withClient:_textInputClient]; @@ -1371,7 +1370,7 @@ - (NSArray*)selectionRectsForRange:(UITextRange*)range { containsEnd:(i == fml::RangeForCharactersInRange( self.text, NSMakeRange(0, self.text.length)) .length) - isVertical:FALSE]; + isVertical:NO]; [rects addObject:selectionRect]; } } @@ -1549,6 +1548,7 @@ - (void)insertText:(NSString*)text { } } + _scribbleFocused = NO; _selectionRects = copiedRects; _selectionAffinity = _kTextAffinityDownstream; [self replaceRange:_selectedTextRange withText:text]; @@ -1567,6 +1567,7 @@ - (void)removeTextPlaceholder:(UITextPlaceholder*)textPlaceholder API_AVAILABLE( - (void)deleteBackward { _selectionAffinity = _kTextAffinityDownstream; + _scribbleFocused = NO; // When deleting Thai vowel, _selectedTextRange has location // but does not have length, so we have to manually set it. @@ -2085,12 +2086,12 @@ - (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction referencePoint:(CGPoint)focusReferencePoint completion:(void (^)(UIResponder* focusedInput))completion API_AVAILABLE(ios(14.0)) { - _activeView.scribbleFocusing = true; + _activeView.scribbleFocusing = YES; [_textInputDelegate focusElement:elementIdentifier atPoint:focusReferencePoint result:^(id _Nullable result) { - _activeView.scribbleFocusing = false; - _activeView.scribbleFocused = true; + _activeView.scribbleFocusing = NO; + _activeView.scribbleFocused = YES; completion(_activeView); }]; } From 671f29c4f051c2751498ed795efede695a8a37a1 Mon Sep 17 00:00:00 2001 From: Aaron Clarke Date: Fri, 2 Jul 2021 18:21:05 -0700 Subject: [PATCH 28/52] did a bit of cleanup (the easy stuff) --- .../ios/framework/Source/FlutterEngine.mm | 47 +++++--- .../Source/FlutterTextInputDelegate.h | 51 ++++++--- .../framework/Source/FlutterTextInputPlugin.h | 1 + .../Source/FlutterTextInputPlugin.mm | 101 ++++++++++-------- 4 files changed, 124 insertions(+), 76 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index ff78d86fa8a11..4d4a86ef9d0ef 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -680,17 +680,23 @@ - (void)notifyLowMemory { #pragma mark - Text input delegate -- (void)updateEditingClient:(int)client withState:(NSDictionary*)state { +- (void)flutterTextInputView:(FlutterTextInputView*)textInputView + updateEditingClient:(int)client + withState:(NSDictionary*)state { [_textInputChannel.get() invokeMethod:@"TextInputClient.updateEditingState" arguments:@[ @(client), state ]]; } -- (void)updateEditingClient:(int)client withState:(NSDictionary*)state withTag:(NSString*)tag { +- (void)flutterTextInputView:(FlutterTextInputView*)textInputView + updateEditingClient:(int)client + withState:(NSDictionary*)state + withTag:(NSString*)tag { [_textInputChannel.get() invokeMethod:@"TextInputClient.updateEditingStateWithTag" arguments:@[ @(client), @{tag : state} ]]; } -- (void)updateFloatingCursor:(FlutterFloatingCursorDragState)state +- (void)flutterTextInputView:(FlutterTextInputView*)textInputView + updateFloatingCursor:(FlutterFloatingCursorDragState)state withClient:(int)client withPosition:(NSDictionary*)position { NSString* stateString; @@ -709,7 +715,9 @@ - (void)updateFloatingCursor:(FlutterFloatingCursorDragState)state arguments:@[ @(client), stateString, position ]]; } -- (void)performAction:(FlutterTextInputAction)action withClient:(int)client { +- (void)flutterTextInputView:(FlutterTextInputView*)textInputView + performAction:(FlutterTextInputAction)action + withClient:(int)client { NSString* actionString; switch (action) { case FlutterTextInputActionUnspecified: @@ -754,50 +762,57 @@ - (void)performAction:(FlutterTextInputAction)action withClient:(int)client { arguments:@[ @(client), actionString ]]; } -- (void)showAutocorrectionPromptRectForStart:(NSUInteger)start - end:(NSUInteger)end - withClient:(int)client { +- (void)flutterTextInputView:(FlutterTextInputView*)textInputView + showAutocorrectionPromptRectForStart:(NSUInteger)start + end:(NSUInteger)end + withClient:(int)client { [_textInputChannel.get() invokeMethod:@"TextInputClient.showAutocorrectionPromptRect" arguments:@[ @(client), @(start), @(end) ]]; } #pragma mark - FlutterViewEngineDelegate -- (void)showToolbar:(int)client { +- (void)flutterTextInputView:(FlutterTextInputView*)textInputView showToolbar:(int)client { [_textInputChannel.get() invokeMethod:@"TextInputClient.showToolbar" arguments:@[ @(client) ]]; } -- (void)focusElement:(UIScribbleElementIdentifier)elementIdentifier - atPoint:(CGPoint)referencePoint - result:(id)callback { +- (void)flutterTextInputPlugin:(FlutterTextInputPlugin*)textInputPlugin + focusElement:(UIScribbleElementIdentifier)elementIdentifier + atPoint:(CGPoint)referencePoint + result:(FlutterResult)callback { [_textInputChannel.get() invokeMethod:@"TextInputClient.focusElement" arguments:@[ elementIdentifier, @(referencePoint.x), @(referencePoint.y) ] result:callback]; } -- (void)requestElementsInRect:(CGRect)rect result:(FlutterResult)callback { +- (void)flutterTextInputPlugin:(FlutterTextInputPlugin*)textInputPlugin + requestElementsInRect:(CGRect)rect + result:(FlutterResult)callback { [_textInputChannel.get() invokeMethod:@"TextInputClient.requestElementsInRect" arguments:@[ @(rect.origin.x), @(rect.origin.y), @(rect.size.width), @(rect.size.height) ] result:callback]; } -- (void)scribbleInteractionBegan { +- (void)flutterTextInputViewScribbleInteractionBegan:(FlutterTextInputView*)textInputView { [_textInputChannel.get() invokeMethod:@"TextInputClient.scribbleInteractionBegan" arguments:nil]; } -- (void)scribbleInteractionFinished { +- (void)flutterTextInputViewScribbleInteractionFinished:(FlutterTextInputView*)textInputView { [_textInputChannel.get() invokeMethod:@"TextInputClient.scribbleInteractionFinished" arguments:nil]; } -- (void)insertTextPlaceholderWithSize:(CGSize)size withClient:(int)client { +- (void)flutterTextInputView:(FlutterTextInputView*)textInputView + insertTextPlaceholderWithSize:(CGSize)size + withClient:(int)client { [_textInputChannel.get() invokeMethod:@"TextInputClient.insertTextPlaceholder" arguments:@[ @(client), @(size.width), @(size.height) ]]; } -- (void)removeTextPlaceholder:(int)client { +- (void)flutterTextInputView:(FlutterTextInputView*)textInputView + removeTextPlaceholder:(int)client { [_textInputChannel.get() invokeMethod:@"TextInputClient.removeTextPlaceholder" arguments:@[ @(client) ]]; } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h index 4a3842364aebb..3c043d16dccf3 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h @@ -7,6 +7,9 @@ #import +@class FlutterTextInputPlugin; +@class FlutterTextInputView; + typedef NS_ENUM(NSInteger, FlutterTextInputAction) { FlutterTextInputActionUnspecified, FlutterTextInputActionDone, @@ -28,26 +31,40 @@ typedef NS_ENUM(NSInteger, FlutterFloatingCursorDragState) { }; @protocol FlutterTextInputDelegate - -- (void)updateEditingClient:(int)client withState:(NSDictionary*)state; -- (void)updateEditingClient:(int)client withState:(NSDictionary*)state withTag:(NSString*)tag; -- (void)performAction:(FlutterTextInputAction)action withClient:(int)client; -- (void)updateFloatingCursor:(FlutterFloatingCursorDragState)state +- (void)flutterTextInputView:(FlutterTextInputView*)textInputView + updateEditingClient:(int)client + withState:(NSDictionary*)state; +- (void)flutterTextInputView:(FlutterTextInputView*)textInputView + updateEditingClient:(int)client + withState:(NSDictionary*)state + withTag:(NSString*)tag; +- (void)flutterTextInputView:(FlutterTextInputView*)textInputView + performAction:(FlutterTextInputAction)action + withClient:(int)client; +- (void)flutterTextInputView:(FlutterTextInputView*)textInputView + updateFloatingCursor:(FlutterFloatingCursorDragState)state withClient:(int)client withPosition:(NSDictionary*)point; -- (void)showAutocorrectionPromptRectForStart:(NSUInteger)start - end:(NSUInteger)end - withClient:(int)client; -- (void)showToolbar:(int)client; -- (void)focusElement:(UIScribbleElementIdentifier)elementIdentifier - atPoint:(CGPoint)referencePoint - result:(id)callback; -- (void)requestElementsInRect:(CGRect)rect result:(FlutterResult)callback; -- (void)scribbleInteractionBegan; -- (void)scribbleInteractionFinished; -- (void)insertTextPlaceholderWithSize:(CGSize)size withClient:(int)client; -- (void)removeTextPlaceholder:(int)client; +- (void)flutterTextInputView:(FlutterTextInputView*)textInputView + showAutocorrectionPromptRectForStart:(NSUInteger)start + end:(NSUInteger)end + withClient:(int)client; +- (void)flutterTextInputView:(FlutterTextInputView*)textInputView showToolbar:(int)client; +- (void)flutterTextInputViewScribbleInteractionBegan:(FlutterTextInputView*)textInputView; +- (void)flutterTextInputViewScribbleInteractionFinished:(FlutterTextInputView*)textInputView; +- (void)flutterTextInputView:(FlutterTextInputView*)textInputView + insertTextPlaceholderWithSize:(CGSize)size + withClient:(int)client; +- (void)flutterTextInputView:(FlutterTextInputView*)textInputView removeTextPlaceholder:(int)client; +// TODO(gaaclarke) Refactor these into their own delegate. +- (void)flutterTextInputPlugin:(FlutterTextInputPlugin*)textInputPlugin + focusElement:(UIScribbleElementIdentifier)elementIdentifier + atPoint:(CGPoint)referencePoint + result:(FlutterResult)callback; +- (void)flutterTextInputPlugin:(FlutterTextInputPlugin*)textInputPlugin + requestElementsInRect:(CGRect)rect + result:(FlutterResult)callback; @end #endif // SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERTEXTINPUTDELEGATE_H_ diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h index 6167898dffcfc..60f6ad4da7569 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h @@ -78,6 +78,7 @@ containsEnd:(BOOL)containsEnd isVertical:(BOOL)isVertical; +- (instancetype)init NS_UNAVAILABLE; @end API_AVAILABLE(ios(13.0)) @interface FlutterTextPlaceholder : UITextPlaceholder diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 50cb38a807cbb..7d65f47ef96d0 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -869,13 +869,13 @@ - (void)setIsVisibleToAutofill:(BOOL)isVisibleToAutofill { - (void)scribbleInteractionWillBeginWriting:(UIScribbleInteraction*)interaction API_AVAILABLE(ios(14.0)) { _scribbleInProgress = YES; - [_textInputDelegate scribbleInteractionBegan]; + [_textInputDelegate flutterTextInputViewScribbleInteractionBegan:self]; } - (void)scribbleInteractionDidFinishWriting:(UIScribbleInteraction*)interaction API_AVAILABLE(ios(14.0)) { _scribbleInProgress = NO; - [_textInputDelegate scribbleInteractionFinished]; + [_textInputDelegate flutterTextInputViewScribbleInteractionFinished:self]; } - (BOOL)scribbleInteraction:(UIScribbleInteraction*)interaction @@ -934,7 +934,7 @@ - (void)setSelectedTextRange:(UITextRange*)selectedTextRange { @"Expected a FlutterTextRange for range (got %@).", [selectedTextRange class]); FlutterTextRange* flutterTextRange = (FlutterTextRange*)selectedTextRange; if (flutterTextRange.range.length > 0) { - [_textInputDelegate showToolbar:_textInputClient]; + [_textInputDelegate flutterTextInputView:self showToolbar:_textInputClient]; } } } @@ -992,8 +992,9 @@ - (void)replaceRange:(UITextRange*)range withText:(NSString*)text { - (BOOL)shouldChangeTextInRange:(UITextRange*)range replacementText:(NSString*)text { if (self.returnKeyType == UIReturnKeyDefault && [text isEqualToString:@"\n"]) { - [self.textInputDelegate performAction:FlutterTextInputActionNewline - withClient:_textInputClient]; + [self.textInputDelegate flutterTextInputView:self + performAction:FlutterTextInputActionNewline + withClient:_textInputClient]; return YES; } @@ -1034,7 +1035,9 @@ - (BOOL)shouldChangeTextInRange:(UITextRange*)range replacementText:(NSString*)t break; } - [self.textInputDelegate performAction:action withClient:_textInputClient]; + [self.textInputDelegate flutterTextInputView:self + performAction:action + withClient:_textInputClient]; return NO; } @@ -1283,9 +1286,10 @@ - (CGRect)firstRectForRange:(UITextRange*)range { } if (!_scribbleInProgress && !_scribbleFocusing && !_scribbleFocused) { - [_textInputDelegate showAutocorrectionPromptRectForStart:start - end:end - withClient:_textInputClient]; + [_textInputDelegate flutterTextInputView:self + showAutocorrectionPromptRectForStart:start + end:end + withClient:_textInputClient]; } NSUInteger first = start; @@ -1454,7 +1458,8 @@ - (void)beginFloatingCursorAtPoint:(CGPoint)point { // (1, 2, -3, -4) would become (-2, -2, 3, 4). NSAssert(!_isFloatingCursorActive, @"Another floating cursor is currently active."); _isFloatingCursorActive = true; - [self.textInputDelegate updateFloatingCursor:FlutterFloatingCursorDragStateStart + [self.textInputDelegate flutterTextInputView:self + updateFloatingCursor:FlutterFloatingCursorDragStateStart withClient:_textInputClient withPosition:@{@"X" : @(point.x), @"Y" : @(point.y)}]; } @@ -1462,7 +1467,8 @@ - (void)beginFloatingCursorAtPoint:(CGPoint)point { - (void)updateFloatingCursorAtPoint:(CGPoint)point { NSAssert(_isFloatingCursorActive, @"updateFloatingCursorAtPoint is called without an active floating cursor."); - [self.textInputDelegate updateFloatingCursor:FlutterFloatingCursorDragStateUpdate + [self.textInputDelegate flutterTextInputView:self + updateFloatingCursor:FlutterFloatingCursorDragStateUpdate withClient:_textInputClient withPosition:@{@"X" : @(point.x), @"Y" : @(point.y)}]; } @@ -1471,7 +1477,8 @@ - (void)endFloatingCursor { NSAssert(_isFloatingCursorActive, @"endFloatingCursor is called without an active floating cursor."); _isFloatingCursorActive = false; - [self.textInputDelegate updateFloatingCursor:FlutterFloatingCursorDragStateEnd + [self.textInputDelegate flutterTextInputView:self + updateFloatingCursor:FlutterFloatingCursorDragStateEnd withClient:_textInputClient withPosition:@{@"X" : @(0), @"Y" : @(0)}]; } @@ -1501,11 +1508,14 @@ - (void)updateEditingState { }; if (_textInputClient == 0 && _autofillId != nil) { - [self.textInputDelegate updateEditingClient:_textInputClient - withState:state - withTag:_autofillId]; + [self.textInputDelegate flutterTextInputView:self + updateEditingClient:_textInputClient + withState:state + withTag:_autofillId]; } else { - [self.textInputDelegate updateEditingClient:_textInputClient withState:state]; + [self.textInputDelegate flutterTextInputView:self + updateEditingClient:_textInputClient + withState:state]; } } @@ -1555,14 +1565,16 @@ - (void)insertText:(NSString*)text { } - (UITextPlaceholder*)insertTextPlaceholderWithSize:(CGSize)size API_AVAILABLE(ios(13.0)) { - [_textInputDelegate insertTextPlaceholderWithSize:size withClient:_textInputClient]; + [_textInputDelegate flutterTextInputView:self + insertTextPlaceholderWithSize:size + withClient:_textInputClient]; _hasPlaceholder = YES; return [[[FlutterTextPlaceholder alloc] init] autorelease]; } - (void)removeTextPlaceholder:(UITextPlaceholder*)textPlaceholder API_AVAILABLE(ios(13.0)) { _hasPlaceholder = NO; - [_textInputDelegate removeTextPlaceholder:_textInputClient]; + [_textInputDelegate flutterTextInputView:self removeTextPlaceholder:_textInputClient]; } - (void)deleteBackward { @@ -2087,13 +2099,14 @@ - (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction completion:(void (^)(UIResponder* focusedInput))completion API_AVAILABLE(ios(14.0)) { _activeView.scribbleFocusing = YES; - [_textInputDelegate focusElement:elementIdentifier - atPoint:focusReferencePoint - result:^(id _Nullable result) { - _activeView.scribbleFocusing = NO; - _activeView.scribbleFocused = YES; - completion(_activeView); - }]; + [_textInputDelegate flutterTextInputPlugin:self + focusElement:elementIdentifier + atPoint:focusReferencePoint + result:^(id _Nullable result) { + _activeView.scribbleFocusing = NO; + _activeView.scribbleFocused = YES; + completion(_activeView); + }]; } - (BOOL)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction @@ -2128,24 +2141,26 @@ - (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction (void (^)(NSArray* elements))completion API_AVAILABLE(ios(14.0)) { [_textInputDelegate - requestElementsInRect:rect - result:^(id _Nullable result) { - NSMutableArray* elements = - [[[NSMutableArray alloc] init] autorelease]; - if ([result isKindOfClass:[NSArray class]]) { - for (NSArray* elementArray in result) { - [elements addObject:elementArray[0]]; - [_scribbleElements - setObject:[NSValue valueWithCGRect:CGRectMake( - [elementArray[1] floatValue], - [elementArray[2] floatValue], - [elementArray[3] floatValue], - [elementArray[4] floatValue])] - forKey:elementArray[0]]; - } - } - completion(elements); - }]; + flutterTextInputPlugin:self + requestElementsInRect:rect + result:^(id _Nullable result) { + NSMutableArray* elements = + [[[NSMutableArray alloc] init] autorelease]; + if ([result isKindOfClass:[NSArray class]]) { + for (NSArray* elementArray in result) { + [elements addObject:elementArray[0]]; + [_scribbleElements + setObject:[NSValue + valueWithCGRect:CGRectMake( + [elementArray[1] floatValue], + [elementArray[2] floatValue], + [elementArray[3] floatValue], + [elementArray[4] floatValue])] + forKey:elementArray[0]]; + } + } + completion(elements); + }]; } #pragma mark - Methods related to Scribble support From 6eed8a7f479b1a67d6968ce9be9ce10c5d313e6c Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Mon, 12 Jul 2021 21:41:10 -0500 Subject: [PATCH 29/52] extract FlutterIndirectScribbleDelegate interface to handle requestElementsInRect and focusElement messages coming from UIIndirectScribbleInteractionDelegate --- .../ios/framework/Source/FlutterEngine.mm | 6 ++++- .../framework/Source/FlutterEngine_Internal.h | 1 + .../Source/FlutterIndirectScribbleDelegate.h | 22 +++++++++++++++++++ .../Source/FlutterTextInputDelegate.h | 8 ------- .../framework/Source/FlutterTextInputPlugin.h | 5 ++++- .../Source/FlutterTextInputPlugin.mm | 18 +++++++-------- 6 files changed, 41 insertions(+), 19 deletions(-) create mode 100644 shell/platform/darwin/ios/framework/Source/FlutterIndirectScribbleDelegate.h diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index 4d4a86ef9d0ef..c32c46064a27e 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -20,6 +20,7 @@ #import "flutter/shell/platform/darwin/common/command_line.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterBinaryMessengerRelay.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject_Internal.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterIndirectScribbleDelegate.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterObservatoryPublisher.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h" @@ -42,7 +43,9 @@ @interface FlutterEngineRegistrar : NSObject - (instancetype)initWithPlugin:(NSString*)pluginKey flutterEngine:(FlutterEngine*)flutterEngine; @end -@interface FlutterEngine () +@interface FlutterEngine () // Maintains a dictionary of plugin names that have registered with the engine. Used by // FlutterEngineRegistrar to implement a FlutterPluginRegistrar. @property(nonatomic, readonly) NSMutableDictionary* pluginPublications; @@ -473,6 +476,7 @@ - (void)setupChannels { _textInputPlugin.reset([[FlutterTextInputPlugin alloc] init]); _textInputPlugin.get().textInputDelegate = self; + _textInputPlugin.get().indirectScribbleDelegate = self; [_textInputPlugin.get() setupIndirectScribbleInteraction:self.viewController]; _platformPlugin.reset([[FlutterPlatformPlugin alloc] initWithEngine:[self getWeakPtr]]); diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h index 698e5fdf8df2a..5c1c21e1f2e61 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h @@ -16,6 +16,7 @@ #include "flutter/shell/common/shell.h" #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject_Internal.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterIndirectScribbleDelegate.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterRestorationPlugin.h" diff --git a/shell/platform/darwin/ios/framework/Source/FlutterIndirectScribbleDelegate.h b/shell/platform/darwin/ios/framework/Source/FlutterIndirectScribbleDelegate.h new file mode 100644 index 0000000000000..ab7e7b4e3ba41 --- /dev/null +++ b/shell/platform/darwin/ios/framework/Source/FlutterIndirectScribbleDelegate.h @@ -0,0 +1,22 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERINDIRECTSCRIBBLEDELEGATE_H_ +#define SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERINDIRECTSCRIBBLEDELEGATE_H_ + +#import + +@class FlutterTextInputPlugin; + +@protocol FlutterIndirectScribbleDelegate +- (void)flutterTextInputPlugin:(FlutterTextInputPlugin*)textInputPlugin + focusElement:(UIScribbleElementIdentifier)elementIdentifier + atPoint:(CGPoint)referencePoint + result:(FlutterResult)callback; +- (void)flutterTextInputPlugin:(FlutterTextInputPlugin*)textInputPlugin + requestElementsInRect:(CGRect)rect + result:(FlutterResult)callback; +@end + +#endif // SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERINDIRECTSCRIBBLEDELEGATE_H_ diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h index 3c043d16dccf3..9fcedb1f24231 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h @@ -57,14 +57,6 @@ typedef NS_ENUM(NSInteger, FlutterFloatingCursorDragState) { withClient:(int)client; - (void)flutterTextInputView:(FlutterTextInputView*)textInputView removeTextPlaceholder:(int)client; -// TODO(gaaclarke) Refactor these into their own delegate. -- (void)flutterTextInputPlugin:(FlutterTextInputPlugin*)textInputPlugin - focusElement:(UIScribbleElementIdentifier)elementIdentifier - atPoint:(CGPoint)referencePoint - result:(FlutterResult)callback; -- (void)flutterTextInputPlugin:(FlutterTextInputPlugin*)textInputPlugin - requestElementsInRect:(CGRect)rect - result:(FlutterResult)callback; @end #endif // SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERTEXTINPUTDELEGATE_H_ diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h index 60f6ad4da7569..19982f2a09d58 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h @@ -8,11 +8,13 @@ #import #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterIndirectScribbleDelegate.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h" @interface FlutterTextInputPlugin : NSObject @property(nonatomic, assign) id textInputDelegate; +@property(nonatomic, assign) id indirectScribbleDelegate; @property(nonatomic, readonly) UIViewController* viewController; @property(nonatomic, assign) NSMutableDictionary* scribbleElements; @@ -110,9 +112,10 @@ FLUTTER_DARWIN_EXPORT @property(nonatomic) UITextSmartDashesType smartDashesType API_AVAILABLE(ios(11.0)); @property(nonatomic, copy) UITextContentType textContentType API_AVAILABLE(ios(10.0)); -// Scribble Support @property(nonatomic, assign) id textInputDelegate; @property(nonatomic, assign) UIAccessibilityElement* backingTextInputAccessibilityObject; + +// Scribble Support @property(nonatomic, assign) UIViewController* viewController; @property(nonatomic) BOOL scribbleFocusing; @property(nonatomic) BOOL scribbleFocused; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 7d65f47ef96d0..25dab701b5ec3 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -2099,14 +2099,14 @@ - (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction completion:(void (^)(UIResponder* focusedInput))completion API_AVAILABLE(ios(14.0)) { _activeView.scribbleFocusing = YES; - [_textInputDelegate flutterTextInputPlugin:self - focusElement:elementIdentifier - atPoint:focusReferencePoint - result:^(id _Nullable result) { - _activeView.scribbleFocusing = NO; - _activeView.scribbleFocused = YES; - completion(_activeView); - }]; + [_indirectScribbleDelegate flutterTextInputPlugin:self + focusElement:elementIdentifier + atPoint:focusReferencePoint + result:^(id _Nullable result) { + _activeView.scribbleFocusing = NO; + _activeView.scribbleFocused = YES; + completion(_activeView); + }]; } - (BOOL)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction @@ -2140,7 +2140,7 @@ - (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction completion: (void (^)(NSArray* elements))completion API_AVAILABLE(ios(14.0)) { - [_textInputDelegate + [_indirectScribbleDelegate flutterTextInputPlugin:self requestElementsInRect:rect result:^(id _Nullable result) { From 6ac1cb65ad0af2755374ea77cef8a50fa45446b5 Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Tue, 20 Jul 2021 11:07:29 -0500 Subject: [PATCH 30/52] Update new member of FlutterTextInputDelegate to pass caller as first argument --- .../ios/framework/Source/FlutterEngine.mm | 5 ++- .../Source/FlutterTextInputDelegate.h | 5 ++- .../Source/FlutterTextInputPlugin.mm | 40 ++++++++++--------- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index 5d2cfee3d8bd4..f3d202814dfa1 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -725,8 +725,9 @@ - (void)notifyLowMemory { #pragma mark - Text input delegate -- (void)handlePressEvent:(FlutterUIPressProxy*)press - nextAction:(void (^)())next API_AVAILABLE(ios(13.4)) { +- (void)flutterTextInputView:(FlutterTextInputView*)textInputView + handlePressEvent:(FlutterUIPressProxy*)press + nextAction:(void (^)())next API_AVAILABLE(ios(13.4)) { if (_viewController.get() != nullptr) { [_viewController.get() handlePressEvent:press nextAction:next]; } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h index 1706b979002c3..84e0c000bd39d 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h @@ -32,8 +32,9 @@ typedef NS_ENUM(NSInteger, FlutterFloatingCursorDragState) { }; @protocol FlutterTextInputDelegate -- (void)handlePressEvent:(FlutterUIPressProxy*)press - nextAction:(void (^)())next API_AVAILABLE(ios(13.4)); +- (void)flutterTextInputView:(FlutterTextInputView*)textInputView + handlePressEvent:(FlutterUIPressProxy*)press + nextAction:(void (^)())next API_AVAILABLE(ios(13.4)); - (void)flutterTextInputView:(FlutterTextInputView*)textInputView updateEditingClient:(int)client withState:(NSDictionary*)state; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 088eb6f57de21..66c06e3a2bced 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -857,11 +857,12 @@ - (void)pressesBegan:(NSSet*)presses withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) { if (@available(iOS 13.4, *)) { for (UIPress* press in presses) { - [_textInputDelegate handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press - withEvent:event] - nextAction:^() { - [super pressesBegan:[NSSet setWithObject:press] withEvent:event]; - }]; + [_textInputDelegate + flutterTextInputView:self + handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press withEvent:event] + nextAction:^() { + [super pressesBegan:[NSSet setWithObject:press] withEvent:event]; + }]; } } else { [super pressesBegan:presses withEvent:event]; @@ -873,10 +874,11 @@ - (void)pressesChanged:(NSSet*)presses if (@available(iOS 13.4, *)) { for (UIPress* press in presses) { [_textInputDelegate - handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press withEvent:event] - nextAction:^() { - [super pressesChanged:[NSSet setWithObject:press] withEvent:event]; - }]; + flutterTextInputView:self + handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press withEvent:event] + nextAction:^() { + [super pressesChanged:[NSSet setWithObject:press] withEvent:event]; + }]; } } else { [super pressesChanged:presses withEvent:event]; @@ -887,11 +889,12 @@ - (void)pressesEnded:(NSSet*)presses withEvent:(UIPressesEvent*)event API_AVAILABLE(ios(9.0)) { if (@available(iOS 13.4, *)) { for (UIPress* press in presses) { - [_textInputDelegate handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press - withEvent:event] - nextAction:^() { - [super pressesEnded:[NSSet setWithObject:press] withEvent:event]; - }]; + [_textInputDelegate + flutterTextInputView:self + handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press withEvent:event] + nextAction:^() { + [super pressesEnded:[NSSet setWithObject:press] withEvent:event]; + }]; } } else { [super pressesEnded:presses withEvent:event]; @@ -903,10 +906,11 @@ - (void)pressesCancelled:(NSSet*)presses if (@available(iOS 13.4, *)) { for (UIPress* press in presses) { [_textInputDelegate - handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press withEvent:event] - nextAction:^() { - [super pressesCancelled:[NSSet setWithObject:press] withEvent:event]; - }]; + flutterTextInputView:self + handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press withEvent:event] + nextAction:^() { + [super pressesCancelled:[NSSet setWithObject:press] withEvent:event]; + }]; } } else { [super pressesCancelled:presses withEvent:event]; From b1232469419e2441ecac4d02d41a9c4f4c426631 Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Tue, 20 Jul 2021 11:51:31 -0500 Subject: [PATCH 31/52] Refactor out FlutterViewResponder protocol, swap focused/focusing bools to use new FlutterScribbleStatus enum --- .../framework/Source/FlutterTextInputPlugin.h | 16 +++++++++----- .../Source/FlutterTextInputPlugin.mm | 22 +++++++++---------- .../Source/FlutterViewController_Internal.h | 3 ++- .../framework/Source/FlutterViewResponder.h | 22 +++++++++++++++++++ 4 files changed, 46 insertions(+), 17 deletions(-) create mode 100644 shell/platform/darwin/ios/framework/Source/FlutterViewResponder.h diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h index 16b87ccf72cf8..1b552d4cb1f13 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h @@ -11,12 +11,19 @@ #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterIndirectScribbleDelegate.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterKeySecondaryResponder.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewResponder.h" + +typedef NS_ENUM(NSInteger, FlutterScribbleStatus) { + FlutterScribbleStatusUnfocused, + FlutterScribbleStatusFocusing, + FlutterScribbleStatusFocused, +}; @interface FlutterTextInputPlugin : NSObject @property(nonatomic, assign) id textInputDelegate; @property(nonatomic, assign) id indirectScribbleDelegate; -@property(nonatomic, readonly) UIViewController* viewController; +@property(nonatomic, readonly) id viewController; @property(nonatomic, assign) NSMutableDictionary* scribbleElements; - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result; @@ -33,7 +40,7 @@ * These are used by the UIIndirectScribbleInteractionDelegate methods to handle focusing on the * correct element */ -- (void)setupIndirectScribbleInteraction:(UIViewController*)viewController; +- (void)setupIndirectScribbleInteraction:(id)viewController; - (void)resetViewController; @end @@ -117,9 +124,8 @@ FLUTTER_DARWIN_EXPORT @property(nonatomic, assign) UIAccessibilityElement* backingTextInputAccessibilityObject; // Scribble Support -@property(nonatomic, assign) UIViewController* viewController; -@property(nonatomic) BOOL scribbleFocusing; -@property(nonatomic) BOOL scribbleFocused; +@property(nonatomic, assign) id viewController; +@property(nonatomic) FlutterScribbleStatus scribbleFocusStatus; @property(nonatomic, strong) NSArray*>* selectionRects; @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 66c06e3a2bced..7cc5905bf5542 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -819,7 +819,7 @@ - (void)setTextInputState:(NSDictionary*)state { // Forward touches to the viewController to allow tapping inside the UITextField as normal - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { - _scribbleFocused = NO; + _scribbleFocusStatus = FlutterScribbleStatusUnfocused; [self.viewController touchesBegan:touches withEvent:event]; } @@ -1019,7 +1019,7 @@ - (void)setSelectedTextRangeLocal:(UITextRange*)selectedTextRange { - (void)setSelectedTextRange:(UITextRange*)selectedTextRange { [self setSelectedTextRangeLocal:selectedTextRange]; [self updateEditingState]; - if (_scribbleInProgress || _scribbleFocused) { + if (_scribbleInProgress || _scribbleFocusStatus == FlutterScribbleStatusFocused) { NSAssert([selectedTextRange isKindOfClass:[FlutterTextRange class]], @"Expected a FlutterTextRange for range (got %@).", [selectedTextRange class]); FlutterTextRange* flutterTextRange = (FlutterTextRange*)selectedTextRange; @@ -1138,7 +1138,7 @@ - (void)setMarkedText:(NSString*)markedText selectedRange:(NSRange)markedSelecte NSRange selectedRange = _selectedTextRange.range; NSRange markedTextRange = ((FlutterTextRange*)self.markedTextRange).range; - if (_scribbleInProgress || _scribbleFocusing || _scribbleFocused) + if (_scribbleInProgress || _scribbleFocusStatus != FlutterScribbleStatusUnfocused) return; if (markedText == nil) @@ -1375,7 +1375,7 @@ - (CGRect)firstRectForRange:(UITextRange*)range { return _cachedFirstRect; } - if (!_scribbleInProgress && !_scribbleFocusing && !_scribbleFocused) { + if (!_scribbleInProgress && _scribbleFocusStatus == FlutterScribbleStatusUnfocused) { [_textInputDelegate flutterTextInputView:self showAutocorrectionPromptRectForStart:start end:end @@ -1648,7 +1648,7 @@ - (void)insertText:(NSString*)text { } } - _scribbleFocused = NO; + _scribbleFocusStatus = FlutterScribbleStatusUnfocused; _selectionRects = copiedRects; _selectionAffinity = _kTextAffinityDownstream; [self replaceRange:_selectedTextRange withText:text]; @@ -1669,7 +1669,7 @@ - (void)removeTextPlaceholder:(UITextPlaceholder*)textPlaceholder API_AVAILABLE( - (void)deleteBackward { _selectionAffinity = _kTextAffinityDownstream; - _scribbleFocused = NO; + _scribbleFocusStatus = FlutterScribbleStatusUnfocused; // When deleting Thai vowel, _selectedTextRange has location // but does not have length, so we have to manually set it. @@ -2180,7 +2180,7 @@ - (void)clearTextInputClient { - (BOOL)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction isElementFocused:(UIScribbleElementIdentifier)elementIdentifier API_AVAILABLE(ios(14.0)) { - return _activeView.scribbleFocused; + return _activeView.scribbleFocusStatus == FlutterScribbleStatusFocused; } - (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction @@ -2188,13 +2188,13 @@ - (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction referencePoint:(CGPoint)focusReferencePoint completion:(void (^)(UIResponder* focusedInput))completion API_AVAILABLE(ios(14.0)) { - _activeView.scribbleFocusing = YES; + _activeView.scribbleFocusStatus = FlutterScribbleStatusFocusing; [_indirectScribbleDelegate flutterTextInputPlugin:self focusElement:elementIdentifier atPoint:focusReferencePoint result:^(id _Nullable result) { - _activeView.scribbleFocusing = NO; - _activeView.scribbleFocused = YES; + _activeView.scribbleFocusStatus = + FlutterScribbleStatusFocused; completion(_activeView); }]; } @@ -2255,7 +2255,7 @@ - (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction #pragma mark - Methods related to Scribble support -- (void)setupIndirectScribbleInteraction:(UIViewController*)viewController { +- (void)setupIndirectScribbleInteraction:(id)viewController { if (_viewController != viewController) { if (@available(iOS 14.0, *)) { UIView* parentView = viewController.view; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h index b6b655026b463..16febb55f99ac 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h @@ -12,6 +12,7 @@ #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterKeyboardManager.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterRestorationPlugin.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterUIPressProxy.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewResponder.h" namespace flutter { class FlutterPlatformViewsController; @@ -26,7 +27,7 @@ extern NSNotificationName const FlutterViewControllerHideHomeIndicator; FLUTTER_DARWIN_EXPORT extern NSNotificationName const FlutterViewControllerShowHomeIndicator; -@interface FlutterViewController () +@interface FlutterViewController () @property(nonatomic, readonly) BOOL isPresentingViewController; @property(nonatomic, readonly) BOOL isVoiceOverRunning; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewResponder.h b/shell/platform/darwin/ios/framework/Source/FlutterViewResponder.h new file mode 100644 index 0000000000000..d3062d51075bd --- /dev/null +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewResponder.h @@ -0,0 +1,22 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERVIEWRESPONDER_H_ +#define SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERVIEWRESPONDER_H_ + +#import + +@protocol FlutterViewResponder + +@property(nonatomic, strong) UIView *view; + +- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event; +- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event; +- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event; +- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event; +- (void)touchesEstimatedPropertiesUpdated:(NSSet*)touches; + +@end + +#endif // SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERVIEWRESPONDER_H_ From 46bacea4eec11c1c9560ced2d9f136bfcbe15c48 Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Tue, 20 Jul 2021 13:21:31 -0500 Subject: [PATCH 32/52] Add position member to FlutterTextSelectionRect and use that to represent selection rects from the framework rather than keeping an array of arrays of numbers --- .../framework/Source/FlutterTextInputPlugin.h | 7 +- .../Source/FlutterTextInputPlugin.mm | 80 +++++++++---------- 2 files changed, 45 insertions(+), 42 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h index 1b552d4cb1f13..e350b1c710444 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h @@ -71,18 +71,23 @@ typedef NS_ENUM(NSInteger, FlutterScribbleStatus) { @interface FlutterTextSelectionRect : UITextSelectionRect @property(nonatomic, assign) CGRect rect; +@property(nonatomic) NSUInteger position; @property(nonatomic, assign) NSWritingDirection writingDirection; @property(nonatomic) BOOL containsStart; @property(nonatomic) BOOL containsEnd; @property(nonatomic) BOOL isVertical; + (instancetype)selectionRectWithRectAndInfo:(CGRect)rect + position:(NSUInteger)position writingDirection:(NSWritingDirection)writingDirection containsStart:(BOOL)containsStart containsEnd:(BOOL)containsEnd isVertical:(BOOL)isVertical; ++ (instancetype)selectionRectWithRect:(CGRect)rect position:(NSUInteger)position; + - (instancetype)initWithRectAndInfo:(CGRect)rect + position:(NSUInteger)position writingDirection:(NSWritingDirection)writingDirection containsStart:(BOOL)containsStart containsEnd:(BOOL)containsEnd @@ -126,7 +131,7 @@ FLUTTER_DARWIN_EXPORT // Scribble Support @property(nonatomic, assign) id viewController; @property(nonatomic) FlutterScribbleStatus scribbleFocusStatus; -@property(nonatomic, strong) NSArray*>* selectionRects; +@property(nonatomic, strong) NSArray* selectionRects; @end #endif // SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERTEXTINPUTPLUGIN_H_ diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 7cc5905bf5542..6fb909a5a26fd 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -516,18 +516,30 @@ @implementation FlutterTextSelectionRect @synthesize isVertical = _isVertical; + (instancetype)selectionRectWithRectAndInfo:(CGRect)rect + position:(NSUInteger)position writingDirection:(NSWritingDirection)writingDirection containsStart:(BOOL)containsStart containsEnd:(BOOL)containsEnd isVertical:(BOOL)isVertical { return [[[FlutterTextSelectionRect alloc] initWithRectAndInfo:rect + position:position writingDirection:writingDirection containsStart:containsStart containsEnd:containsEnd isVertical:isVertical] autorelease]; } ++ (instancetype)selectionRectWithRect:(CGRect)rect position:(NSUInteger)position { + return [[[FlutterTextSelectionRect alloc] initWithRectAndInfo:rect + position:position + writingDirection:UITextWritingDirectionNatural + containsStart:NO + containsEnd:NO + isVertical:NO] autorelease]; +} + - (instancetype)initWithRectAndInfo:(CGRect)rect + position:(NSUInteger)position writingDirection:(NSWritingDirection)writingDirection containsStart:(BOOL)containsStart containsEnd:(BOOL)containsEnd @@ -535,6 +547,7 @@ - (instancetype)initWithRectAndInfo:(CGRect)rect self = [super init]; if (self) { self.rect = rect; + self.position = position; self.writingDirection = writingDirection; self.containsStart = containsStart; self.containsEnd = containsEnd; @@ -1389,17 +1402,14 @@ - (CGRect)firstRectForRange:(UITextRange*)range { FlutterTextRange* textRange = [FlutterTextRange rangeWithNSRange:fml::RangeForCharactersInRange(self.text, NSMakeRange(0, self.text.length))]; for (NSUInteger i = 0; i < [_selectionRects count]; i++) { - BOOL startsOnOrBeforeStartOfRange = [_selectionRects[i][4] unsignedIntegerValue] <= first; + BOOL startsOnOrBeforeStartOfRange = _selectionRects[i].position <= first; BOOL isLastSelectionRect = i + 1 == [_selectionRects count]; BOOL endOfTextIsAfterStartOfRange = isLastSelectionRect && textRange.range.length > first; BOOL nextSelectionRectIsAfterStartOfRange = - !isLastSelectionRect && [_selectionRects[i + 1][4] unsignedIntegerValue] > first; + !isLastSelectionRect && _selectionRects[i + 1].position > first; if (startsOnOrBeforeStartOfRange && (endOfTextIsAfterStartOfRange || nextSelectionRectIsAfterStartOfRange)) { - CGRect rect = - CGRectMake([_selectionRects[i][0] floatValue], [_selectionRects[i][1] floatValue], - [_selectionRects[i][2] floatValue], [_selectionRects[i][3] floatValue]); - return rect; + return _selectionRects[i].rect; } } @@ -1448,17 +1458,16 @@ - (NSArray*)selectionRectsForRange:(UITextRange*)range { NSUInteger end = ((FlutterTextPosition*)range.end).index; NSMutableArray* rects = [[[NSMutableArray alloc] init] autorelease]; for (NSUInteger i = 0; i < [_selectionRects count]; i++) { - if ([_selectionRects[i][4] unsignedIntegerValue] >= start && - [_selectionRects[i][4] unsignedIntegerValue] <= end) { - float width = [_selectionRects[i][2] floatValue]; + if (_selectionRects[i].position >= start && _selectionRects[i].position <= end) { + float width = _selectionRects[i].rect.size.width; if (start == end) { width = 0; } - CGRect rect = - CGRectMake([_selectionRects[i][0] floatValue], [_selectionRects[i][1] floatValue], width, - [_selectionRects[i][3] floatValue]); + CGRect rect = CGRectMake(_selectionRects[i].rect.origin.x, _selectionRects[i].rect.origin.y, + width, _selectionRects[i].rect.size.height); FlutterTextSelectionRect* selectionRect = [FlutterTextSelectionRect selectionRectWithRectAndInfo:rect + position:_selectionRects[i].position writingDirection:UITextWritingDirectionNatural containsStart:(i == 0) containsEnd:(i == fml::RangeForCharactersInRange( @@ -1483,15 +1492,12 @@ - (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRang CGRect _closestRect = CGRectZero; NSUInteger _closestPosition = 0; for (NSUInteger i = 0; i < [_selectionRects count]; i++) { - NSUInteger position = [_selectionRects[i][4] unsignedIntegerValue]; + NSUInteger position = _selectionRects[i].position; if (position >= start && position <= end) { - CGRect rect = - CGRectMake([_selectionRects[i][0] floatValue], [_selectionRects[i][1] floatValue], - [_selectionRects[i][2] floatValue], [_selectionRects[i][3] floatValue]); BOOL isFirst = _closestIndex == 0; - if (isFirst || isPositionCloserToPoint(point, rect, _closestRect, NO)) { + if (isFirst || isPositionCloserToPoint(point, _selectionRects[i].rect, _closestRect, NO)) { _closestIndex = i; - _closestRect = rect; + _closestRect = _selectionRects[i].rect; _closestPosition = position; } } @@ -1502,12 +1508,9 @@ - (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRang if ([_selectionRects count] > 0 && textRange.range.length == end) { NSUInteger i = [_selectionRects count] - 1; - NSUInteger position = [_selectionRects[i][4] unsignedIntegerValue] + 1; + NSUInteger position = _selectionRects[i].position + 1; if (position <= end) { - CGRect rect = - CGRectMake([_selectionRects[i][0] floatValue], [_selectionRects[i][1] floatValue], - [_selectionRects[i][2] floatValue], [_selectionRects[i][3] floatValue]); - if (isPositionCloserToPoint(point, rect, _closestRect, YES)) { + if (isPositionCloserToPoint(point, _selectionRects[i].rect, _closestRect, YES)) { _closestIndex = [_selectionRects count]; _closestPosition = position; } @@ -1614,7 +1617,7 @@ - (BOOL)hasText { } - (void)insertText:(NSString*)text { - NSMutableArray*>* copiedRects = + NSMutableArray* copiedRects = [[NSMutableArray alloc] initWithCapacity:[_selectionRects count]]; NSAssert([_selectedTextRange.start isKindOfClass:[FlutterTextPosition class]], @"Expected a FlutterTextPosition for position (got %@).", @@ -1622,29 +1625,20 @@ - (void)insertText:(NSString*)text { NSUInteger insertPosition = ((FlutterTextPosition*)_selectedTextRange.start).index - 1; NSUInteger insertIndex = 0; for (NSUInteger i = 0; i < [_selectionRects count]; i++) { - NSUInteger rectPosition = [_selectionRects[i][4] unsignedIntegerValue]; + NSUInteger rectPosition = _selectionRects[i].position; if (rectPosition == insertPosition) { insertIndex = i; for (NSUInteger j = 0; j <= text.length; j++) { - [copiedRects addObject:@[ - _selectionRects[i][0], - _selectionRects[i][1], - _selectionRects[i][2], - _selectionRects[i][3], - [NSNumber numberWithInt:rectPosition + j], - ]]; + [copiedRects + addObject:[FlutterTextSelectionRect selectionRectWithRect:_selectionRects[i].rect + position:rectPosition + j]]; } } else { if (rectPosition > insertPosition) { rectPosition = rectPosition + text.length; } - [copiedRects addObject:@[ - _selectionRects[i][0], - _selectionRects[i][1], - _selectionRects[i][2], - _selectionRects[i][3], - [NSNumber numberWithInt:rectPosition], - ]]; + [copiedRects addObject:[FlutterTextSelectionRect selectionRectWithRect:_selectionRects[i].rect + position:rectPosition]]; } } @@ -1885,11 +1879,15 @@ - (void)updateMarkedRect:(NSDictionary*)dictionary { } - (void)setSelectionRects:(NSArray*)rects { - NSMutableArray*>* rectsAsRect = + NSMutableArray* rectsAsRect = [[[NSMutableArray alloc] initWithCapacity:[rects count]] autorelease]; for (NSUInteger i = 0; i < [rects count]; i++) { NSArray* rect = rects[i]; - [rectsAsRect addObject:rect]; + [rectsAsRect + addObject:[FlutterTextSelectionRect + selectionRectWithRect:CGRectMake([rect[0] floatValue], [rect[1] floatValue], + [rect[2] floatValue], [rect[3] floatValue]) + position:[rect[4] unsignedIntegerValue]]]; } _activeView.selectionRects = rectsAsRect; } From a622e839d24aca5d294b4ed5709178b771001d13 Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Tue, 20 Jul 2021 13:28:11 -0500 Subject: [PATCH 33/52] Fix linter errors --- .../darwin/ios/framework/Source/FlutterTextInputPlugin.h | 3 ++- .../darwin/ios/framework/Source/FlutterViewResponder.h | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h index e350b1c710444..3e5ca618c0583 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h @@ -19,7 +19,8 @@ typedef NS_ENUM(NSInteger, FlutterScribbleStatus) { FlutterScribbleStatusFocused, }; -@interface FlutterTextInputPlugin : NSObject +@interface FlutterTextInputPlugin + : NSObject @property(nonatomic, assign) id textInputDelegate; @property(nonatomic, assign) id indirectScribbleDelegate; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewResponder.h b/shell/platform/darwin/ios/framework/Source/FlutterViewResponder.h index d3062d51075bd..0ce80d25f755c 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewResponder.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewResponder.h @@ -9,7 +9,7 @@ @protocol FlutterViewResponder -@property(nonatomic, strong) UIView *view; +@property(nonatomic, strong) UIView* view; - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event; - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event; From 640267ebc704f6bdae271dcbb1dbd30dcaf620c1 Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Thu, 22 Jul 2021 13:14:55 -0500 Subject: [PATCH 34/52] Fix tests to use new selection rects and delegate style --- .../Source/FlutterTextInputPluginTest.mm | 196 ++++++++++-------- 1 file changed, 105 insertions(+), 91 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm index 1b2859e81b65e..409e514755768 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm @@ -12,6 +12,17 @@ FLUTTER_ASSERT_ARC +// TODO(fbcouch) +// - tests for closestPositionToPoint +// - setMarkedText does nothing when scribble in progress +// - setMarkedText does nothing when scribble focus in progress +// - positionFromPosition returns early when scribble in progress +// - firstRectForRange only shows autocorrect prompt rect if not scribbling +// - firstRectForRange logic tests +// - closestPositionToPoint logic tests +// - selectionRectsForRange logic tests +// - insertText adds placeholder selection rects + @interface FlutterTextInputView () @property(nonatomic, copy) NSString* autofillId; - (void)setEditableTransform:(NSArray*)matrix; @@ -213,7 +224,10 @@ - (void)testAutocorrectionPromptRectAppears { [inputView firstRectForRange:[FlutterTextRange rangeWithNSRange:NSMakeRange(0, 1)]]; // Verify behavior. - OCMVerify([engine showAutocorrectionPromptRectForStart:0 end:1 withClient:0]); + OCMVerify([engine flutterTextInputView:inputView + showAutocorrectionPromptRectForStart:0 + end:1 + withClient:0]); } - (void)testTextRangeFromPositionMatchesUITextViewBehavior { @@ -311,7 +325,7 @@ - (void)testUITextInputCallsUpdateEditingStateOnce { inputView.textInputDelegate = engine; __block int updateCount = 0; - OCMStub([engine updateEditingClient:0 withState:[OCMArg isNotNil]]) + OCMStub([engine flutterTextInputView:inputView updateEditingClient:0 withState:[OCMArg isNotNil]]) .andDo(^(NSInvocation* invocation) { updateCount++; }); @@ -342,7 +356,7 @@ - (void)testTextChangesDoNotTriggerUpdateEditingClient { inputView.textInputDelegate = engine; __block int updateCount = 0; - OCMStub([engine updateEditingClient:0 withState:[OCMArg isNotNil]]) + OCMStub([engine flutterTextInputView:inputView updateEditingClient:0 withState:[OCMArg isNotNil]]) .andDo(^(NSInvocation* invocation) { updateCount++; }); @@ -383,7 +397,7 @@ - (void)testUITextInputAvoidUnnecessaryUndateEditingClientCalls { inputView.textInputDelegate = engine; __block int updateCount = 0; - OCMStub([engine updateEditingClient:0 withState:[OCMArg isNotNil]]) + OCMStub([engine flutterTextInputView:inputView updateEditingClient:0 withState:[OCMArg isNotNil]]) .andDo(^(NSInvocation* invocation) { updateCount++; }); @@ -415,30 +429,33 @@ - (void)testUpdateEditingClientNegativeSelection { @"selectionExtent" : @-1 }]; [inputView updateEditingState]; - OCMVerify([engine updateEditingClient:0 - withState:[OCMArg checkWithBlock:^BOOL(NSDictionary* state) { - return ([state[@"selectionBase"] intValue]) == 0 && - ([state[@"selectionExtent"] intValue] == 0); - }]]); + OCMVerify([engine flutterTextInputView:inputView + updateEditingClient:0 + withState:[OCMArg checkWithBlock:^BOOL(NSDictionary* state) { + return ([state[@"selectionBase"] intValue]) == 0 && + ([state[@"selectionExtent"] intValue] == 0); + }]]); // Returns (0, 0) when either end goes below 0. [inputView setTextInputState:@{@"text" : @"SELECTION", @"selectionBase" : @-1, @"selectionExtent" : @1}]; [inputView updateEditingState]; - OCMVerify([engine updateEditingClient:0 - withState:[OCMArg checkWithBlock:^BOOL(NSDictionary* state) { - return ([state[@"selectionBase"] intValue]) == 0 && - ([state[@"selectionExtent"] intValue] == 0); - }]]); + OCMVerify([engine flutterTextInputView:inputView + updateEditingClient:0 + withState:[OCMArg checkWithBlock:^BOOL(NSDictionary* state) { + return ([state[@"selectionBase"] intValue]) == 0 && + ([state[@"selectionExtent"] intValue] == 0); + }]]); [inputView setTextInputState:@{@"text" : @"SELECTION", @"selectionBase" : @1, @"selectionExtent" : @-1}]; [inputView updateEditingState]; - OCMVerify([engine updateEditingClient:0 - withState:[OCMArg checkWithBlock:^BOOL(NSDictionary* state) { - return ([state[@"selectionBase"] intValue]) == 0 && - ([state[@"selectionExtent"] intValue] == 0); - }]]); + OCMVerify([engine flutterTextInputView:inputView + updateEditingClient:0 + withState:[OCMArg checkWithBlock:^BOOL(NSDictionary* state) { + return ([state[@"selectionBase"] intValue]) == 0 && + ([state[@"selectionExtent"] intValue] == 0); + }]]); } - (void)testUpdateEditingClientSelectionClamping { @@ -453,11 +470,12 @@ - (void)testUpdateEditingClientSelectionClamping { [inputView setTextInputState:@{@"text" : @"SELECTION", @"selectionBase" : @0, @"selectionExtent" : @0}]; [inputView updateEditingState]; - OCMVerify([engine updateEditingClient:0 - withState:[OCMArg checkWithBlock:^BOOL(NSDictionary* state) { - return ([state[@"selectionBase"] intValue]) == 0 && - ([state[@"selectionExtent"] intValue] == 0); - }]]); + OCMVerify([engine flutterTextInputView:inputView + updateEditingClient:0 + withState:[OCMArg checkWithBlock:^BOOL(NSDictionary* state) { + return ([state[@"selectionBase"] intValue]) == 0 && + ([state[@"selectionExtent"] intValue] == 0); + }]]); // Needs clamping. [inputView setTextInputState:@{ @@ -467,21 +485,23 @@ - (void)testUpdateEditingClientSelectionClamping { }]; [inputView updateEditingState]; - OCMVerify([engine updateEditingClient:0 - withState:[OCMArg checkWithBlock:^BOOL(NSDictionary* state) { - return ([state[@"selectionBase"] intValue]) == 0 && - ([state[@"selectionExtent"] intValue] == 9); - }]]); + OCMVerify([engine flutterTextInputView:inputView + updateEditingClient:0 + withState:[OCMArg checkWithBlock:^BOOL(NSDictionary* state) { + return ([state[@"selectionBase"] intValue]) == 0 && + ([state[@"selectionExtent"] intValue] == 9); + }]]); // No clamping needed, but in reverse direction. [inputView setTextInputState:@{@"text" : @"SELECTION", @"selectionBase" : @1, @"selectionExtent" : @0}]; [inputView updateEditingState]; - OCMVerify([engine updateEditingClient:0 - withState:[OCMArg checkWithBlock:^BOOL(NSDictionary* state) { - return ([state[@"selectionBase"] intValue]) == 0 && - ([state[@"selectionExtent"] intValue] == 1); - }]]); + OCMVerify([engine flutterTextInputView:inputView + updateEditingClient:0 + withState:[OCMArg checkWithBlock:^BOOL(NSDictionary* state) { + return ([state[@"selectionBase"] intValue]) == 0 && + ([state[@"selectionExtent"] intValue] == 1); + }]]); // Both ends need clamping. [inputView setTextInputState:@{ @@ -490,11 +510,12 @@ - (void)testUpdateEditingClientSelectionClamping { @"selectionExtent" : @9999 }]; [inputView updateEditingState]; - OCMVerify([engine updateEditingClient:0 - withState:[OCMArg checkWithBlock:^BOOL(NSDictionary* state) { - return ([state[@"selectionBase"] intValue]) == 9 && - ([state[@"selectionExtent"] intValue] == 9); - }]]); + OCMVerify([engine flutterTextInputView:inputView + updateEditingClient:0 + withState:[OCMArg checkWithBlock:^BOOL(NSDictionary* state) { + return ([state[@"selectionBase"] intValue]) == 9 && + ([state[@"selectionExtent"] intValue] == 9); + }]]); } #pragma mark - UITextInput methods - Tests @@ -551,14 +572,10 @@ - (void)testFirstRectForRangeReturnsCorrectSelectionRect { FlutterTextRange* range = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 1)]; CGRect testRect = CGRectMake(100, 100, 100, 100); [inputView setSelectionRects:@[ - @[ @0, @0, @100, @100, @0 ], - @[ - @(testRect.origin.x), @(testRect.origin.y), @(testRect.size.width), @(testRect.size.height), - @1 - ], - @[ @200, @200, @100, @100, @2 ] + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 0, 100, 100) position:0U], + [FlutterTextSelectionRect selectionRectWithRect:testRect position:1U], + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(200, 200, 100, 100) position:2U], ]]; - NSLog(@"testRect: %@, firstRect: %@", @(testRect), @([inputView firstRectForRange:range])); XCTAssertTrue(CGRectEqualToRect(testRect, [inputView firstRectForRange:range])); [inputView setTextInputState:@{@"text" : @"COM"}]; @@ -572,46 +589,46 @@ - (void)testClosestPositionToPoint { // Minimize the vertical distance from the center of the rects first [inputView setSelectionRects:@[ - @[ @0, @0, @100, @100, @0 ], - @[ @0, @100, @100, @100, @1 ], - @[ @0, @200, @100, @100, @2 ], + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 0, 100, 100) position:0U], + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 100, 100, 100) position:1U], + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 200, 100, 100) position:2U], ]]; CGPoint point = CGPointMake(150, 150); - XCTAssertEqual(1, ((FlutterTextPosition*)[inputView closestPositionToPoint:point]).index); + XCTAssertEqual(1U, ((FlutterTextPosition*)[inputView closestPositionToPoint:point]).index); // Then, if the point is above the bottom of the closest rects vertically, get the closest x // origin [inputView setSelectionRects:@[ - @[ @0, @0, @100, @100, @0 ], - @[ @0, @100, @100, @100, @1 ], - @[ @100, @100, @100, @100, @2 ], - @[ @200, @100, @100, @100, @3 ], - @[ @0, @200, @100, @100, @4 ], + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 0, 100, 100) position:0U], + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 100, 100, 100) position:1U], + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(100, 100, 100, 100) position:2U], + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(200, 100, 100, 100) position:3U], + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 200, 100, 100) position:4U], ]]; point = CGPointMake(125, 150); - XCTAssertEqual(2, ((FlutterTextPosition*)[inputView closestPositionToPoint:point]).index); + XCTAssertEqual(2U, ((FlutterTextPosition*)[inputView closestPositionToPoint:point]).index); // However, if the point is below the bottom of the closest rects vertically, get the position // farthest to the right [inputView setSelectionRects:@[ - @[ @0, @0, @100, @100, @0 ], - @[ @0, @100, @100, @100, @1 ], - @[ @100, @100, @100, @100, @2 ], - @[ @200, @100, @100, @100, @3 ], - @[ @0, @300, @100, @100, @4 ], + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 0, 100, 100) position:0U], + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 100, 100, 100) position:1U], + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(100, 100, 100, 100) position:2U], + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(200, 100, 100, 100) position:3U], + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 300, 100, 100) position:4U], ]]; point = CGPointMake(125, 201); - XCTAssertEqual(3, ((FlutterTextPosition*)[inputView closestPositionToPoint:point]).index); + XCTAssertEqual(3U, ((FlutterTextPosition*)[inputView closestPositionToPoint:point]).index); // Also check a point at the right edge of the last selection rect [inputView setSelectionRects:@[ - @[ @0, @0, @100, @100, @0 ], - @[ @0, @100, @100, @100, @1 ], - @[ @100, @100, @100, @100, @2 ], - @[ @200, @100, @100, @100, @3 ], + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 0, 100, 100) position:0U], + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 100, 100, 100) position:1U], + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(100, 100, 100, 100) position:2U], + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(200, 100, 100, 100) position:3U], ]]; point = CGPointMake(125, 250); - XCTAssertEqual(4, ((FlutterTextPosition*)[inputView closestPositionToPoint:point]).index); + XCTAssertEqual(4U, ((FlutterTextPosition*)[inputView closestPositionToPoint:point]).index); } - (void)testSelectionRectsForRange { @@ -623,20 +640,14 @@ - (void)testSelectionRectsForRange { CGRect testRect0 = CGRectMake(100, 100, 100, 100); CGRect testRect1 = CGRectMake(200, 200, 100, 100); [inputView setSelectionRects:@[ - @[ @0, @0, @100, @100, @0 ], - @[ - @(testRect0.origin.x), @(testRect0.origin.y), @(testRect0.size.width), - @(testRect0.size.height), @1 - ], - @[ - @(testRect1.origin.x), @(testRect1.origin.y), @(testRect1.size.width), - @(testRect1.size.height), @2 - ], - @[ @300, @300, @100, @100, @3 ] + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 0, 100, 100) position:0U], + [FlutterTextSelectionRect selectionRectWithRect:testRect0 position:1U], + [FlutterTextSelectionRect selectionRectWithRect:testRect1 position:2U], + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(300, 300, 100, 100) position:3U], ]]; XCTAssertTrue(CGRectEqualToRect(testRect0, [inputView selectionRectsForRange:range][0].rect)); XCTAssertTrue(CGRectEqualToRect(testRect1, [inputView selectionRectsForRange:range][1].rect)); - XCTAssertEqual(2, [[inputView selectionRectsForRange:range] count]); + XCTAssertEqual(2U, [[inputView selectionRectsForRange:range] count]); } - (void)testClosestPositionToPointWithinRange { @@ -645,29 +656,29 @@ - (void)testClosestPositionToPointWithinRange { // Do not return a position before the start of the range [inputView setSelectionRects:@[ - @[ @0, @0, @100, @100, @0 ], - @[ @0, @100, @100, @100, @1 ], - @[ @100, @100, @100, @100, @2 ], - @[ @200, @100, @100, @100, @3 ], - @[ @0, @200, @100, @100, @4 ], + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 0, 100, 100) position:0U], + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 100, 100, 100) position:1U], + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(100, 100, 100, 100) position:2U], + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(200, 100, 100, 100) position:3U], + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 200, 100, 100) position:4U], ]]; CGPoint point = CGPointMake(125, 150); FlutterTextRange* range = [[FlutterTextRange rangeWithNSRange:NSMakeRange(3, 2)] copy]; XCTAssertEqual( - 3, ((FlutterTextPosition*)[inputView closestPositionToPoint:point withinRange:range]).index); + 3U, ((FlutterTextPosition*)[inputView closestPositionToPoint:point withinRange:range]).index); // Do not return a position after the end of the range [inputView setSelectionRects:@[ - @[ @0, @0, @100, @100, @0 ], - @[ @0, @100, @100, @100, @1 ], - @[ @100, @100, @100, @100, @2 ], - @[ @200, @100, @100, @100, @3 ], - @[ @0, @200, @100, @100, @4 ], + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 0, 100, 100) position:0U], + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 100, 100, 100) position:1U], + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(100, 100, 100, 100) position:2U], + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(200, 100, 100, 100) position:3U], + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 200, 100, 100) position:4U], ]]; point = CGPointMake(125, 150); range = [[FlutterTextRange rangeWithNSRange:NSMakeRange(0, 1)] copy]; XCTAssertEqual( - 1, ((FlutterTextPosition*)[inputView closestPositionToPoint:point withinRange:range]).index); + 1U, ((FlutterTextPosition*)[inputView closestPositionToPoint:point withinRange:range]).index); } #pragma mark - Floating Cursor - Tests @@ -962,7 +973,10 @@ - (void)testAutofillInputViews { [self ensureOnlyActiveViewCanBecomeFirstResponder]; // Verify behavior. - OCMVerify([engine updateEditingClient:0 withState:[OCMArg isNotNil] withTag:@"field2"]); + OCMVerify([engine flutterTextInputView:inactiveView + updateEditingClient:0 + withState:[OCMArg isNotNil] + withTag:@"field2"]); } - (void)testPasswordAutofillHack { From f2d67b57fb0ebbce22d5b19196f4c021decbac4a Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Sun, 25 Jul 2021 20:57:16 -0500 Subject: [PATCH 35/52] Add some more tests for scribble functions --- .../Source/FlutterTextInputPlugin.mm | 4 +- .../Source/FlutterTextInputPluginTest.mm | 169 ++++++++++++++++-- 2 files changed, 157 insertions(+), 16 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 6fb909a5a26fd..4d84748570520 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -1622,12 +1622,10 @@ - (void)insertText:(NSString*)text { NSAssert([_selectedTextRange.start isKindOfClass:[FlutterTextPosition class]], @"Expected a FlutterTextPosition for position (got %@).", [_selectedTextRange.start class]); - NSUInteger insertPosition = ((FlutterTextPosition*)_selectedTextRange.start).index - 1; - NSUInteger insertIndex = 0; + NSUInteger insertPosition = ((FlutterTextPosition*)_selectedTextRange.start).index; for (NSUInteger i = 0; i < [_selectionRects count]; i++) { NSUInteger rectPosition = _selectionRects[i].position; if (rectPosition == insertPosition) { - insertIndex = i; for (NSUInteger j = 0; j <= text.length; j++) { [copiedRects addObject:[FlutterTextSelectionRect selectionRectWithRect:_selectionRects[i].rect diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm index 409e514755768..9cb9c158d7f2d 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm @@ -12,17 +12,6 @@ FLUTTER_ASSERT_ARC -// TODO(fbcouch) -// - tests for closestPositionToPoint -// - setMarkedText does nothing when scribble in progress -// - setMarkedText does nothing when scribble focus in progress -// - positionFromPosition returns early when scribble in progress -// - firstRectForRange only shows autocorrect prompt rect if not scribbling -// - firstRectForRange logic tests -// - closestPositionToPoint logic tests -// - selectionRectsForRange logic tests -// - insertText adds placeholder selection rects - @interface FlutterTextInputView () @property(nonatomic, copy) NSString* autofillId; - (void)setEditableTransform:(NSArray*)matrix; @@ -230,6 +219,57 @@ - (void)testAutocorrectionPromptRectAppears { withClient:0]); } +- (void)testAutocorrectionPromptRectDoesNotAppearDuringScribble { + if (@available(iOS 14.0, *)) { + FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithFrame:CGRectZero]; + inputView.textInputDelegate = engine; + + __block int callCount = 0; + OCMStub([engine flutterTextInputView:inputView + showAutocorrectionPromptRectForStart:0 + end:1 + withClient:0]) + .andDo(^(NSInvocation* invocation) { + callCount++; + }); + + [inputView firstRectForRange:[FlutterTextRange rangeWithNSRange:NSMakeRange(0, 1)]]; + // showAutocorrectionPromptRectForStart fires in response to firstRectForRange + XCTAssertEqual(callCount, 1); + + UIScribbleInteraction* scribbleInteraction = + [[UIScribbleInteraction alloc] initWithDelegate:inputView]; + + [inputView scribbleInteractionWillBeginWriting:scribbleInteraction]; + [inputView firstRectForRange:[FlutterTextRange rangeWithNSRange:NSMakeRange(0, 1)]]; + // showAutocorrectionPromptRectForStart does not fire in response to setMarkedText during a + // scribble interaction.firstRectForRange + XCTAssertEqual(callCount, 1); + + [inputView scribbleInteractionDidFinishWriting:scribbleInteraction]; + [inputView firstRectForRange:[FlutterTextRange rangeWithNSRange:NSMakeRange(0, 1)]]; + // showAutocorrectionPromptRectForStart fires in response to firstRectForRange. + XCTAssertEqual(callCount, 2); + + inputView.scribbleFocusStatus = FlutterScribbleStatusFocusing; + [inputView firstRectForRange:[FlutterTextRange rangeWithNSRange:NSMakeRange(0, 1)]]; + // showAutocorrectionPromptRectForStart does not fire in response to firstRectForRange during a + // scribble-initiated focus. + XCTAssertEqual(callCount, 2); + + inputView.scribbleFocusStatus = FlutterScribbleStatusFocused; + [inputView firstRectForRange:[FlutterTextRange rangeWithNSRange:NSMakeRange(0, 1)]]; + // showAutocorrectionPromptRectForStart does not fire in response to firstRectForRange after a + // scribble-initiated focus. + XCTAssertEqual(callCount, 2); + + inputView.scribbleFocusStatus = FlutterScribbleStatusUnfocused; + [inputView firstRectForRange:[FlutterTextRange rangeWithNSRange:NSMakeRange(0, 1)]]; + // showAutocorrectionPromptRectForStart fires in response to firstRectForRange. + XCTAssertEqual(callCount, 3); + } +} + - (void)testTextRangeFromPositionMatchesUITextViewBehavior { FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithFrame:CGRectZero]; FlutterTextPosition* fromPosition = [[FlutterTextPosition alloc] initWithIndex:2]; @@ -415,6 +455,55 @@ - (void)testUITextInputAvoidUnnecessaryUndateEditingClientCalls { XCTAssertEqual(updateCount, 2); } +- (void)testSetMarkedTextDuringScribbleDoesNotTriggerUpdateEditingClient { + if (@available(iOS 14.0, *)) { + FlutterTextInputView* inputView = [[FlutterTextInputView alloc] init]; + inputView.textInputDelegate = engine; + + __block int updateCount = 0; + OCMStub([engine flutterTextInputView:inputView + updateEditingClient:0 + withState:[OCMArg isNotNil]]) + .andDo(^(NSInvocation* invocation) { + updateCount++; + }); + + [inputView setMarkedText:@"marked text" selectedRange:NSMakeRange(0, 1)]; + // updateEditingClient fires in response to setMarkedText. + XCTAssertEqual(updateCount, 1); + + UIScribbleInteraction* scribbleInteraction = + [[UIScribbleInteraction alloc] initWithDelegate:inputView]; + + [inputView scribbleInteractionWillBeginWriting:scribbleInteraction]; + [inputView setMarkedText:@"during writing" selectedRange:NSMakeRange(1, 2)]; + // updateEditingClient does not fire in response to setMarkedText during a scribble interaction. + XCTAssertEqual(updateCount, 1); + + [inputView scribbleInteractionDidFinishWriting:scribbleInteraction]; + [inputView setMarkedText:@"marked text" selectedRange:NSMakeRange(0, 1)]; + // updateEditingClient fires in response to setMarkedText. + XCTAssertEqual(updateCount, 2); + + inputView.scribbleFocusStatus = FlutterScribbleStatusFocusing; + [inputView setMarkedText:@"during focus" selectedRange:NSMakeRange(1, 2)]; + // updateEditingClient does not fire in response to setMarkedText during a scribble-initiated + // focus. + XCTAssertEqual(updateCount, 2); + + inputView.scribbleFocusStatus = FlutterScribbleStatusFocused; + [inputView setMarkedText:@"after focus" selectedRange:NSMakeRange(2, 3)]; + // updateEditingClient does not fire in response to setMarkedText after a scribble-initiated + // focus. + XCTAssertEqual(updateCount, 2); + + inputView.scribbleFocusStatus = FlutterScribbleStatusUnfocused; + [inputView setMarkedText:@"marked text" selectedRange:NSMakeRange(0, 1)]; + // updateEditingClient fires in response to setMarkedText. + XCTAssertEqual(updateCount, 3); + } +} + - (void)testUpdateEditingClientNegativeSelection { FlutterTextInputView* inputView = [[FlutterTextInputView alloc] init]; inputView.textInputDelegate = engine; @@ -635,8 +724,6 @@ - (void)testSelectionRectsForRange { FlutterTextInputView* inputView = [[FlutterTextInputView alloc] init]; [inputView setTextInputState:@{@"text" : @"COMPOSING"}]; - FlutterTextRange* range = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 1)]; - CGRect testRect0 = CGRectMake(100, 100, 100, 100); CGRect testRect1 = CGRectMake(200, 200, 100, 100); [inputView setSelectionRects:@[ @@ -645,9 +732,19 @@ - (void)testSelectionRectsForRange { [FlutterTextSelectionRect selectionRectWithRect:testRect1 position:2U], [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(300, 300, 100, 100) position:3U], ]]; + + // Returns the matching rects within a range + FlutterTextRange* range = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 1)]; XCTAssertTrue(CGRectEqualToRect(testRect0, [inputView selectionRectsForRange:range][0].rect)); XCTAssertTrue(CGRectEqualToRect(testRect1, [inputView selectionRectsForRange:range][1].rect)); XCTAssertEqual(2U, [[inputView selectionRectsForRange:range] count]); + + // Returns a 0 width rect for a 0-length range + range = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 0)]; + XCTAssertEqual(1U, [[inputView selectionRectsForRange:range] count]); + XCTAssertTrue(CGRectEqualToRect( + CGRectMake(testRect0.origin.x, testRect0.origin.y, 0, testRect0.size.height), + [inputView selectionRectsForRange:range][0].rect)); } - (void)testClosestPositionToPointWithinRange { @@ -728,6 +825,52 @@ - (void)testBoundsForFloatingCursor { XCTAssertTrue(CGRectEqualToRect(initialBounds, inputView.bounds)); } +#pragma mark - UIKeyInput Overrides - Tests + +- (void)testInsertTextAddsPlaceholderSelectionRects { + FlutterTextInputView* inputView = [[FlutterTextInputView alloc] init]; + [inputView + setTextInputState:@{@"text" : @"test", @"selectionBase" : @1, @"selectionExtent" : @1}]; + + FlutterTextSelectionRect* first = + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 0, 100, 100) position:0U]; + FlutterTextSelectionRect* second = + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(100, 100, 100, 100) position:1U]; + FlutterTextSelectionRect* third = + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(200, 200, 100, 100) position:2U]; + FlutterTextSelectionRect* fourth = + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(300, 300, 100, 100) position:3U]; + [inputView setSelectionRects:@[ first, second, third, fourth ]]; + + // Inserts additional selection rects at the selection start + [inputView insertText:@"in"]; + NSArray* selectionRects = + [inputView selectionRectsForRange:[FlutterTextRange rangeWithNSRange:NSMakeRange(0, 6)]]; + XCTAssertEqual(6U, [selectionRects count]); + + XCTAssertEqual(first.position, ((FlutterTextSelectionRect*)selectionRects[0]).position); + XCTAssertTrue(CGRectEqualToRect(first.rect, ((FlutterTextSelectionRect*)selectionRects[0]).rect)); + + XCTAssertEqual(second.position, ((FlutterTextSelectionRect*)selectionRects[1]).position); + XCTAssertTrue( + CGRectEqualToRect(second.rect, ((FlutterTextSelectionRect*)selectionRects[1]).rect)); + + XCTAssertEqual(second.position + 1, ((FlutterTextSelectionRect*)selectionRects[2]).position); + XCTAssertTrue( + CGRectEqualToRect(second.rect, ((FlutterTextSelectionRect*)selectionRects[2]).rect)); + + XCTAssertEqual(second.position + 2, ((FlutterTextSelectionRect*)selectionRects[3]).position); + XCTAssertTrue( + CGRectEqualToRect(second.rect, ((FlutterTextSelectionRect*)selectionRects[3]).rect)); + + XCTAssertEqual(third.position + 2, ((FlutterTextSelectionRect*)selectionRects[4]).position); + XCTAssertTrue(CGRectEqualToRect(third.rect, ((FlutterTextSelectionRect*)selectionRects[4]).rect)); + + XCTAssertEqual(fourth.position + 2, ((FlutterTextSelectionRect*)selectionRects[5]).position); + XCTAssertTrue( + CGRectEqualToRect(fourth.rect, ((FlutterTextSelectionRect*)selectionRects[5]).rect)); +} + #pragma mark - Autofill - Utilities - (NSMutableDictionary*)mutablePasswordTemplateCopy { From 7bfe2e956947e6dd4212779c7b31b9fe1d92d90e Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Sun, 25 Jul 2021 21:02:16 -0500 Subject: [PATCH 36/52] Add new headers to licenses_flutter golden file --- ci/licenses_golden/licenses_flutter | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index e9a22a5a7cb26..ecf082565e434 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1066,6 +1066,7 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterFakeKeyEvents.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterFakeKeyEvents.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterHeadlessDartRunner.mm +FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterIndirectScribbleDelegate.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterKeyPrimaryResponder.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterKeySecondaryResponder.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterKeyboardManager.h @@ -1100,6 +1101,7 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterView.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h +FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterViewResponder.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterViewTest.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/IOKit.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/KeyCodeMap.mm From 8fae87909c5aa150a4a2f357160a8e908e0fac90 Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Sun, 25 Jul 2021 21:20:40 -0500 Subject: [PATCH 37/52] Rename viewController to viewResponder; move viewResponder prop on the FlutterTextInputPlugin to the mm file --- .../ios/framework/Source/FlutterEngine.mm | 2 +- .../framework/Source/FlutterTextInputPlugin.h | 8 +++--- .../Source/FlutterTextInputPlugin.mm | 27 ++++++++++--------- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index f3d202814dfa1..c37f35befe5e6 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -357,7 +357,7 @@ - (void)notifyViewControllerDeallocated { platform_view->SetOwnerViewController({}); } } - [_textInputPlugin.get() resetViewController]; + [_textInputPlugin.get() resetViewResponder]; _viewController.reset(); } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h index 3e5ca618c0583..cbf83717620af 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h @@ -24,7 +24,7 @@ typedef NS_ENUM(NSInteger, FlutterScribbleStatus) { @property(nonatomic, assign) id textInputDelegate; @property(nonatomic, assign) id indirectScribbleDelegate; -@property(nonatomic, readonly) id viewController; +// @property(nonatomic, readonly) id viewResponder; @property(nonatomic, assign) NSMutableDictionary* scribbleElements; - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result; @@ -41,8 +41,8 @@ typedef NS_ENUM(NSInteger, FlutterScribbleStatus) { * These are used by the UIIndirectScribbleInteractionDelegate methods to handle focusing on the * correct element */ -- (void)setupIndirectScribbleInteraction:(id)viewController; -- (void)resetViewController; +- (void)setupIndirectScribbleInteraction:(id)viewResponder; +- (void)resetViewResponder; @end @@ -130,7 +130,7 @@ FLUTTER_DARWIN_EXPORT @property(nonatomic, assign) UIAccessibilityElement* backingTextInputAccessibilityObject; // Scribble Support -@property(nonatomic, assign) id viewController; +@property(nonatomic, assign) id viewResponder; @property(nonatomic) FlutterScribbleStatus scribbleFocusStatus; @property(nonatomic, strong) NSArray* selectionRects; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 4d84748570520..9a67bf62b6775 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -830,26 +830,26 @@ - (void)setTextInputState:(NSDictionary*)state { } } -// Forward touches to the viewController to allow tapping inside the UITextField as normal +// Forward touches to the viewResponder to allow tapping inside the UITextField as normal - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { _scribbleFocusStatus = FlutterScribbleStatusUnfocused; - [self.viewController touchesBegan:touches withEvent:event]; + [self.viewResponder touchesBegan:touches withEvent:event]; } - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event { - [self.viewController touchesMoved:touches withEvent:event]; + [self.viewResponder touchesMoved:touches withEvent:event]; } - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event { - [self.viewController touchesEnded:touches withEvent:event]; + [self.viewResponder touchesEnded:touches withEvent:event]; } - (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event { - [self.viewController touchesCancelled:touches withEvent:event]; + [self.viewResponder touchesCancelled:touches withEvent:event]; } - (void)touchesEstimatedPropertiesUpdated:(NSSet*)touches { - [self.viewController touchesEstimatedPropertiesUpdated:touches]; + [self.viewResponder touchesEstimatedPropertiesUpdated:touches]; } // The documentation for presses* handlers (implemented below) is entirely // unclear about how to handle the case where some, but not all, of the presses @@ -1770,6 +1770,7 @@ @interface FlutterTextInputPlugin () NSMutableDictionary* autofillContext; @property(nonatomic, strong) FlutterTextInputView* activeView; @property(nonatomic, strong) FlutterTextInputViewAccessibilityHider* inputHider; +@property(nonatomic, readonly) id viewResponder; @end @implementation FlutterTextInputPlugin { @@ -1892,7 +1893,7 @@ - (void)setSelectionRects:(NSArray*)rects { - (void)showTextInput { _activeView.textInputDelegate = _textInputDelegate; - _activeView.viewController = _viewController; + _activeView.viewResponder = _viewResponder; [self addToInputParentViewIfNeeded:_activeView]; // Adds a delay to prevent the text view from receiving accessibility // focus in case it is activated during semantics updates. @@ -2251,10 +2252,10 @@ - (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction #pragma mark - Methods related to Scribble support -- (void)setupIndirectScribbleInteraction:(id)viewController { - if (_viewController != viewController) { +- (void)setupIndirectScribbleInteraction:(id)viewResponder { + if (_viewResponder != viewResponder) { if (@available(iOS 14.0, *)) { - UIView* parentView = viewController.view; + UIView* parentView = viewResponder.view; if (parentView != nil) { UIIndirectScribbleInteraction* _scribbleInteraction = [[[UIIndirectScribbleInteraction alloc] @@ -2263,11 +2264,11 @@ - (void)setupIndirectScribbleInteraction:(id)viewControlle } } } - _viewController = viewController; + _viewResponder = viewResponder; } -- (void)resetViewController { - _viewController = nil; +- (void)resetViewResponder { + _viewResponder = nil; } #pragma mark - From 4008ea4e8a07fe35b5612b11181c6a7ad4254491 Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Tue, 3 Aug 2021 21:45:45 -0500 Subject: [PATCH 38/52] More PR changes; provide frame to inputHider so that UIScribbleInteractionDelegate methods fire properly --- .../Source/FlutterIndirectScribbleDelegate.h | 2 + .../framework/Source/FlutterTextInputPlugin.h | 3 +- .../Source/FlutterTextInputPlugin.mm | 117 ++++++++++++------ .../framework/Source/FlutterViewResponder.h | 2 + 4 files changed, 87 insertions(+), 37 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterIndirectScribbleDelegate.h b/shell/platform/darwin/ios/framework/Source/FlutterIndirectScribbleDelegate.h index ab7e7b4e3ba41..f5647e6173387 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterIndirectScribbleDelegate.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterIndirectScribbleDelegate.h @@ -7,6 +7,7 @@ #import +NS_ASSUME_NONNULL_BEGIN @class FlutterTextInputPlugin; @protocol FlutterIndirectScribbleDelegate @@ -18,5 +19,6 @@ requestElementsInRect:(CGRect)rect result:(FlutterResult)callback; @end +NS_ASSUME_NONNULL_END #endif // SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERINDIRECTSCRIBBLEDELEGATE_H_ diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h index cbf83717620af..d9114ad3d74c2 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h @@ -24,8 +24,7 @@ typedef NS_ENUM(NSInteger, FlutterScribbleStatus) { @property(nonatomic, assign) id textInputDelegate; @property(nonatomic, assign) id indirectScribbleDelegate; -// @property(nonatomic, readonly) id viewResponder; -@property(nonatomic, assign) +@property(nonatomic, strong) NSMutableDictionary* scribbleElements; - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 9a67bf62b6775..313c5de83f52a 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -355,29 +355,45 @@ static FlutterAutofillType autofillTypeOf(NSDictionary* configuration) { return FlutterAutofillTypeNone; } -static BOOL isPositionCloserToPoint(CGPoint point, - CGRect position, - CGRect otherPosition, - BOOL checkRightBoundary) { - CGPoint pointForPosition = - CGPointMake(position.origin.x + (checkRightBoundary ? position.size.width : 0), - position.origin.y + position.size.height * 0.5); - float yDist = fabs(pointForPosition.y - point.y); - float xDist = fabs(pointForPosition.x - point.x); - - CGPoint pointForOtherPosition = - CGPointMake(otherPosition.origin.x + (checkRightBoundary ? position.size.width : 0), - otherPosition.origin.y + otherPosition.size.height * 0.5); - float yDistOther = fabs(pointForOtherPosition.y - point.y); - float xDistOther = fabs(pointForOtherPosition.x - point.x); +static BOOL isApproximatelyEqual(float x, float y, float delta) { + return x >= y - delta && x <= y + delta; +} + +// Checks whether point should be considered closer to selectionRect compared to +// otherSelectionRect. +// +// If checkRightBoundary is set, the right-center point on selectionRect and +// otherSelectionRect will be used instead of the left-center point. +// +// This uses special (empirically determined) logic for determining the closer rect, +// rather than a simple distance calculation. First, the closer vertical distance is +// determined. Within the closest y distance, if the point is above the bottom +// of the closest rect, the x distance will be minimized; however, if the point is +// below the bottom of the rect, the x value will be maximized. +static BOOL isSelectionRectCloserToPoint(CGPoint point, + CGRect selectionRect, + CGRect otherSelectionRect, + BOOL checkRightBoundary) { + CGPoint pointForSelectionRect = + CGPointMake(selectionRect.origin.x + (checkRightBoundary ? selectionRect.size.width : 0), + selectionRect.origin.y + selectionRect.size.height * 0.5); + float yDist = fabs(pointForSelectionRect.y - point.y); + float xDist = fabs(pointForSelectionRect.x - point.x); + + CGPoint pointForOtherSelectionRect = + CGPointMake(otherSelectionRect.origin.x + (checkRightBoundary ? selectionRect.size.width : 0), + otherSelectionRect.origin.y + otherSelectionRect.size.height * 0.5); + float yDistOther = fabs(pointForOtherSelectionRect.y - point.y); + float xDistOther = fabs(pointForOtherSelectionRect.x - point.x); BOOL isCloserVertically = yDist < yDistOther - 1; - BOOL isEqualVertically = yDist >= yDistOther - 1 && yDist <= yDistOther + 1; - BOOL isAboveBottomOfLine = point.y <= position.origin.y + position.size.height; + BOOL isEqualVertically = isApproximatelyEqual(yDist, yDistOther, 1); + BOOL isAboveBottomOfLine = point.y <= selectionRect.origin.y + selectionRect.size.height; BOOL isCloserHorizontally = xDist < xDistOther; - BOOL isBelowBottomOfLine = point.y > position.origin.y + position.size.height; + BOOL isBelowBottomOfLine = point.y > selectionRect.origin.y + selectionRect.size.height; BOOL isFartherToRight = - position.origin.x + (checkRightBoundary ? position.size.width : 0) > otherPosition.origin.x; + selectionRect.origin.x + (checkRightBoundary ? selectionRect.size.width : 0) > + otherSelectionRect.origin.x; return (isCloserVertically || (isEqualVertically && ((isAboveBottomOfLine && isCloserHorizontally) || (isBelowBottomOfLine && isFartherToRight)))); @@ -872,7 +888,8 @@ - (void)pressesBegan:(NSSet*)presses for (UIPress* press in presses) { [_textInputDelegate flutterTextInputView:self - handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press withEvent:event] + handlePressEvent:[[[FlutterUIPressProxy alloc] initWithPress:press + withEvent:event] autorelease] nextAction:^() { [super pressesBegan:[NSSet setWithObject:press] withEvent:event]; }]; @@ -888,7 +905,8 @@ - (void)pressesChanged:(NSSet*)presses for (UIPress* press in presses) { [_textInputDelegate flutterTextInputView:self - handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press withEvent:event] + handlePressEvent:[[[FlutterUIPressProxy alloc] initWithPress:press + withEvent:event] autorelease] nextAction:^() { [super pressesChanged:[NSSet setWithObject:press] withEvent:event]; }]; @@ -904,7 +922,8 @@ - (void)pressesEnded:(NSSet*)presses for (UIPress* press in presses) { [_textInputDelegate flutterTextInputView:self - handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press withEvent:event] + handlePressEvent:[[[FlutterUIPressProxy alloc] initWithPress:press + withEvent:event] autorelease] nextAction:^() { [super pressesEnded:[NSSet setWithObject:press] withEvent:event]; }]; @@ -920,7 +939,8 @@ - (void)pressesCancelled:(NSSet*)presses for (UIPress* press in presses) { [_textInputDelegate flutterTextInputView:self - handlePressEvent:[[FlutterUIPressProxy alloc] initWithPress:press withEvent:event] + handlePressEvent:[[[FlutterUIPressProxy alloc] initWithPress:press + withEvent:event] autorelease] nextAction:^() { [super pressesCancelled:[NSSet setWithObject:press] withEvent:event]; }]; @@ -971,13 +991,15 @@ - (void)setIsVisibleToAutofill:(BOOL)isVisibleToAutofill { - (void)scribbleInteractionWillBeginWriting:(UIScribbleInteraction*)interaction API_AVAILABLE(ios(14.0)) { + NSLog(@"[scribble] scribbleInteractionWillBeginWriting"); _scribbleInProgress = YES; [_textInputDelegate flutterTextInputViewScribbleInteractionBegan:self]; } - (void)scribbleInteractionDidFinishWriting:(UIScribbleInteraction*)interaction API_AVAILABLE(ios(14.0)) { - _scribbleInProgress = NO; + NSLog(@"[scribble] scribbleInteractionDidFinishWriting"); + // _scribbleInProgress = NO; [_textInputDelegate flutterTextInputViewScribbleInteractionFinished:self]; } @@ -1151,8 +1173,10 @@ - (void)setMarkedText:(NSString*)markedText selectedRange:(NSRange)markedSelecte NSRange selectedRange = _selectedTextRange.range; NSRange markedTextRange = ((FlutterTextRange*)self.markedTextRange).range; - if (_scribbleInProgress || _scribbleFocusStatus != FlutterScribbleStatusUnfocused) + NSLog(@"[scribble] setMarkedText - _scribbleInProgress %@", @(_scribbleInProgress)); + if (_scribbleInProgress || _scribbleFocusStatus != FlutterScribbleStatusUnfocused) { return; + } if (markedText == nil) markedText = @""; @@ -1203,11 +1227,20 @@ - (UITextRange*)textRangeFromPosition:(UITextPosition*)fromPosition } - (NSUInteger)decrementOffsetPosition:(NSUInteger)position { - return MAX(0, position - 1); + NSLog(@"[scribble] decrementOffsetPosition %@ (%@)", @(position), @(_scribbleInProgress)); + // if (_scribbleInProgress) { + // return MAX(0, position - 1); + // } + return fml::RangeForCharacterAtIndex(self.text, MAX(0, position - 1)).location; } - (NSUInteger)incrementOffsetPosition:(NSUInteger)position { - return MIN(position + 1, self.text.length); + NSLog(@"[scribble] incrementOffsetPosition %@ (%@)", @(position), @(_scribbleInProgress)); + // if (_scribbleInProgress) { + // return MIN(position + 1, self.text.length); + // } + NSRange charRange = fml::RangeForCharacterAtIndex(self.text, position); + return MIN(position + charRange.length, self.text.length); } - (UITextPosition*)positionFromPosition:(UITextPosition*)position offset:(NSInteger)offset { @@ -1218,6 +1251,7 @@ - (UITextPosition*)positionFromPosition:(UITextPosition*)position offset:(NSInte return nil; } + NSLog(@"[scribble] positionFromPosition - _scribbleInProgress %@", @(_scribbleInProgress)); if (_scribbleInProgress) { return [FlutterTextPosition positionWithIndex:newLocation]; } @@ -1388,6 +1422,7 @@ - (CGRect)firstRectForRange:(UITextRange*)range { return _cachedFirstRect; } + NSLog(@"[scribble] firstRectForRange - _scribbleInProgress %@", @(_scribbleInProgress)); if (!_scribbleInProgress && _scribbleFocusStatus == FlutterScribbleStatusUnfocused) { [_textInputDelegate flutterTextInputView:self showAutocorrectionPromptRectForStart:start @@ -1495,7 +1530,8 @@ - (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRang NSUInteger position = _selectionRects[i].position; if (position >= start && position <= end) { BOOL isFirst = _closestIndex == 0; - if (isFirst || isPositionCloserToPoint(point, _selectionRects[i].rect, _closestRect, NO)) { + if (isFirst || isSelectionRectCloserToPoint(point, _selectionRects[i].rect, _closestRect, + /*checkRightBoundary=*/NO)) { _closestIndex = i; _closestRect = _selectionRects[i].rect; _closestPosition = position; @@ -1510,7 +1546,8 @@ - (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRang NSUInteger i = [_selectionRects count] - 1; NSUInteger position = _selectionRects[i].position + 1; if (position <= end) { - if (isPositionCloserToPoint(point, _selectionRects[i].rect, _closestRect, YES)) { + if (isSelectionRectCloserToPoint(point, _selectionRects[i].rect, _closestRect, + /*checkRightBoundary=*/YES)) { _closestIndex = [_selectionRects count]; _closestPosition = position; } @@ -1641,6 +1678,7 @@ - (void)insertText:(NSString*)text { } _scribbleFocusStatus = FlutterScribbleStatusUnfocused; + _scribbleInProgress = NO; _selectionRects = copiedRects; _selectionAffinity = _kTextAffinityDownstream; [self replaceRange:_selectedTextRange withText:text]; @@ -1662,6 +1700,7 @@ - (void)removeTextPlaceholder:(UITextPlaceholder*)textPlaceholder API_AVAILABLE( - (void)deleteBackward { _selectionAffinity = _kTextAffinityDownstream; _scribbleFocusStatus = FlutterScribbleStatusUnfocused; + _scribbleInProgress = NO; // When deleting Thai vowel, _selectedTextRange has location // but does not have length, so we have to manually set it. @@ -1859,13 +1898,21 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { - (void)setEditableSizeAndTransform:(NSDictionary*)dictionary { [_activeView setEditableTransform:dictionary[@"transform"]]; - // TODO(fbcouch): only do this on iPadOS? // This is necessary to set up where the scribble interactable element will be - int leftIndex = 12; - int topIndex = 13; - _activeView.frame = CGRectMake([dictionary[@"transform"][leftIndex] intValue], - [dictionary[@"transform"][topIndex] intValue], - [dictionary[@"width"] intValue], [dictionary[@"height"] intValue]); + if (@available(iOS 14.0, *)) { + NSString* deviceModel = (NSString*)[UIDevice currentDevice].model; + if ([[deviceModel substringWithRange:NSMakeRange(0, 4)] isEqualToString:@"iPad"]) { + int leftIndex = 12; + int topIndex = 13; + _activeView.frame = + CGRectMake([dictionary[@"transform"][leftIndex] intValue], + [dictionary[@"transform"][topIndex] intValue], [dictionary[@"width"] intValue], + [dictionary[@"height"] intValue]); + _inputHider.frame = [[[UIApplication sharedApplication] delegate] window].frame; + NSLog(@"[scribble] _inputHider.frame: %@", @(_inputHider.frame)); + NSLog(@"[scribble] _activeView.frame: %@", @(_activeView.frame)); + } + } } - (void)updateMarkedRect:(NSDictionary*)dictionary { diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewResponder.h b/shell/platform/darwin/ios/framework/Source/FlutterViewResponder.h index 0ce80d25f755c..a9bf602e86fef 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewResponder.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewResponder.h @@ -7,6 +7,7 @@ #import +NS_ASSUME_NONNULL_BEGIN @protocol FlutterViewResponder @property(nonatomic, strong) UIView* view; @@ -18,5 +19,6 @@ - (void)touchesEstimatedPropertiesUpdated:(NSSet*)touches; @end +NS_ASSUME_NONNULL_END #endif // SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERVIEWRESPONDER_H_ From c6aa05eb0cc520ec9e3f5901a124630cc3cb4b59 Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Wed, 4 Aug 2021 09:52:22 -0500 Subject: [PATCH 39/52] Swap scribbleInProgress over to enum, fix issue where cursor wouldn't go to the end of the line when tapping --- .../framework/Source/FlutterTextInputPlugin.h | 16 +++-- .../Source/FlutterTextInputPlugin.mm | 59 ++++++++++--------- .../Source/FlutterTextInputPluginTest.mm | 12 ++-- 3 files changed, 49 insertions(+), 38 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h index d9114ad3d74c2..37059cf0c619b 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h @@ -13,10 +13,16 @@ #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewResponder.h" -typedef NS_ENUM(NSInteger, FlutterScribbleStatus) { - FlutterScribbleStatusUnfocused, - FlutterScribbleStatusFocusing, - FlutterScribbleStatusFocused, +typedef NS_ENUM(NSInteger, FlutterScribbleFocusStatus) { + FlutterScribbleFocusStatusUnfocused, + FlutterScribbleFocusStatusFocusing, + FlutterScribbleFocusStatusFocused, +}; + +typedef NS_ENUM(NSInteger, FlutterScribbleInteractionStatus) { + FlutterScribbleInteractionStatusNone, + FlutterScribbleInteractionStatusStarted, + FlutterScribbleInteractionStatusEnding, }; @interface FlutterTextInputPlugin @@ -130,7 +136,7 @@ FLUTTER_DARWIN_EXPORT // Scribble Support @property(nonatomic, assign) id viewResponder; -@property(nonatomic) FlutterScribbleStatus scribbleFocusStatus; +@property(nonatomic) FlutterScribbleFocusStatus scribbleFocusStatus; @property(nonatomic, strong) NSArray* selectionRects; @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 313c5de83f52a..c7dc6a73502f0 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -389,7 +389,7 @@ static BOOL isSelectionRectCloserToPoint(CGPoint point, BOOL isCloserVertically = yDist < yDistOther - 1; BOOL isEqualVertically = isApproximatelyEqual(yDist, yDistOther, 1); BOOL isAboveBottomOfLine = point.y <= selectionRect.origin.y + selectionRect.size.height; - BOOL isCloserHorizontally = xDist < xDistOther; + BOOL isCloserHorizontally = xDist <= xDistOther; BOOL isBelowBottomOfLine = point.y > selectionRect.origin.y + selectionRect.size.height; BOOL isFartherToRight = selectionRect.origin.x + (checkRightBoundary ? selectionRect.size.width : 0) > @@ -647,7 +647,7 @@ @implementation FlutterTextInputView { FlutterTextRange* _selectedTextRange; UIInputViewController* _inputViewController; CGRect _cachedFirstRect; - BOOL _scribbleInProgress; + FlutterScribbleInteractionStatus _scribbleInteractionStatus; BOOL _hasPlaceholder; // Whether to show the system keyboard when this view // becomes the first responder. Typically set to false @@ -673,6 +673,7 @@ - (instancetype)init { _selectedTextRange = [[FlutterTextRange alloc] initWithNSRange:NSMakeRange(0, 0)]; _markedRect = kInvalidFirstRect; _cachedFirstRect = kInvalidFirstRect; + _scribbleInteractionStatus = FlutterScribbleInteractionStatusNone; // Initialize with the zero matrix which is not // an affine transform. _editableTransform = CATransform3D(); @@ -848,7 +849,10 @@ - (void)setTextInputState:(NSDictionary*)state { // Forward touches to the viewResponder to allow tapping inside the UITextField as normal - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { - _scribbleFocusStatus = FlutterScribbleStatusUnfocused; + _scribbleFocusStatus = FlutterScribbleFocusStatusUnfocused; + if (_scribbleInteractionStatus == FlutterScribbleInteractionStatusEnding) { + _scribbleInteractionStatus = FlutterScribbleInteractionStatusNone; + } [self.viewResponder touchesBegan:touches withEvent:event]; } @@ -992,14 +996,14 @@ - (void)setIsVisibleToAutofill:(BOOL)isVisibleToAutofill { - (void)scribbleInteractionWillBeginWriting:(UIScribbleInteraction*)interaction API_AVAILABLE(ios(14.0)) { NSLog(@"[scribble] scribbleInteractionWillBeginWriting"); - _scribbleInProgress = YES; + _scribbleInteractionStatus = FlutterScribbleInteractionStatusStarted; [_textInputDelegate flutterTextInputViewScribbleInteractionBegan:self]; } - (void)scribbleInteractionDidFinishWriting:(UIScribbleInteraction*)interaction API_AVAILABLE(ios(14.0)) { NSLog(@"[scribble] scribbleInteractionDidFinishWriting"); - // _scribbleInProgress = NO; + _scribbleInteractionStatus = FlutterScribbleInteractionStatusEnding; [_textInputDelegate flutterTextInputViewScribbleInteractionFinished:self]; } @@ -1054,7 +1058,8 @@ - (void)setSelectedTextRangeLocal:(UITextRange*)selectedTextRange { - (void)setSelectedTextRange:(UITextRange*)selectedTextRange { [self setSelectedTextRangeLocal:selectedTextRange]; [self updateEditingState]; - if (_scribbleInProgress || _scribbleFocusStatus == FlutterScribbleStatusFocused) { + if (_scribbleInteractionStatus != FlutterScribbleInteractionStatusNone || + _scribbleFocusStatus == FlutterScribbleFocusStatusFocused) { NSAssert([selectedTextRange isKindOfClass:[FlutterTextRange class]], @"Expected a FlutterTextRange for range (got %@).", [selectedTextRange class]); FlutterTextRange* flutterTextRange = (FlutterTextRange*)selectedTextRange; @@ -1062,6 +1067,10 @@ - (void)setSelectedTextRange:(UITextRange*)selectedTextRange { [_textInputDelegate flutterTextInputView:self showToolbar:_textInputClient]; } } + + if (_scribbleInteractionStatus == FlutterScribbleInteractionStatusEnding) { + _scribbleInteractionStatus = FlutterScribbleInteractionStatusNone; + } } - (id)insertDictationResultPlaceholder { @@ -1173,8 +1182,8 @@ - (void)setMarkedText:(NSString*)markedText selectedRange:(NSRange)markedSelecte NSRange selectedRange = _selectedTextRange.range; NSRange markedTextRange = ((FlutterTextRange*)self.markedTextRange).range; - NSLog(@"[scribble] setMarkedText - _scribbleInProgress %@", @(_scribbleInProgress)); - if (_scribbleInProgress || _scribbleFocusStatus != FlutterScribbleStatusUnfocused) { + if (_scribbleInteractionStatus != FlutterScribbleInteractionStatusNone || + _scribbleFocusStatus != FlutterScribbleFocusStatusUnfocused) { return; } @@ -1227,18 +1236,10 @@ - (UITextRange*)textRangeFromPosition:(UITextPosition*)fromPosition } - (NSUInteger)decrementOffsetPosition:(NSUInteger)position { - NSLog(@"[scribble] decrementOffsetPosition %@ (%@)", @(position), @(_scribbleInProgress)); - // if (_scribbleInProgress) { - // return MAX(0, position - 1); - // } return fml::RangeForCharacterAtIndex(self.text, MAX(0, position - 1)).location; } - (NSUInteger)incrementOffsetPosition:(NSUInteger)position { - NSLog(@"[scribble] incrementOffsetPosition %@ (%@)", @(position), @(_scribbleInProgress)); - // if (_scribbleInProgress) { - // return MIN(position + 1, self.text.length); - // } NSRange charRange = fml::RangeForCharacterAtIndex(self.text, position); return MIN(position + charRange.length, self.text.length); } @@ -1251,8 +1252,8 @@ - (UITextPosition*)positionFromPosition:(UITextPosition*)position offset:(NSInte return nil; } - NSLog(@"[scribble] positionFromPosition - _scribbleInProgress %@", @(_scribbleInProgress)); - if (_scribbleInProgress) { + NSLog(@"positionFromPosition _scribbleInteractionStatus %@", @(_scribbleInteractionStatus)); + if (_scribbleInteractionStatus != FlutterScribbleInteractionStatusNone) { return [FlutterTextPosition positionWithIndex:newLocation]; } @@ -1422,8 +1423,8 @@ - (CGRect)firstRectForRange:(UITextRange*)range { return _cachedFirstRect; } - NSLog(@"[scribble] firstRectForRange - _scribbleInProgress %@", @(_scribbleInProgress)); - if (!_scribbleInProgress && _scribbleFocusStatus == FlutterScribbleStatusUnfocused) { + if (_scribbleInteractionStatus == FlutterScribbleInteractionStatusNone && + _scribbleFocusStatus == FlutterScribbleFocusStatusUnfocused) { [_textInputDelegate flutterTextInputView:self showAutocorrectionPromptRectForStart:start end:end @@ -1677,8 +1678,10 @@ - (void)insertText:(NSString*)text { } } - _scribbleFocusStatus = FlutterScribbleStatusUnfocused; - _scribbleInProgress = NO; + _scribbleFocusStatus = FlutterScribbleFocusStatusUnfocused; + if (_scribbleInteractionStatus == FlutterScribbleInteractionStatusEnding) { + _scribbleInteractionStatus = FlutterScribbleInteractionStatusNone; + } _selectionRects = copiedRects; _selectionAffinity = _kTextAffinityDownstream; [self replaceRange:_selectedTextRange withText:text]; @@ -1699,8 +1702,10 @@ - (void)removeTextPlaceholder:(UITextPlaceholder*)textPlaceholder API_AVAILABLE( - (void)deleteBackward { _selectionAffinity = _kTextAffinityDownstream; - _scribbleFocusStatus = FlutterScribbleStatusUnfocused; - _scribbleInProgress = NO; + _scribbleFocusStatus = FlutterScribbleFocusStatusUnfocused; + if (_scribbleInteractionStatus == FlutterScribbleInteractionStatusEnding) { + _scribbleInteractionStatus = FlutterScribbleInteractionStatusNone; + } // When deleting Thai vowel, _selectedTextRange has location // but does not have length, so we have to manually set it. @@ -2224,7 +2229,7 @@ - (void)clearTextInputClient { - (BOOL)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction isElementFocused:(UIScribbleElementIdentifier)elementIdentifier API_AVAILABLE(ios(14.0)) { - return _activeView.scribbleFocusStatus == FlutterScribbleStatusFocused; + return _activeView.scribbleFocusStatus == FlutterScribbleFocusStatusFocused; } - (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction @@ -2232,13 +2237,13 @@ - (void)indirectScribbleInteraction:(UIIndirectScribbleInteraction*)interaction referencePoint:(CGPoint)focusReferencePoint completion:(void (^)(UIResponder* focusedInput))completion API_AVAILABLE(ios(14.0)) { - _activeView.scribbleFocusStatus = FlutterScribbleStatusFocusing; + _activeView.scribbleFocusStatus = FlutterScribbleFocusStatusFocusing; [_indirectScribbleDelegate flutterTextInputPlugin:self focusElement:elementIdentifier atPoint:focusReferencePoint result:^(id _Nullable result) { _activeView.scribbleFocusStatus = - FlutterScribbleStatusFocused; + FlutterScribbleFocusStatusFocused; completion(_activeView); }]; } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm index 9cb9c158d7f2d..cf89bdc6c4374 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm @@ -251,19 +251,19 @@ - (void)testAutocorrectionPromptRectDoesNotAppearDuringScribble { // showAutocorrectionPromptRectForStart fires in response to firstRectForRange. XCTAssertEqual(callCount, 2); - inputView.scribbleFocusStatus = FlutterScribbleStatusFocusing; + inputView.scribbleFocusStatus = FlutterScribbleFocusStatusFocusing; [inputView firstRectForRange:[FlutterTextRange rangeWithNSRange:NSMakeRange(0, 1)]]; // showAutocorrectionPromptRectForStart does not fire in response to firstRectForRange during a // scribble-initiated focus. XCTAssertEqual(callCount, 2); - inputView.scribbleFocusStatus = FlutterScribbleStatusFocused; + inputView.scribbleFocusStatus = FlutterScribbleFocusStatusFocused; [inputView firstRectForRange:[FlutterTextRange rangeWithNSRange:NSMakeRange(0, 1)]]; // showAutocorrectionPromptRectForStart does not fire in response to firstRectForRange after a // scribble-initiated focus. XCTAssertEqual(callCount, 2); - inputView.scribbleFocusStatus = FlutterScribbleStatusUnfocused; + inputView.scribbleFocusStatus = FlutterScribbleFocusStatusUnfocused; [inputView firstRectForRange:[FlutterTextRange rangeWithNSRange:NSMakeRange(0, 1)]]; // showAutocorrectionPromptRectForStart fires in response to firstRectForRange. XCTAssertEqual(callCount, 3); @@ -485,19 +485,19 @@ - (void)testSetMarkedTextDuringScribbleDoesNotTriggerUpdateEditingClient { // updateEditingClient fires in response to setMarkedText. XCTAssertEqual(updateCount, 2); - inputView.scribbleFocusStatus = FlutterScribbleStatusFocusing; + inputView.scribbleFocusStatus = FlutterScribbleFocusStatusFocusing; [inputView setMarkedText:@"during focus" selectedRange:NSMakeRange(1, 2)]; // updateEditingClient does not fire in response to setMarkedText during a scribble-initiated // focus. XCTAssertEqual(updateCount, 2); - inputView.scribbleFocusStatus = FlutterScribbleStatusFocused; + inputView.scribbleFocusStatus = FlutterScribbleFocusStatusFocused; [inputView setMarkedText:@"after focus" selectedRange:NSMakeRange(2, 3)]; // updateEditingClient does not fire in response to setMarkedText after a scribble-initiated // focus. XCTAssertEqual(updateCount, 2); - inputView.scribbleFocusStatus = FlutterScribbleStatusUnfocused; + inputView.scribbleFocusStatus = FlutterScribbleFocusStatusUnfocused; [inputView setMarkedText:@"marked text" selectedRange:NSMakeRange(0, 1)]; // updateEditingClient fires in response to setMarkedText. XCTAssertEqual(updateCount, 3); From a39f759f739d95b96cb766faa8e318620f9132c8 Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Wed, 4 Aug 2021 17:04:51 -0500 Subject: [PATCH 40/52] Fix up tests, remove logs, prevent native toolbar from popping up --- .../framework/Source/FlutterTextInputPlugin.h | 1 + .../Source/FlutterTextInputPlugin.mm | 81 +++++++++++-------- .../Source/FlutterTextInputPluginTest.mm | 2 + 3 files changed, 52 insertions(+), 32 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h index 37059cf0c619b..37ed1348062fa 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h @@ -138,6 +138,7 @@ FLUTTER_DARWIN_EXPORT @property(nonatomic, assign) id viewResponder; @property(nonatomic) FlutterScribbleFocusStatus scribbleFocusStatus; @property(nonatomic, strong) NSArray* selectionRects; +- (void)resetScribbleInteractionStatusIfEnding; @end #endif // SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERTEXTINPUTPLUGIN_H_ diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index c7dc6a73502f0..285c1702fd1d1 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -399,6 +399,17 @@ static BOOL isSelectionRectCloserToPoint(CGPoint point, (isBelowBottomOfLine && isFartherToRight)))); } +// Checks whether Scribble features are possibly available – meaning this is an iPad running iOS 14+ +static BOOL isScribbleAvailable() { + if (@available(iOS 14.0, *)) { + NSString* deviceModel = (NSString*)[UIDevice currentDevice].model; + if ([[deviceModel substringWithRange:NSMakeRange(0, 4)] isEqualToString:@"iPad"]) { + return YES; + } + } + return NO; +} + #pragma mark - FlutterTextPosition @implementation FlutterTextPosition @@ -850,9 +861,7 @@ - (void)setTextInputState:(NSDictionary*)state { // Forward touches to the viewResponder to allow tapping inside the UITextField as normal - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { _scribbleFocusStatus = FlutterScribbleFocusStatusUnfocused; - if (_scribbleInteractionStatus == FlutterScribbleInteractionStatusEnding) { - _scribbleInteractionStatus = FlutterScribbleInteractionStatusNone; - } + [self resetScribbleInteractionStatusIfEnding]; [self.viewResponder touchesBegan:touches withEvent:event]; } @@ -995,14 +1004,12 @@ - (void)setIsVisibleToAutofill:(BOOL)isVisibleToAutofill { - (void)scribbleInteractionWillBeginWriting:(UIScribbleInteraction*)interaction API_AVAILABLE(ios(14.0)) { - NSLog(@"[scribble] scribbleInteractionWillBeginWriting"); _scribbleInteractionStatus = FlutterScribbleInteractionStatusStarted; [_textInputDelegate flutterTextInputViewScribbleInteractionBegan:self]; } - (void)scribbleInteractionDidFinishWriting:(UIScribbleInteraction*)interaction API_AVAILABLE(ios(14.0)) { - NSLog(@"[scribble] scribbleInteractionDidFinishWriting"); _scribbleInteractionStatus = FlutterScribbleInteractionStatusEnding; [_textInputDelegate flutterTextInputViewScribbleInteractionFinished:self]; } @@ -1027,6 +1034,22 @@ - (BOOL)canBecomeFirstResponder { return _textInputClient != 0; } +- (BOOL)canPerformAction:(SEL)action withSender:(id)sender { + // When scribble is available, the FlutterTextInputView will display the native toolbar unless + // these text editing actions are disabled + if (isScribbleAvailable() && + (action == @selector(paste:) || action == @selector(cut:) || action == @selector(copy:) || + action == @selector(select:) || action == @selector(selectAll:) || + action == @selector(delete:) || action == @selector(makeTextWritingDirectionLeftToRight:) || + action == @selector(makeTextWritingDirectionRightToLeft:) || + action == @selector(toggleBoldface:) || action == @selector(toggleItalics:) || + action == @selector(toggleUnderline:) || action == NSSelectorFromString(@"_define:") || + action == NSSelectorFromString(@"_lookup:") || action == NSSelectorFromString(@"_share:"))) { + return NO; + } + return [super canPerformAction:action withSender:sender]; +} + #pragma mark - UITextInput Overrides - (id)tokenizer { @@ -1068,9 +1091,7 @@ - (void)setSelectedTextRange:(UITextRange*)selectedTextRange { } } - if (_scribbleInteractionStatus == FlutterScribbleInteractionStatusEnding) { - _scribbleInteractionStatus = FlutterScribbleInteractionStatusNone; - } + [self resetScribbleInteractionStatusIfEnding]; } - (id)insertDictationResultPlaceholder { @@ -1113,9 +1134,6 @@ - (void)replaceRangeLocal:(NSRange)range withText:(NSString*)text { [self.text replaceCharactersInRange:[self clampSelection:range forText:self.text] withString:text]; - [self setSelectedTextRangeLocal:[FlutterTextRange - rangeWithNSRange:[self clampSelection:selectedRange - forText:self.text]]]; } - (void)replaceRange:(UITextRange*)range withText:(NSString*)text { @@ -1252,7 +1270,6 @@ - (UITextPosition*)positionFromPosition:(UITextPosition*)position offset:(NSInte return nil; } - NSLog(@"positionFromPosition _scribbleInteractionStatus %@", @(_scribbleInteractionStatus)); if (_scribbleInteractionStatus != FlutterScribbleInteractionStatusNone) { return [FlutterTextPosition positionWithIndex:newLocation]; } @@ -1425,6 +1442,7 @@ - (CGRect)firstRectForRange:(UITextRange*)range { if (_scribbleInteractionStatus == FlutterScribbleInteractionStatusNone && _scribbleFocusStatus == FlutterScribbleFocusStatusUnfocused) { + NSLog(@"showAutocorrectionPromptRectForStart:%@, end:%@", @(start), @(end)); [_textInputDelegate flutterTextInputView:self showAutocorrectionPromptRectForStart:start end:end @@ -1679,9 +1697,7 @@ - (void)insertText:(NSString*)text { } _scribbleFocusStatus = FlutterScribbleFocusStatusUnfocused; - if (_scribbleInteractionStatus == FlutterScribbleInteractionStatusEnding) { - _scribbleInteractionStatus = FlutterScribbleInteractionStatusNone; - } + [self resetScribbleInteractionStatusIfEnding]; _selectionRects = copiedRects; _selectionAffinity = _kTextAffinityDownstream; [self replaceRange:_selectedTextRange withText:text]; @@ -1703,9 +1719,7 @@ - (void)removeTextPlaceholder:(UITextPlaceholder*)textPlaceholder API_AVAILABLE( - (void)deleteBackward { _selectionAffinity = _kTextAffinityDownstream; _scribbleFocusStatus = FlutterScribbleFocusStatusUnfocused; - if (_scribbleInteractionStatus == FlutterScribbleInteractionStatusEnding) { - _scribbleInteractionStatus = FlutterScribbleInteractionStatusNone; - } + [self resetScribbleInteractionStatusIfEnding]; // When deleting Thai vowel, _selectedTextRange has location // but does not have length, so we have to manually set it. @@ -1752,6 +1766,12 @@ - (BOOL)accessibilityElementsHidden { return !_accessibilityEnabled; } +- (void)resetScribbleInteractionStatusIfEnding { + if (_scribbleInteractionStatus == FlutterScribbleInteractionStatusEnding) { + _scribbleInteractionStatus = FlutterScribbleInteractionStatusNone; + } +} + @end /** @@ -1903,20 +1923,17 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { - (void)setEditableSizeAndTransform:(NSDictionary*)dictionary { [_activeView setEditableTransform:dictionary[@"transform"]]; - // This is necessary to set up where the scribble interactable element will be - if (@available(iOS 14.0, *)) { - NSString* deviceModel = (NSString*)[UIDevice currentDevice].model; - if ([[deviceModel substringWithRange:NSMakeRange(0, 4)] isEqualToString:@"iPad"]) { - int leftIndex = 12; - int topIndex = 13; - _activeView.frame = - CGRectMake([dictionary[@"transform"][leftIndex] intValue], - [dictionary[@"transform"][topIndex] intValue], [dictionary[@"width"] intValue], - [dictionary[@"height"] intValue]); - _inputHider.frame = [[[UIApplication sharedApplication] delegate] window].frame; - NSLog(@"[scribble] _inputHider.frame: %@", @(_inputHider.frame)); - NSLog(@"[scribble] _activeView.frame: %@", @(_activeView.frame)); - } + if (isScribbleAvailable()) { + // This is necessary to set up where the scribble interactable element will be + int leftIndex = 12; + int topIndex = 13; + _inputHider.frame = + CGRectMake([dictionary[@"transform"][leftIndex] intValue], + [dictionary[@"transform"][topIndex] intValue], [dictionary[@"width"] intValue], + [dictionary[@"height"] intValue]); + _activeView.frame = + CGRectMake(0, 0, [dictionary[@"width"] intValue], [dictionary[@"height"] intValue]); + _activeView.tintColor = [UIColor clearColor]; } } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm index cf89bdc6c4374..3602db408fdd5 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm @@ -247,6 +247,7 @@ - (void)testAutocorrectionPromptRectDoesNotAppearDuringScribble { XCTAssertEqual(callCount, 1); [inputView scribbleInteractionDidFinishWriting:scribbleInteraction]; + [inputView resetScribbleInteractionStatusIfEnding]; [inputView firstRectForRange:[FlutterTextRange rangeWithNSRange:NSMakeRange(0, 1)]]; // showAutocorrectionPromptRectForStart fires in response to firstRectForRange. XCTAssertEqual(callCount, 2); @@ -481,6 +482,7 @@ - (void)testSetMarkedTextDuringScribbleDoesNotTriggerUpdateEditingClient { XCTAssertEqual(updateCount, 1); [inputView scribbleInteractionDidFinishWriting:scribbleInteraction]; + [inputView resetScribbleInteractionStatusIfEnding]; [inputView setMarkedText:@"marked text" selectedRange:NSMakeRange(0, 1)]; // updateEditingClient fires in response to setMarkedText. XCTAssertEqual(updateCount, 2); From 7a4e440b26451d13d550d035f37400fedbf3bd73 Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Sat, 7 Aug 2021 15:43:47 -0500 Subject: [PATCH 41/52] Add back errant removal of setSelectedTextRangeLocal call in replaceRangeLocal --- .../darwin/ios/framework/Source/FlutterTextInputPlugin.mm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 285c1702fd1d1..a72d09c3e0fb4 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -1134,6 +1134,9 @@ - (void)replaceRangeLocal:(NSRange)range withText:(NSString*)text { [self.text replaceCharactersInRange:[self clampSelection:range forText:self.text] withString:text]; + [self setSelectedTextRangeLocal:[FlutterTextRange + rangeWithNSRange:[self clampSelection:selectedRange + forText:self.text]]]; } - (void)replaceRange:(UITextRange*)range withText:(NSString*)text { From a95f2d9cb6e20979043e2f99af65fa39319c36c1 Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Sun, 8 Aug 2021 21:50:48 -0500 Subject: [PATCH 42/52] Add workarounds for UITextInteraction-related selection and gesture problems --- .../Source/FlutterTextInputPlugin.mm | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index a72d09c3e0fb4..24cceccdf9b93 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -721,6 +721,16 @@ - (instancetype)init { return self; } +// Prevent UITextInteraction gesture detectors from showing autocomplete +// popups on tap or double tap. +- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gestureRecognizer { + if ([NSStringFromClass([gestureRecognizer class]) + isEqual:@"PKTextInputDrawingGestureRecognizer"]) { + return YES; + } + return NO; +} + - (void)configureWithDictionary:(NSDictionary*)configuration { NSAssert(!_decommissioned, @"Attempt to reuse a decommissioned view, for %@", configuration); NSDictionary* inputType = configuration[kKeyboardType]; @@ -776,6 +786,19 @@ - (UITextContentType)textContentType { return _textContentType; } +// Prevent UITextInteraction from showing selection handles or highlights. +- (UIColor*)insertionPointColor { + return [UIColor clearColor]; +} + +- (UIColor*)selectionBarColor { + return [UIColor clearColor]; +} + +- (UIColor*)selectionHighlightColor { + return [UIColor clearColor]; +} + - (UIInputViewController*)inputViewController { if (_isSystemKeyboardEnabled) { return nil; @@ -1076,6 +1099,12 @@ - (void)setSelectedTextRangeLocal:(UITextRange*)selectedTextRange { } [oldSelectedRange release]; } + // This is required to remove the UITextSelectionView that the UITextInteraction + // adds (seemingly multiple times) that leaves a grey box after replacing a selection, + // for example when hitting backspace or using Scribble to delete text. + for (NSUInteger i = 0; i < [self.subviews count]; i++) { + [self.subviews[i] removeFromSuperview]; + } } - (void)setSelectedTextRange:(UITextRange*)selectedTextRange { @@ -1445,7 +1474,6 @@ - (CGRect)firstRectForRange:(UITextRange*)range { if (_scribbleInteractionStatus == FlutterScribbleInteractionStatusNone && _scribbleFocusStatus == FlutterScribbleFocusStatusUnfocused) { - NSLog(@"showAutocorrectionPromptRectForStart:%@, end:%@", @(start), @(end)); [_textInputDelegate flutterTextInputView:self showAutocorrectionPromptRectForStart:start end:end From c0aeba01d942520ae365d58293734ace46a33daa Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Wed, 11 Aug 2021 14:55:54 -0500 Subject: [PATCH 43/52] Change up workaround a bit to move add the UITextInteraction in -beginFloatingCursor: and remove it in -endFloatingCursor: --- .../Source/FlutterTextInputPlugin.mm | 46 ++++++++----------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 24cceccdf9b93..8682b23e6d0aa 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -648,6 +648,7 @@ @interface FlutterTextInputView () @property(nonatomic, assign) CGRect markedRect; @property(nonatomic) BOOL isVisibleToAutofill; @property(nonatomic, assign) BOOL accessibilityEnabled; +@property(nonatomic, strong) UITextInteraction* textInteraction API_AVAILABLE(ios(13.0)); - (void)setEditableTransform:(NSArray*)matrix; @end @@ -706,31 +707,11 @@ - (instancetype)init { _smartDashesType = UITextSmartDashesTypeYes; } _selectionRects = @[]; - - // This makes sure UITextSelectionView.interactionAssistant is not nil so - // UITextSelectionView has access to this view (and its bounds). Otherwise - // floating cursor breaks: https://github.com/flutter/flutter/issues/70267. - if (@available(iOS 13.0, *)) { - UITextInteraction* interaction = - [UITextInteraction textInteractionForMode:UITextInteractionModeEditable]; - interaction.textInput = self; - [self addInteraction:interaction]; - } } return self; } -// Prevent UITextInteraction gesture detectors from showing autocomplete -// popups on tap or double tap. -- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gestureRecognizer { - if ([NSStringFromClass([gestureRecognizer class]) - isEqual:@"PKTextInputDrawingGestureRecognizer"]) { - return YES; - } - return NO; -} - - (void)configureWithDictionary:(NSDictionary*)configuration { NSAssert(!_decommissioned, @"Attempt to reuse a decommissioned view, for %@", configuration); NSDictionary* inputType = configuration[kKeyboardType]; @@ -786,7 +767,9 @@ - (UITextContentType)textContentType { return _textContentType; } -// Prevent UITextInteraction from showing selection handles or highlights. +// Prevent UIKit from showing selection handles or highlights. This is needed +// because Scribble interactions require the view to have it's actual frame on +// the screen. - (UIColor*)insertionPointColor { return [UIColor clearColor]; } @@ -1099,12 +1082,6 @@ - (void)setSelectedTextRangeLocal:(UITextRange*)selectedTextRange { } [oldSelectedRange release]; } - // This is required to remove the UITextSelectionView that the UITextInteraction - // adds (seemingly multiple times) that leaves a grey box after replacing a selection, - // for example when hitting backspace or using Scribble to delete text. - for (NSUInteger i = 0; i < [self.subviews count]; i++) { - [self.subviews[i] removeFromSuperview]; - } } - (void)setSelectedTextRange:(UITextRange*)selectedTextRange { @@ -1638,6 +1615,15 @@ - (void)beginFloatingCursorAtPoint:(CGPoint)point { // (1, 2, -3, -4) would become (-2, -2, 3, 4). NSAssert(!_isFloatingCursorActive, @"Another floating cursor is currently active."); _isFloatingCursorActive = true; + // This makes sure UITextSelectionView.interactionAssistant is not nil so + // UITextSelectionView has access to this view (and its bounds). Otherwise + // floating cursor breaks: https://github.com/flutter/flutter/issues/70267. + NSLog(@"beginFloatingCursorAtPoint"); + if (@available(iOS 13.0, *)) { + self.textInteraction = [UITextInteraction textInteractionForMode:UITextInteractionModeEditable]; + self.textInteraction.textInput = self; + [self addInteraction:_textInteraction]; + } [self.textInputDelegate flutterTextInputView:self updateFloatingCursor:FlutterFloatingCursorDragStateStart withClient:_textInputClient @@ -1657,6 +1643,12 @@ - (void)endFloatingCursor { NSAssert(_isFloatingCursorActive, @"endFloatingCursor is called without an active floating cursor."); _isFloatingCursorActive = false; + if (@available(iOS 13.0, *)) { + if (_textInteraction != NULL) { + [self removeInteraction:_textInteraction]; + self.textInteraction = NULL; + } + } [self.textInputDelegate flutterTextInputView:self updateFloatingCursor:FlutterFloatingCursorDragStateEnd withClient:_textInputClient From ae8f5361ce05c519ee766ee3767d9450bcacd3c3 Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Wed, 11 Aug 2021 15:36:31 -0500 Subject: [PATCH 44/52] Move setup of UIScribbleInteraction to the init method of FlutterTextInputView so that it reliably gets added --- .../ios/framework/Source/FlutterTextInputPlugin.mm | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 8682b23e6d0aa..4143585dc2f35 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -707,6 +707,12 @@ - (instancetype)init { _smartDashesType = UITextSmartDashesTypeYes; } _selectionRects = @[]; + + if (@available(iOS 14.0, *)) { + UIScribbleInteraction* interaction = + [[[UIScribbleInteraction alloc] initWithDelegate:self] autorelease]; + [self addInteraction:interaction]; + } } return self; @@ -2247,11 +2253,6 @@ - (void)addToInputParentViewIfNeeded:(FlutterTextInputView*)inputView { UIView* parentView = self.keyWindow; if (_inputHider.superview != parentView) { [parentView addSubview:_inputHider]; - if (@available(iOS 14.0, *)) { - UIScribbleInteraction* interaction = - [[[UIScribbleInteraction alloc] initWithDelegate:inputView] autorelease]; - [inputView addInteraction:interaction]; - } } } From f106b91effedc751fefd11d6509955eff40a001a Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Thu, 12 Aug 2021 15:10:37 -0500 Subject: [PATCH 45/52] Remove remaining NSLog --- .../darwin/ios/framework/Source/FlutterTextInputPlugin.mm | 1 - 1 file changed, 1 deletion(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 4143585dc2f35..3c6c01792488e 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -1624,7 +1624,6 @@ - (void)beginFloatingCursorAtPoint:(CGPoint)point { // This makes sure UITextSelectionView.interactionAssistant is not nil so // UITextSelectionView has access to this view (and its bounds). Otherwise // floating cursor breaks: https://github.com/flutter/flutter/issues/70267. - NSLog(@"beginFloatingCursorAtPoint"); if (@available(iOS 13.0, *)) { self.textInteraction = [UITextInteraction textInteractionForMode:UITextInteractionModeEditable]; self.textInteraction.textInput = self; From a141a148037e530a156463196545248c7d0b3e43 Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Thu, 12 Aug 2021 16:24:46 -0500 Subject: [PATCH 46/52] Add guard clause to selectionRectsForRange to prevent crash when changing keyboard to Japanese --- .../darwin/ios/framework/Source/FlutterTextInputPlugin.mm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 3c6c01792488e..64551f678ad41 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -1518,6 +1518,12 @@ - (UITextPosition*)closestPositionToPoint:(CGPoint)point { } - (NSArray*)selectionRectsForRange:(UITextRange*)range { + // At least in the simulator, swapping to the Japanese keyboard crashes the app as this method + // is called immediately with a UITextRange with a UITextPosition rather than FlutterTextPosition + // for the start and end. + if (![range.start isKindOfClass:[FlutterTextPosition class]]) { + return @[]; + } NSAssert([range.start isKindOfClass:[FlutterTextPosition class]], @"Expected a FlutterTextPosition for range.start (got %@).", [range.start class]); NSAssert([range.end isKindOfClass:[FlutterTextPosition class]], From fc8c9b10653d1e2f67d58de90b0dda7397f05f95 Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Wed, 15 Sep 2021 21:56:13 -0500 Subject: [PATCH 47/52] Clean up some comments --- .../framework/Source/FlutterTextInputPlugin.h | 2 +- .../Source/FlutterTextInputPlugin.mm | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h index 37ed1348062fa..a9f79a673009a 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h @@ -44,7 +44,7 @@ typedef NS_ENUM(NSInteger, FlutterScribbleInteractionStatus) { /** * These are used by the UIIndirectScribbleInteractionDelegate methods to handle focusing on the - * correct element + * correct element. */ - (void)setupIndirectScribbleInteraction:(id)viewResponder; - (void)resetViewResponder; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 64551f678ad41..e04304d6111ac 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -365,10 +365,10 @@ static BOOL isApproximatelyEqual(float x, float y, float delta) { // If checkRightBoundary is set, the right-center point on selectionRect and // otherSelectionRect will be used instead of the left-center point. // -// This uses special (empirically determined) logic for determining the closer rect, -// rather than a simple distance calculation. First, the closer vertical distance is -// determined. Within the closest y distance, if the point is above the bottom -// of the closest rect, the x distance will be minimized; however, if the point is +// This uses special (empirically determined using a 1st gen iPad pro, 9.7" model running +// iOS 14.7.1) logic for determining the closer rect, rather than a simple distance calculation. +// First, the closer vertical distance is determined. Within the closest y distance, if the point is +// above the bottom of the closest rect, the x distance will be minimized; however, if the point is // below the bottom of the rect, the x value will be maximized. static BOOL isSelectionRectCloserToPoint(CGPoint point, CGRect selectionRect, @@ -399,7 +399,8 @@ static BOOL isSelectionRectCloserToPoint(CGPoint point, (isBelowBottomOfLine && isFartherToRight)))); } -// Checks whether Scribble features are possibly available – meaning this is an iPad running iOS 14+ +// Checks whether Scribble features are possibly available – meaning this is an iPad running iOS +// 14 or higher. static BOOL isScribbleAvailable() { if (@available(iOS 14.0, *)) { NSString* deviceModel = (NSString*)[UIDevice currentDevice].model; @@ -870,7 +871,7 @@ - (void)setTextInputState:(NSDictionary*)state { } } -// Forward touches to the viewResponder to allow tapping inside the UITextField as normal +// Forward touches to the viewResponder to allow tapping inside the UITextField as normal. - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { _scribbleFocusStatus = FlutterScribbleFocusStatusUnfocused; [self resetScribbleInteractionStatusIfEnding]; @@ -1008,7 +1009,7 @@ - (BOOL)isVisibleToAutofill { // account. - (void)setIsVisibleToAutofill:(BOOL)isVisibleToAutofill { // This probably needs to change (think it is getting overwritten by the updateSizeAndTransform - // stuff for now) + // stuff for now). self.frame = isVisibleToAutofill ? CGRectMake(0, 0, 1, 1) : CGRectZero; } @@ -1048,7 +1049,7 @@ - (BOOL)canBecomeFirstResponder { - (BOOL)canPerformAction:(SEL)action withSender:(id)sender { // When scribble is available, the FlutterTextInputView will display the native toolbar unless - // these text editing actions are disabled + // these text editing actions are disabled. if (isScribbleAvailable() && (action == @selector(paste:) || action == @selector(cut:) || action == @selector(copy:) || action == @selector(select:) || action == @selector(selectAll:) || @@ -1958,7 +1959,7 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { - (void)setEditableSizeAndTransform:(NSDictionary*)dictionary { [_activeView setEditableTransform:dictionary[@"transform"]]; if (isScribbleAvailable()) { - // This is necessary to set up where the scribble interactable element will be + // This is necessary to set up where the scribble interactable element will be. int leftIndex = 12; int topIndex = 13; _inputHider.frame = From 3966913d2b7190a6e80dac979fcd105f7ad796e9 Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Thu, 23 Sep 2021 20:53:31 -0500 Subject: [PATCH 48/52] Add comment explaining isCloserVertically --- .../darwin/ios/framework/Source/FlutterTextInputPlugin.mm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index fa0f687d76b22..a5a8033ab790b 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -427,6 +427,9 @@ static BOOL isSelectionRectCloserToPoint(CGPoint point, float yDistOther = fabs(pointForOtherSelectionRect.y - point.y); float xDistOther = fabs(pointForOtherSelectionRect.x - point.x); + // This serves a similar purpose to isApproximatelyEqual, allowing a little buffer before + // declaring something closer vertically to account for the small variations in size and position + // of SelectionRects, especially when dealing with emoji. BOOL isCloserVertically = yDist < yDistOther - 1; BOOL isEqualVertically = isApproximatelyEqual(yDist, yDistOther, 1); BOOL isAboveBottomOfLine = point.y <= selectionRect.origin.y + selectionRect.size.height; From cbfd5b3a1c6791ff88a014039a0eae8f6348f1ef Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Sat, 25 Sep 2021 20:15:01 -0500 Subject: [PATCH 49/52] Fix compile error (need to add flutterTextInputView art to - updateEditingClient:withDelta: call --- .../darwin/ios/framework/Source/FlutterTextInputPlugin.mm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index d6eb7a270b677..13c2350b90367 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -1810,7 +1810,9 @@ - (void)updateEditingStateWithDelta:(FlutterTextEditingDelta*)delta { @"deltas" : @[ deltaToFramework ], }; - [self.textInputDelegate updateEditingClient:_textInputClient withDelta:deltas]; + [self.textInputDelegate flutterTextInputView:self + updateEditingClient:_textInputClient + withDelta:deltas]; } - (BOOL)hasText { From 8ea77a773b972ae03a7551f075943706ee23bc23 Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Mon, 27 Sep 2021 11:35:39 -0500 Subject: [PATCH 50/52] Update - updateEditingClient:withDelta: in tests --- .../Source/FlutterTextInputPluginTest.mm | 178 ++++++++++-------- 1 file changed, 95 insertions(+), 83 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm index bfbd6c2205d2a..e582b1cb82042 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm @@ -366,7 +366,7 @@ - (void)testTextEditingDeltasAreGeneratedOnTextInput { inputView.enableDeltaModel = YES; __block int updateCount = 0; - OCMStub([engine updateEditingClient:0 withDelta:[OCMArg isNotNil]]) + OCMStub([engine flutterTextInputView:inputView updateEditingClient:0 withDelta:[OCMArg isNotNil]]) .andDo(^(NSInvocation* invocation) { updateCount++; }); @@ -377,82 +377,91 @@ - (void)testTextEditingDeltasAreGeneratedOnTextInput { // Verify correct delta is generated. OCMVerify([engine - updateEditingClient:0 - withDelta:[OCMArg checkWithBlock:^BOOL(NSDictionary* state) { - return ([[state[@"deltas"] objectAtIndex:0][@"oldText"] isEqualToString:@""]) && - ([[state[@"deltas"] objectAtIndex:0][@"deltaText"] - isEqualToString:@"text to insert"]) && - ([[state[@"deltas"] objectAtIndex:0][@"deltaStart"] intValue] == 0) && - ([[state[@"deltas"] objectAtIndex:0][@"deltaEnd"] intValue] == 0); - }]]); + flutterTextInputView:inputView + updateEditingClient:0 + withDelta:[OCMArg checkWithBlock:^BOOL(NSDictionary* state) { + return ([[state[@"deltas"] objectAtIndex:0][@"oldText"] isEqualToString:@""]) && + ([[state[@"deltas"] objectAtIndex:0][@"deltaText"] + isEqualToString:@"text to insert"]) && + ([[state[@"deltas"] objectAtIndex:0][@"deltaStart"] intValue] == 0) && + ([[state[@"deltas"] objectAtIndex:0][@"deltaEnd"] intValue] == 0); + }]]); [inputView deleteBackward]; XCTAssertEqual(updateCount, 2); OCMVerify([engine - updateEditingClient:0 - withDelta:[OCMArg checkWithBlock:^BOOL(NSDictionary* state) { - return ([[state[@"deltas"] objectAtIndex:0][@"oldText"] - isEqualToString:@"text to insert"]) && - ([[state[@"deltas"] objectAtIndex:0][@"deltaText"] isEqualToString:@""]) && - ([[state[@"deltas"] objectAtIndex:0][@"deltaStart"] intValue] == 13) && - ([[state[@"deltas"] objectAtIndex:0][@"deltaEnd"] intValue] == 14); - }]]); + flutterTextInputView:inputView + updateEditingClient:0 + withDelta:[OCMArg checkWithBlock:^BOOL(NSDictionary* state) { + return ([[state[@"deltas"] objectAtIndex:0][@"oldText"] + isEqualToString:@"text to insert"]) && + ([[state[@"deltas"] objectAtIndex:0][@"deltaText"] + isEqualToString:@""]) && + ([[state[@"deltas"] objectAtIndex:0][@"deltaStart"] intValue] == 13) && + ([[state[@"deltas"] objectAtIndex:0][@"deltaEnd"] intValue] == 14); + }]]); inputView.selectedTextRange = [FlutterTextRange rangeWithNSRange:NSMakeRange(0, 1)]; XCTAssertEqual(updateCount, 3); OCMVerify([engine - updateEditingClient:0 - withDelta:[OCMArg checkWithBlock:^BOOL(NSDictionary* state) { - return ([[state[@"deltas"] objectAtIndex:0][@"oldText"] - isEqualToString:@"text to inser"]) && - ([[state[@"deltas"] objectAtIndex:0][@"deltaText"] isEqualToString:@""]) && - ([[state[@"deltas"] objectAtIndex:0][@"deltaStart"] intValue] == -1) && - ([[state[@"deltas"] objectAtIndex:0][@"deltaEnd"] intValue] == -1); - }]]); + flutterTextInputView:inputView + updateEditingClient:0 + withDelta:[OCMArg checkWithBlock:^BOOL(NSDictionary* state) { + return ([[state[@"deltas"] objectAtIndex:0][@"oldText"] + isEqualToString:@"text to inser"]) && + ([[state[@"deltas"] objectAtIndex:0][@"deltaText"] + isEqualToString:@""]) && + ([[state[@"deltas"] objectAtIndex:0][@"deltaStart"] intValue] == -1) && + ([[state[@"deltas"] objectAtIndex:0][@"deltaEnd"] intValue] == -1); + }]]); [inputView replaceRange:[FlutterTextRange rangeWithNSRange:NSMakeRange(0, 1)] withText:@"replace text"]; XCTAssertEqual(updateCount, 4); OCMVerify([engine - updateEditingClient:0 - withDelta:[OCMArg checkWithBlock:^BOOL(NSDictionary* state) { - return ([[state[@"deltas"] objectAtIndex:0][@"oldText"] - isEqualToString:@"text to inser"]) && - ([[state[@"deltas"] objectAtIndex:0][@"deltaText"] - isEqualToString:@"replace text"]) && - ([[state[@"deltas"] objectAtIndex:0][@"deltaStart"] intValue] == 0) && - ([[state[@"deltas"] objectAtIndex:0][@"deltaEnd"] intValue] == 1); - }]]); + flutterTextInputView:inputView + updateEditingClient:0 + withDelta:[OCMArg checkWithBlock:^BOOL(NSDictionary* state) { + return ([[state[@"deltas"] objectAtIndex:0][@"oldText"] + isEqualToString:@"text to inser"]) && + ([[state[@"deltas"] objectAtIndex:0][@"deltaText"] + isEqualToString:@"replace text"]) && + ([[state[@"deltas"] objectAtIndex:0][@"deltaStart"] intValue] == 0) && + ([[state[@"deltas"] objectAtIndex:0][@"deltaEnd"] intValue] == 1); + }]]); [inputView setMarkedText:@"marked text" selectedRange:NSMakeRange(0, 1)]; XCTAssertEqual(updateCount, 5); OCMVerify([engine - updateEditingClient:0 - withDelta:[OCMArg checkWithBlock:^BOOL(NSDictionary* state) { - return ([[state[@"deltas"] objectAtIndex:0][@"oldText"] - isEqualToString:@"replace textext to inser"]) && - ([[state[@"deltas"] objectAtIndex:0][@"deltaText"] - isEqualToString:@"marked text"]) && - ([[state[@"deltas"] objectAtIndex:0][@"deltaStart"] intValue] == 12) && - ([[state[@"deltas"] objectAtIndex:0][@"deltaEnd"] intValue] == 12); - }]]); + flutterTextInputView:inputView + updateEditingClient:0 + withDelta:[OCMArg checkWithBlock:^BOOL(NSDictionary* state) { + return ([[state[@"deltas"] objectAtIndex:0][@"oldText"] + isEqualToString:@"replace textext to inser"]) && + ([[state[@"deltas"] objectAtIndex:0][@"deltaText"] + isEqualToString:@"marked text"]) && + ([[state[@"deltas"] objectAtIndex:0][@"deltaStart"] intValue] == 12) && + ([[state[@"deltas"] objectAtIndex:0][@"deltaEnd"] intValue] == 12); + }]]); [inputView unmarkText]; XCTAssertEqual(updateCount, 6); OCMVerify([engine - updateEditingClient:0 - withDelta:[OCMArg checkWithBlock:^BOOL(NSDictionary* state) { - return ([[state[@"deltas"] objectAtIndex:0][@"oldText"] - isEqualToString:@"replace textmarked textext to inser"]) && - ([[state[@"deltas"] objectAtIndex:0][@"deltaText"] isEqualToString:@""]) && - ([[state[@"deltas"] objectAtIndex:0][@"deltaStart"] intValue] == -1) && - ([[state[@"deltas"] objectAtIndex:0][@"deltaEnd"] intValue] == -1); - }]]); + flutterTextInputView:inputView + updateEditingClient:0 + withDelta:[OCMArg checkWithBlock:^BOOL(NSDictionary* state) { + return ([[state[@"deltas"] objectAtIndex:0][@"oldText"] + isEqualToString:@"replace textmarked textext to inser"]) && + ([[state[@"deltas"] objectAtIndex:0][@"deltaText"] + isEqualToString:@""]) && + ([[state[@"deltas"] objectAtIndex:0][@"deltaStart"] intValue] == -1) && + ([[state[@"deltas"] objectAtIndex:0][@"deltaEnd"] intValue] == -1); + }]]); } - (void)testTextEditingDeltasAreGeneratedOnSetMarkedTextReplacement { @@ -461,7 +470,7 @@ - (void)testTextEditingDeltasAreGeneratedOnSetMarkedTextReplacement { inputView.enableDeltaModel = YES; __block int updateCount = 0; - OCMStub([engine updateEditingClient:0 withDelta:[OCMArg isNotNil]]) + OCMStub([engine flutterTextInputView:inputView updateEditingClient:0 withDelta:[OCMArg isNotNil]]) .andDo(^(NSInvocation* invocation) { updateCount++; }); @@ -478,15 +487,16 @@ - (void)testTextEditingDeltasAreGeneratedOnSetMarkedTextReplacement { XCTAssertEqual(updateCount, 2); OCMVerify([engine - updateEditingClient:0 - withDelta:[OCMArg checkWithBlock:^BOOL(NSDictionary* state) { - return ([[state[@"deltas"] objectAtIndex:0][@"oldText"] - isEqualToString:@"Some initial text"]) && - ([[state[@"deltas"] objectAtIndex:0][@"deltaText"] - isEqualToString:@"new marked text."]) && - ([[state[@"deltas"] objectAtIndex:0][@"deltaStart"] intValue] == 13) && - ([[state[@"deltas"] objectAtIndex:0][@"deltaEnd"] intValue] == 17); - }]]); + flutterTextInputView:inputView + updateEditingClient:0 + withDelta:[OCMArg checkWithBlock:^BOOL(NSDictionary* state) { + return ([[state[@"deltas"] objectAtIndex:0][@"oldText"] + isEqualToString:@"Some initial text"]) && + ([[state[@"deltas"] objectAtIndex:0][@"deltaText"] + isEqualToString:@"new marked text."]) && + ([[state[@"deltas"] objectAtIndex:0][@"deltaStart"] intValue] == 13) && + ([[state[@"deltas"] objectAtIndex:0][@"deltaEnd"] intValue] == 17); + }]]); } - (void)testTextEditingDeltasAreGeneratedOnSetMarkedTextInsertion { @@ -495,7 +505,7 @@ - (void)testTextEditingDeltasAreGeneratedOnSetMarkedTextInsertion { inputView.enableDeltaModel = YES; __block int updateCount = 0; - OCMStub([engine updateEditingClient:0 withDelta:[OCMArg isNotNil]]) + OCMStub([engine flutterTextInputView:inputView updateEditingClient:0 withDelta:[OCMArg isNotNil]]) .andDo(^(NSInvocation* invocation) { updateCount++; }); @@ -512,15 +522,16 @@ - (void)testTextEditingDeltasAreGeneratedOnSetMarkedTextInsertion { XCTAssertEqual(updateCount, 2); OCMVerify([engine - updateEditingClient:0 - withDelta:[OCMArg checkWithBlock:^BOOL(NSDictionary* state) { - return ([[state[@"deltas"] objectAtIndex:0][@"oldText"] - isEqualToString:@"Some initial text"]) && - ([[state[@"deltas"] objectAtIndex:0][@"deltaText"] - isEqualToString:@"text."]) && - ([[state[@"deltas"] objectAtIndex:0][@"deltaStart"] intValue] == 13) && - ([[state[@"deltas"] objectAtIndex:0][@"deltaEnd"] intValue] == 17); - }]]); + flutterTextInputView:inputView + updateEditingClient:0 + withDelta:[OCMArg checkWithBlock:^BOOL(NSDictionary* state) { + return ([[state[@"deltas"] objectAtIndex:0][@"oldText"] + isEqualToString:@"Some initial text"]) && + ([[state[@"deltas"] objectAtIndex:0][@"deltaText"] + isEqualToString:@"text."]) && + ([[state[@"deltas"] objectAtIndex:0][@"deltaStart"] intValue] == 13) && + ([[state[@"deltas"] objectAtIndex:0][@"deltaEnd"] intValue] == 17); + }]]); } - (void)testTextEditingDeltasAreGeneratedOnSetMarkedTextDeletion { @@ -529,7 +540,7 @@ - (void)testTextEditingDeltasAreGeneratedOnSetMarkedTextDeletion { inputView.enableDeltaModel = YES; __block int updateCount = 0; - OCMStub([engine updateEditingClient:0 withDelta:[OCMArg isNotNil]]) + OCMStub([engine flutterTextInputView:inputView updateEditingClient:0 withDelta:[OCMArg isNotNil]]) .andDo(^(NSInvocation* invocation) { updateCount++; }); @@ -546,15 +557,16 @@ - (void)testTextEditingDeltasAreGeneratedOnSetMarkedTextDeletion { XCTAssertEqual(updateCount, 2); OCMVerify([engine - updateEditingClient:0 - withDelta:[OCMArg checkWithBlock:^BOOL(NSDictionary* state) { - return ([[state[@"deltas"] objectAtIndex:0][@"oldText"] - isEqualToString:@"Some initial text"]) && - ([[state[@"deltas"] objectAtIndex:0][@"deltaText"] - isEqualToString:@"tex"]) && - ([[state[@"deltas"] objectAtIndex:0][@"deltaStart"] intValue] == 13) && - ([[state[@"deltas"] objectAtIndex:0][@"deltaEnd"] intValue] == 17); - }]]); + flutterTextInputView:inputView + updateEditingClient:0 + withDelta:[OCMArg checkWithBlock:^BOOL(NSDictionary* state) { + return ([[state[@"deltas"] objectAtIndex:0][@"oldText"] + isEqualToString:@"Some initial text"]) && + ([[state[@"deltas"] objectAtIndex:0][@"deltaText"] + isEqualToString:@"tex"]) && + ([[state[@"deltas"] objectAtIndex:0][@"deltaStart"] intValue] == 13) && + ([[state[@"deltas"] objectAtIndex:0][@"deltaEnd"] intValue] == 17); + }]]); } #pragma mark - EditingState tests @@ -596,7 +608,7 @@ - (void)testUITextInputCallsUpdateEditingStateWithDeltaOnce { inputView.enableDeltaModel = YES; __block int updateCount = 0; - OCMStub([engine updateEditingClient:0 withDelta:[OCMArg isNotNil]]) + OCMStub([engine flutterTextInputView:inputView updateEditingClient:0 withDelta:[OCMArg isNotNil]]) .andDo(^(NSInvocation* invocation) { updateCount++; }); @@ -669,7 +681,7 @@ - (void)testTextChangesDoNotTriggerUpdateEditingClientWithDelta { inputView.enableDeltaModel = YES; __block int updateCount = 0; - OCMStub([engine updateEditingClient:0 withDelta:[OCMArg isNotNil]]) + OCMStub([engine flutterTextInputView:inputView updateEditingClient:0 withDelta:[OCMArg isNotNil]]) .andDo(^(NSInvocation* invocation) { updateCount++; }); From 07e195cf069ba762c6371bcd2c277292811fd47d Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Fri, 5 Nov 2021 10:20:50 -0500 Subject: [PATCH 51/52] Incorporate feedback --- .../Source/FlutterTextInputPlugin.mm | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index deb66ba089d36..3fce2582c40c6 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -398,7 +398,7 @@ static FlutterAutofillType autofillTypeOf(NSDictionary* configuration) { } static BOOL isApproximatelyEqual(float x, float y, float delta) { - return x >= y - delta && x <= y + delta; + return fabsf(x - y) <= delta; } // Checks whether point should be considered closer to selectionRect compared to @@ -448,8 +448,7 @@ static BOOL isSelectionRectCloserToPoint(CGPoint point, // 14 or higher. static BOOL isScribbleAvailable() { if (@available(iOS 14.0, *)) { - NSString* deviceModel = (NSString*)[UIDevice currentDevice].model; - if ([[deviceModel substringWithRange:NSMakeRange(0, 4)] isEqualToString:@"iPad"]) { + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { return YES; } } @@ -753,7 +752,7 @@ - (instancetype)init { _smartQuotesType = UITextSmartQuotesTypeYes; _smartDashesType = UITextSmartDashesTypeYes; } - _selectionRects = @[]; + _selectionRects = [[NSArray alloc] init]; if (@available(iOS 14.0, *)) { UIScribbleInteraction* interaction = @@ -1097,14 +1096,7 @@ - (BOOL)canBecomeFirstResponder { - (BOOL)canPerformAction:(SEL)action withSender:(id)sender { // When scribble is available, the FlutterTextInputView will display the native toolbar unless // these text editing actions are disabled. - if (isScribbleAvailable() && - (action == @selector(paste:) || action == @selector(cut:) || action == @selector(copy:) || - action == @selector(select:) || action == @selector(selectAll:) || - action == @selector(delete:) || action == @selector(makeTextWritingDirectionLeftToRight:) || - action == @selector(makeTextWritingDirectionRightToLeft:) || - action == @selector(toggleBoldface:) || action == @selector(toggleItalics:) || - action == @selector(toggleUnderline:) || action == NSSelectorFromString(@"_define:") || - action == NSSelectorFromString(@"_lookup:") || action == NSSelectorFromString(@"_share:"))) { + if (isScribbleAvailable()) { return NO; } return [super canPerformAction:action withSender:sender]; @@ -1865,7 +1857,8 @@ - (void)insertText:(NSString*)text { _scribbleFocusStatus = FlutterScribbleFocusStatusUnfocused; [self resetScribbleInteractionStatusIfEnding]; - _selectionRects = copiedRects; + self.selectionRects = copiedRects; + [copiedRects release]; _selectionAffinity = _kTextAffinityDownstream; [self replaceRange:_selectedTextRange withText:text]; } From ee11699bbda577bf8252d9ca54f3fe2f61a0512d Mon Sep 17 00:00:00 2001 From: Jami Couch Date: Fri, 5 Nov 2021 13:27:06 -0500 Subject: [PATCH 52/52] Rename scribbleInteraction variable --- .../darwin/ios/framework/Source/FlutterTextInputPlugin.mm | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 35e4a3474a45c..4a111d3aace6f 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -2404,10 +2404,9 @@ - (void)setupIndirectScribbleInteraction:(id)viewResponder if (@available(iOS 14.0, *)) { UIView* parentView = viewResponder.view; if (parentView != nil) { - UIIndirectScribbleInteraction* _scribbleInteraction = - [[[UIIndirectScribbleInteraction alloc] - initWithDelegate:(id)self] autorelease]; - [parentView addInteraction:_scribbleInteraction]; + UIIndirectScribbleInteraction* scribbleInteraction = [[[UIIndirectScribbleInteraction alloc] + initWithDelegate:(id)self] autorelease]; + [parentView addInteraction:scribbleInteraction]; } } }