Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[iOS] Add iOS live text input engine side support #34751

Merged
merged 6 commits into from
Jul 27, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
constexpr char kTextPlainFormat[] = "text/plain";
const UInt32 kKeyPressClickSoundId = 1306;

} // namespaces
} // namespace

namespace flutter {

Expand All @@ -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 @@ -287,4 +291,19 @@ - (NSDictionary*)clipboardHasStrings {
return @{@"value" : @(hasStrings)};
}

- (BOOL)isLiveTextInputAvailable {
return [[self textField] canPerformAction:@selector(captureTextFromCamera:) withSender:nil];
luckysmg marked this conversation as resolved.
Show resolved Hide resolved
}

- (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 @@ -47,6 +47,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 @@ -2102,6 +2103,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 @@ -2146,6 +2150,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