Skip to content

Commit

Permalink
[iOS] Add iOS live text input engine side support (flutter#34751)
Browse files Browse the repository at this point in the history
  • Loading branch information
luckysmg authored and betrevisan committed Jul 29, 2022
1 parent 198022c commit a1ceb9c
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@

@implementation FlutterPlatformPlugin {
fml::WeakPtr<FlutterEngine> _engine;
// Used to detect whether this device has live text input ability or not.
UITextField* _textField;
}

- (instancetype)initWithEngine:(fml::WeakPtr<FlutterEngine>)engine {
Expand Down Expand Up @@ -88,6 +90,8 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
result(nil);
} else if ([method isEqualToString:@"Clipboard.hasStrings"]) {
result([self clipboardHasStrings]);
} else if ([method isEqualToString:@"LiveText.isLiveTextInputAvailable"]) {
result(@([self isLiveTextInputAvailable]));
} else {
result(FlutterMethodNotImplemented);
}
Expand Down Expand Up @@ -277,4 +281,19 @@ - (NSDictionary*)clipboardHasStrings {
return @{@"value" : @([UIPasteboard generalPasteboard].hasStrings)};
}

- (BOOL)isLiveTextInputAvailable {
return [[self textField] canPerformAction:@selector(captureTextFromCamera:) withSender:nil];
}

- (UITextField*)textField {
if (_textField == nil) {
_textField = [[UITextField alloc] init];
}
return _textField;
}

- (void)dealloc {
[_textField release];
[super dealloc];
}
@end
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
@interface FlutterPlatformPluginTest : XCTestCase
@end

@interface FlutterPlatformPlugin ()
- (BOOL)isLiveTextInputAvailable;
@end

@implementation FlutterPlatformPluginTest

- (void)testClipboardHasCorrectStrings {
Expand Down Expand Up @@ -111,4 +115,24 @@ - (void)testPopSystemNavigator {
OCMVerify([navigationControllerMock popViewControllerAnimated:YES]);
}

- (void)testWhetherDeviceHasLiveTextInputInvokeCorrectly {
FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test" project:nil];
std::unique_ptr<fml::WeakPtrFactory<FlutterEngine>> _weakFactory =
std::make_unique<fml::WeakPtrFactory<FlutterEngine>>(engine);
XCTestExpectation* invokeExpectation =
[self expectationWithDescription:@"isLiveTextInputAvailableInvoke"];
FlutterPlatformPlugin* plugin =
[[FlutterPlatformPlugin alloc] initWithEngine:_weakFactory->GetWeakPtr()];
FlutterPlatformPlugin* mockPlugin = OCMPartialMock(plugin);
FlutterMethodCall* methodCall =
[FlutterMethodCall methodCallWithMethodName:@"LiveText.isLiveTextInputAvailable"
arguments:nil];
FlutterResult result = ^(id result) {
OCMVerify([mockPlugin isLiveTextInputAvailable]);
[invokeExpectation fulfill];
};
[mockPlugin handleMethodCall:methodCall result:result];
[self waitForExpectationsWithTimeout:1 handler:nil];
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
static NSString* const kSetMarkedTextRectMethod = @"TextInput.setMarkedTextRect";
static NSString* const kFinishAutofillContextMethod = @"TextInput.finishAutofillContext";
static NSString* const kSetSelectionRectsMethod = @"TextInput.setSelectionRects";
static NSString* const kStartLiveTextInputMethod = @"TextInput.startLiveTextInput";

#pragma mark - TextInputConfiguration Field Names
static NSString* const kSecureTextEntry = @"obscureText";
Expand Down Expand Up @@ -2098,6 +2099,9 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
} else if ([method isEqualToString:kSetSelectionRectsMethod]) {
[self setSelectionRects:args];
result(nil);
} else if ([method isEqualToString:kStartLiveTextInputMethod]) {
[self startLiveTextInput];
result(nil);
} else {
result(FlutterMethodNotImplemented);
}
Expand Down Expand Up @@ -2142,6 +2146,15 @@ - (void)setSelectionRects:(NSArray*)rects {
_activeView.selectionRects = rectsAsRect;
}

- (void)startLiveTextInput {
if (@available(iOS 15.0, *)) {
if (_activeView == nil || !_activeView.isFirstResponder) {
return;
}
[_activeView captureTextFromCamera:nil];
}
}

- (void)showTextInput {
_activeView.viewResponder = _viewResponder;
[self addToInputParentViewIfNeeded:_activeView];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ - (void)cleanUpViewHierarchy:(BOOL)includeActiveView
- (UIView*)hostView;
- (fml::WeakPtr<FlutterTextInputPlugin>)getWeakPtr;
- (void)addToInputParentViewIfNeeded:(FlutterTextInputView*)inputView;
- (void)startLiveTextInput;
@end

@interface FlutterTextInputPluginTest : XCTestCase
Expand Down Expand Up @@ -165,6 +166,17 @@ - (FlutterTextRange*)getLineRangeFromTokenizer:(id<UITextInputTokenizer>)tokeniz
}

#pragma mark - Tests

- (void)testInvokeStartLiveTextInput {
FlutterMethodCall* methodCall =
[FlutterMethodCall methodCallWithMethodName:@"TextInput.startLiveTextInput" arguments:nil];
FlutterTextInputPlugin* mockPlugin = OCMPartialMock(textInputPlugin);
[mockPlugin handleMethodCall:methodCall
result:^(id _Nullable result){
}];
OCMVerify([mockPlugin startLiveTextInput]);
}

- (void)testNoDanglingEnginePointer {
__weak FlutterTextInputPlugin* weakFlutterTextInputPlugin;
FlutterViewController* flutterViewController = [[FlutterViewController alloc] init];
Expand Down

0 comments on commit a1ceb9c

Please sign in to comment.