diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java index 6c77899c3a6a5..155f87b04b833 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java @@ -286,7 +286,8 @@ public static Configuration fromJson(@NonNull JSONObject json) throws JSONExcept TextCapitalization.fromValue(json.getString("textCapitalization")), InputType.fromJson(json.getJSONObject("inputType")), inputAction, - json.isNull("actionLabel") ? null : json.getString("actionLabel") + json.isNull("actionLabel") ? null : json.getString("actionLabel"), + json.isNull("textContentType") ? null : TextContentType.fromJson(json.getJSONObject("textContentType")) ); } @@ -328,6 +329,8 @@ private static Integer inputActionFromTextInputAction(@NonNull String inputActio public final Integer inputAction; @Nullable public final String actionLabel; + @Nullable + public final TextContentType textContentType; public Configuration( boolean obscureText, @@ -336,7 +339,8 @@ public Configuration( @NonNull TextCapitalization textCapitalization, @NonNull InputType inputType, @Nullable Integer inputAction, - @Nullable String actionLabel + @Nullable String actionLabel, + @Nullable TextContentType textContentType ) { this.obscureText = obscureText; this.autocorrect = autocorrect; @@ -345,6 +349,7 @@ public Configuration( this.inputType = inputType; this.inputAction = inputAction; this.actionLabel = actionLabel; + this.textContentType = textContentType; } } @@ -406,6 +411,23 @@ static TextInputType fromValue(@NonNull String encodedName) throws NoSuchFieldEx } } + /** + * Types of text content for auto-fill functionality + */ + public static class TextContentType { + @NonNull + public static TextContentType fromJson(@NonNull JSONObject json) throws JSONException, NoSuchFieldException { + return new TextContentType(json.getString("rawValue")); + } + + @NonNull + public final String rawValue; + + public TextContentType(@NonNull String rawValue) { + this.rawValue = rawValue; + } + } + /** * Text capitalization schemes. */ diff --git a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java index f836ddcffee87..3556d29eed649 100644 --- a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java @@ -230,6 +230,12 @@ public InputConnection createInputConnection(View view, EditorInfo outAttrs) { outAttrs.actionId = enterAction; } outAttrs.imeOptions |= enterAction; + + String autofillHintValue = configuration.textContentType.rawValue; + if (autofillHintValue != null) { + String[] autofillHints = {autofillHintValue}; + view.setAutofillHints(autofillHints); + } InputConnectionAdaptor connection = new InputConnectionAdaptor( view, diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 76d9a8106b5cb..d25ca703831b0 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -45,6 +45,75 @@ static UITextAutocapitalizationType ToUITextAutoCapitalizationType(NSDictionary* return UITextAutocapitalizationTypeNone; } +static UITextContentType ToUITextContentType(NSDictionary* type) API_AVAILABLE(ios(10.0)) { + NSDictionary* contentTypeDictionary = type[@"textContentType"]; + NSString* contentType = contentTypeDictionary[@"name"]; + + if ([contentType isEqualToString:@"TextContentType.personName"]) + return UITextContentTypeName; + else if ([contentType isEqualToString:@"TextContentType.personNamePrefix"]) + return UITextContentTypeNamePrefix; + else if ([contentType isEqualToString:@"TextContentType.personGivenName"]) + return UITextContentTypeGivenName; + else if ([contentType isEqualToString:@"TextContentType.personMiddleName"]) + return UITextContentTypeMiddleName; + else if ([contentType isEqualToString:@"TextContentType.personFamilyName"]) + return UITextContentTypeFamilyName; + else if ([contentType isEqualToString:@"TextContentType.personNameSuffix"]) + return UITextContentTypeNameSuffix; + else if ([contentType isEqualToString:@"TextContentType.postalAddress"]) + return UITextContentTypeFullStreetAddress; + else if ([contentType isEqualToString:@"TextContentType.streetAddress"]) + return UITextContentTypeStreetAddressLine1; + else if ([contentType isEqualToString:@"TextContentType.extendedAddress"]) + return UITextContentTypeStreetAddressLine2; + else if ([contentType isEqualToString:@"TextContentType.addressLocality"]) + return UITextContentTypeAddressCity; + else if ([contentType isEqualToString:@"TextContentType.addressRegion"]) + return UITextContentTypeAddressState; + else if ([contentType isEqualToString:@"TextContentType.addressCountry"]) + return UITextContentTypeCountryName; + else if ([contentType isEqualToString:@"TextContentType.postalCode"]) + return UITextContentTypePostalCode; + else if ([contentType isEqualToString:@"TextContentType.phoneNumber"]) + return UITextContentTypeTelephoneNumber; + else if ([contentType isEqualToString:@"TextContentType.emailAddress"]) + return UITextContentTypeEmailAddress; + else if ([contentType isEqualToString:@"TextContentType.url"]) + return UITextContentTypeURL; + else if ([contentType isEqualToString:@"TextContentType.creditCardNumber"]) + return UITextContentTypeCreditCardNumber; + else if ([contentType isEqualToString:@"TextContentType.username"]) { + if (@available(iOS 11.0, *)) { + return UITextContentTypeUsername; + } else { + return nil; + } + } else if ([contentType isEqualToString:@"TextContentType.password"]) { + if (@available(iOS 11.0, *)) { + return UITextContentTypePassword; + } else { + return nil; + } + } else if ([contentType isEqualToString:@"TextContentType.newPassword"]) { + if (@available(iOS 12.0, *)) { + return UITextContentTypeNewPassword; + } else { + return nil; + } + } else if ([contentType isEqualToString:@"TextContentType.smsOTPCode"]) { + if (@available(iOS 12.0, *)) { + return UITextContentTypeOneTimeCode; + } else { + return nil; + } + } + + // If we don't have a known match, try and cast it from the raw value + NSString* rawValue = contentTypeDictionary[@"rawValue"]; + return (UITextContentType)rawValue; +} + static UIReturnKeyType ToUIReturnKeyType(NSString* inputType) { // 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 @@ -154,6 +223,7 @@ @interface FlutterTextInputView : UIView @property(nonatomic) UITextAutocapitalizationType autocapitalizationType; @property(nonatomic) UITextAutocorrectionType autocorrectionType; @property(nonatomic) UITextSpellCheckingType spellCheckingType; +@property(nonatomic, copy) UITextContentType textContentType API_AVAILABLE(ios(10.0)); @property(nonatomic) BOOL enablesReturnKeyAutomatically; @property(nonatomic) UIKeyboardAppearance keyboardAppearance; @property(nonatomic) UIKeyboardType keyboardType; @@ -195,6 +265,7 @@ - (instancetype)init { _keyboardType = UIKeyboardTypeDefault; _returnKeyType = UIReturnKeyDone; _secureTextEntry = NO; + _textContentType = nil; if (@available(iOS 11.0, *)) { _smartQuotesType = UITextSmartQuotesTypeYes; _smartDashesType = UITextSmartDashesTypeYes; @@ -793,6 +864,9 @@ - (void)setTextInputClient:(int)client withConfiguration:(NSDictionary*)configur _activeView.autocorrectionType = autocorrect && ![autocorrect boolValue] ? UITextAutocorrectionTypeNo : UITextAutocorrectionTypeDefault; + if (@available(iOS 10.0, *)) { + _activeView.textContentType = ToUITextContentType(configuration); + } [_activeView setTextInputClient:client]; [_activeView reloadInputViews]; }