diff --git a/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java b/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java index c77524dfa06..02190a84b4f 100644 --- a/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java +++ b/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java @@ -172,12 +172,36 @@ class InputConnectionAdaptor extends BaseInputConnection { @Override public boolean performEditorAction(int actionCode) { - // TODO(abarth): Support more actions. switch (actionCode) { + // TODO(mattcarroll): is newline an appropriate action for "none"? case EditorInfo.IME_ACTION_NONE: mFlutterChannel.invokeMethod("TextInputClient.performAction", Arrays.asList(mClient, "TextInputAction.newline")); break; + case EditorInfo.IME_ACTION_UNSPECIFIED: + mFlutterChannel.invokeMethod("TextInputClient.performAction", + Arrays.asList(mClient, "TextInputAction.unspecified")); + break; + case EditorInfo.IME_ACTION_GO: + mFlutterChannel.invokeMethod("TextInputClient.performAction", + Arrays.asList(mClient, "TextInputAction.go")); + break; + case EditorInfo.IME_ACTION_SEARCH: + mFlutterChannel.invokeMethod("TextInputClient.performAction", + Arrays.asList(mClient, "TextInputAction.search")); + break; + case EditorInfo.IME_ACTION_SEND: + mFlutterChannel.invokeMethod("TextInputClient.performAction", + Arrays.asList(mClient, "TextInputAction.send")); + break; + case EditorInfo.IME_ACTION_NEXT: + mFlutterChannel.invokeMethod("TextInputClient.performAction", + Arrays.asList(mClient, "TextInputAction.next")); + break; + case EditorInfo.IME_ACTION_PREVIOUS: + mFlutterChannel.invokeMethod("TextInputClient.performAction", + Arrays.asList(mClient, "TextInputAction.previous")); + break; default: case EditorInfo.IME_ACTION_DONE: mFlutterChannel.invokeMethod("TextInputClient.performAction", diff --git a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java index 0d8f05e3a94..5ada698404b 100644 --- a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java @@ -115,9 +115,29 @@ public class TextInputPlugin implements MethodCallHandler { } private static int inputActionFromTextInputAction(String inputAction) { - if (inputAction.equals("TextInputAction.newline")) - return EditorInfo.IME_ACTION_NONE; - return EditorInfo.IME_ACTION_DONE; + switch (inputAction) { + case "TextInputAction.newline": + return EditorInfo.IME_ACTION_NONE; + case "TextInputAction.none": + return EditorInfo.IME_ACTION_NONE; + case "TextInputAction.unspecified": + return EditorInfo.IME_ACTION_UNSPECIFIED; + case "TextInputAction.done": + return EditorInfo.IME_ACTION_DONE; + case "TextInputAction.go": + return EditorInfo.IME_ACTION_GO; + case "TextInputAction.search": + return EditorInfo.IME_ACTION_SEARCH; + case "TextInputAction.send": + return EditorInfo.IME_ACTION_SEND; + case "TextInputAction.next": + return EditorInfo.IME_ACTION_NEXT; + case "TextInputAction.previous": + return EditorInfo.IME_ACTION_PREVIOUS; + default: + // Present default key if bad input type is given. + return EditorInfo.IME_ACTION_UNSPECIFIED; + } } public InputConnection createInputConnection(FlutterView view, EditorInfo outAttrs) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h b/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h index e82f2c41f42..3136eb08cca 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h @@ -8,7 +8,16 @@ #import typedef NS_ENUM(NSInteger, FlutterTextInputAction) { + FlutterTextInputActionUnspecified, FlutterTextInputActionDone, + FlutterTextInputActionGo, + FlutterTextInputActionSend, + FlutterTextInputActionSearch, + FlutterTextInputActionNext, + FlutterTextInputActionContinue, + FlutterTextInputActionJoin, + FlutterTextInputActionRoute, + FlutterTextInputActionEmergencyCall, FlutterTextInputActionNewline, }; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 79aee477a36..b188bf0ccc3 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -5,6 +5,7 @@ #include "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h" #include +#include static const char _kTextAffinityDownstream[] = "TextAffinity.downstream"; static const char _kTextAffinityUpstream[] = "TextAffinity.upstream"; @@ -30,9 +31,46 @@ static UIKeyboardType ToUIKeyboardType(NSDictionary* type) { } static UIReturnKeyType ToUIReturnKeyType(NSString* inputType) { - if ([inputType isEqualToString:@"TextInputType.multiline"]) + // Where did the term "unspecified" come from? iOS has a "default" and Android + // has "unspecified." These 2 terms seem to mean the same thing but we need + // to pick just one. "unspecified" was chosen because "default" is often a + // reserved word in languages with switch statements (dart, java, etc). + if ([inputType isEqualToString:@"TextInputAction.unspecified"]) return UIReturnKeyDefault; - return UIReturnKeyDone; + + if ([inputType isEqualToString:@"TextInputAction.done"]) + return UIReturnKeyDone; + + if ([inputType isEqualToString:@"TextInputAction.go"]) + return UIReturnKeyGo; + + if ([inputType isEqualToString:@"TextInputAction.send"]) + return UIReturnKeySend; + + if ([inputType isEqualToString:@"TextInputAction.search"]) + return UIReturnKeySearch; + + if ([inputType isEqualToString:@"TextInputAction.next"]) + return UIReturnKeyNext; + + if (@available(iOS 9.0, *)) + if ([inputType isEqualToString:@"TextInputAction.continueAction"]) + return UIReturnKeyContinue; + + if ([inputType isEqualToString:@"TextInputAction.join"]) + return UIReturnKeyJoin; + + if ([inputType isEqualToString:@"TextInputAction.route"]) + return UIReturnKeyRoute; + + if ([inputType isEqualToString:@"TextInputAction.emergencyCall"]) + return UIReturnKeyEmergencyCall; + + if ([inputType isEqualToString:@"TextInputAction.newline"]) + return UIReturnKeyDefault; + + // Present default key if bad input type is given. + return UIReturnKeyDefault; } static UITextAutocapitalizationType ToUITextAutocapitalizationType(NSString* inputType) { @@ -277,14 +315,52 @@ static UITextAutocapitalizationType ToUITextAutocapitalizationType(NSString* inp } - (BOOL)shouldChangeTextInRange:(UITextRange*)range replacementText:(NSString*)text { - if (self.returnKeyType == UIReturnKeyDone && [text isEqualToString:@"\n"]) { - [self resignFirstResponder]; - [self removeFromSuperview]; - [_textInputDelegate performAction:FlutterTextInputActionDone withClient:_textInputClient]; + if (self.returnKeyType == UIReturnKeyDefault && [text isEqualToString:@"\n"]) { + [_textInputDelegate performAction:FlutterTextInputActionNewline withClient:_textInputClient]; + return YES; + } + + if ([text isEqualToString:@"\n"]) { + FlutterTextInputAction action; + switch (self.returnKeyType) { + case UIReturnKeyDefault: + action = FlutterTextInputActionUnspecified; + break; + case UIReturnKeyDone: + action = FlutterTextInputActionDone; + break; + case UIReturnKeyGo: + action = FlutterTextInputActionGo; + break; + case UIReturnKeySend: + action = FlutterTextInputActionSend; + break; + case UIReturnKeySearch: + case UIReturnKeyGoogle: + case UIReturnKeyYahoo: + action = FlutterTextInputActionSearch; + break; + case UIReturnKeyNext: + action = FlutterTextInputActionNext; + break; + case UIReturnKeyContinue: + action = FlutterTextInputActionContinue; + break; + case UIReturnKeyJoin: + action = FlutterTextInputActionJoin; + break; + case UIReturnKeyRoute: + action = FlutterTextInputActionRoute; + break; + case UIReturnKeyEmergencyCall: + action = FlutterTextInputActionEmergencyCall; + break; + } + + [_textInputDelegate performAction:action withClient:_textInputClient]; return NO; } - if (self.returnKeyType == UIReturnKeyDefault && [text isEqualToString:@"\n"]) - [_textInputDelegate performAction:FlutterTextInputActionNewline withClient:_textInputClient]; + return YES; } @@ -612,7 +688,7 @@ static UITextAutocapitalizationType ToUITextAutocapitalizationType(NSString* inp - (void)setTextInputClient:(int)client withConfiguration:(NSDictionary*)configuration { NSDictionary* inputType = configuration[@"inputType"]; _view.keyboardType = ToUIKeyboardType(inputType); - _view.returnKeyType = ToUIReturnKeyType(inputType[@"name"]); + _view.returnKeyType = ToUIReturnKeyType(configuration[@"inputAction"]); _view.autocapitalizationType = ToUITextAutocapitalizationType(inputType[@"name"]); _view.secureTextEntry = [configuration[@"obscureText"] boolValue]; NSString* autocorrect = configuration[@"autocorrect"]; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index b69a40c21e0..252d5e61e4b 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -717,9 +717,40 @@ static inline blink::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* to - (void)performAction:(FlutterTextInputAction)action withClient:(int)client { NSString* actionString; switch (action) { + case FlutterTextInputActionUnspecified: + // Where did the term "unspecified" come from? iOS has a "default" and Android + // has "unspecified." These 2 terms seem to mean the same thing but we need + // to pick just one. "unspecified" was chosen because "default" is often a + // reserved word in languages with switch statements (dart, java, etc). + actionString = @"TextInputAction.unspecified"; + break; case FlutterTextInputActionDone: actionString = @"TextInputAction.done"; break; + case FlutterTextInputActionGo: + actionString = @"TextInputAction.go"; + break; + case FlutterTextInputActionSend: + actionString = @"TextInputAction.send"; + break; + case FlutterTextInputActionSearch: + actionString = @"TextInputAction.search"; + break; + case FlutterTextInputActionNext: + actionString = @"TextInputAction.next"; + break; + case FlutterTextInputActionContinue: + actionString = @"TextInputAction.continue"; + break; + case FlutterTextInputActionJoin: + actionString = @"TextInputAction.join"; + break; + case FlutterTextInputActionRoute: + actionString = @"TextInputAction.route"; + break; + case FlutterTextInputActionEmergencyCall: + actionString = @"TextInputAction.emergencyCall"; + break; case FlutterTextInputActionNewline: actionString = @"TextInputAction.newline"; break;