Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
e31f7089
Unverified
Commit
e31f7089
authored
Apr 20, 2020
by
LongCatIsLooong
Committed by
GitHub
Apr 20, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Autofill Part 1 (#52126)
parent
b7e30cfc
Changes
12
Show whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
1605 additions
and
30 deletions
+1605
-30
services.dart
packages/flutter/lib/services.dart
+1
-0
text_field.dart
packages/flutter/lib/src/cupertino/text_field.dart
+5
-0
text_field.dart
packages/flutter/lib/src/material/text_field.dart
+5
-0
autofill.dart
packages/flutter/lib/src/services/autofill.dart
+811
-0
text_input.dart
packages/flutter/lib/src/services/text_input.dart
+57
-3
autofill.dart
packages/flutter/lib/src/widgets/autofill.dart
+227
-0
editable_text.dart
packages/flutter/lib/src/widgets/editable_text.dart
+78
-25
widgets.dart
packages/flutter/lib/widgets.dart
+1
-0
autofill_test.dart
packages/flutter/test/services/autofill_test.dart
+238
-0
text_input_test.dart
packages/flutter/test/services/text_input_test.dart
+3
-0
autofill_group_test.dart
packages/flutter/test/widgets/autofill_group_test.dart
+166
-0
editable_text_test.dart
packages/flutter/test/widgets/editable_text_test.dart
+13
-2
No files found.
packages/flutter/lib/services.dart
View file @
e31f7089
...
...
@@ -11,6 +11,7 @@
library
services
;
export
'src/services/asset_bundle.dart'
;
export
'src/services/autofill.dart'
;
export
'src/services/binary_messenger.dart'
;
export
'src/services/binding.dart'
;
export
'src/services/clipboard.dart'
;
...
...
packages/flutter/lib/src/cupertino/text_field.dart
View file @
e31f7089
...
...
@@ -268,6 +268,7 @@ class CupertinoTextField extends StatefulWidget {
this
.
onTap
,
this
.
scrollController
,
this
.
scrollPhysics
,
this
.
autofillHints
,
})
:
assert
(
textAlign
!=
null
),
assert
(
readOnly
!=
null
),
assert
(
autofocus
!=
null
),
...
...
@@ -579,6 +580,9 @@ class CupertinoTextField extends StatefulWidget {
/// {@macro flutter.material.textfield.onTap}
final
GestureTapCallback
onTap
;
/// {@macro flutter.widgets.editableText.autofillHints}
final
Iterable
<
String
>
autofillHints
;
@override
_CupertinoTextFieldState
createState
()
=>
_CupertinoTextFieldState
();
...
...
@@ -950,6 +954,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
scrollController:
widget
.
scrollController
,
scrollPhysics:
widget
.
scrollPhysics
,
enableInteractiveSelection:
widget
.
enableInteractiveSelection
,
autofillHints:
widget
.
autofillHints
,
),
),
);
...
...
packages/flutter/lib/src/material/text_field.dart
View file @
e31f7089
...
...
@@ -346,6 +346,7 @@ class TextField extends StatefulWidget {
this
.
buildCounter
,
this
.
scrollController
,
this
.
scrollPhysics
,
this
.
autofillHints
,
})
:
assert
(
textAlign
!=
null
),
assert
(
readOnly
!=
null
),
assert
(
autofocus
!=
null
),
...
...
@@ -710,6 +711,9 @@ class TextField extends StatefulWidget {
/// {@macro flutter.widgets.editableText.scrollController}
final
ScrollController
scrollController
;
/// {@macro flutter.widgets.editableText.autofillHints}
final
Iterable
<
String
>
autofillHints
;
@override
_TextFieldState
createState
()
=>
_TextFieldState
();
...
...
@@ -1049,6 +1053,7 @@ class _TextFieldState extends State<TextField> implements TextSelectionGestureDe
dragStartBehavior:
widget
.
dragStartBehavior
,
scrollController:
widget
.
scrollController
,
scrollPhysics:
widget
.
scrollPhysics
,
autofillHints:
widget
.
autofillHints
,
autocorrectionTextRectColor:
autocorrectionTextRectColor
,
),
);
...
...
packages/flutter/lib/src/services/autofill.dart
0 → 100644
View file @
e31f7089
// Copyright 2014 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.
import
'package:flutter/foundation.dart'
;
import
'text_input.dart'
;
/// A collection of commonly used autofill hint strings on different platforms.
///
/// Each hint may not be supported on every platform, and may get translated to
/// different strings on different platforms. Please refer to their documentation
/// for what each value corresponds to on different platforms.
class
AutofillHints
{
AutofillHints
.
_
();
/// The input field expects an address locality (city/town).
///
/// This hint will be translated to the below values on different platforms:
///
/// * Android: [AUTOFILL_HINT_POSTAL_ADDRESS_LOCALITY](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_POSTAL_ADDRESS_LOCALITY).
/// * iOS: [addressCity](https://developer.apple.com/documentation/uikit/uitextcontenttype).
/// * Otherwise, the hint string will be used as-is.
static
const
String
addressCity
=
'addressCity'
;
/// The input field expects a city name combined with a state name.
///
/// This hint will be translated to the below values on different platforms:
///
/// * iOS: [addressCityAndState](https://developer.apple.com/documentation/uikit/uitextcontenttype).
/// * Otherwise, the hint string will be used as-is.
static
const
String
addressCityAndState
=
'addressCityAndState'
;
/// The input field expects a region/state.
///
/// This hint will be translated to the below values on different platforms:
///
/// * Android: [AUTOFILL_HINT_POSTAL_ADDRESS_REGION](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_POSTAL_ADDRESS_REGION).
/// * iOS: [addressState](https://developer.apple.com/documentation/uikit/uitextcontenttype).
/// * Otherwise, the hint string will be used as-is.
static
const
String
addressState
=
'addressState'
;
/// The input field expects a person's full birth date.
///
/// This hint will be translated to the below values on different platforms:
///
/// * Android: [AUTOFILL_HINT_BIRTH_DATE_FULL](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_BIRTH_DATE_FULL).
/// * web: ["bday"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
birthday
=
'birthday'
;
/// The input field expects a person's birth day(of the month).
///
/// This hint will be translated to the below values on different platforms:
///
/// * Android: [AUTOFILL_HINT_BIRTH_DATE_DAY](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_BIRTH_DATE_DAY).
/// * web: ["bday-day"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
birthdayDay
=
'birthdayDay'
;
/// The input field expects a person's birth month.
///
/// This hint will be translated to the below values on different platforms:
///
/// * Android: [AUTOFILL_HINT_BIRTH_DATE_MONTH](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_BIRTH_DATE_MONTH).
/// * web: ["bday-month"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
birthdayMonth
=
'birthdayMonth'
;
/// The input field expects a person's birth year.
///
/// This hint will be translated to the below values on different platforms:
///
/// * Android: [AUTOFILL_HINT_BIRTH_DATE_YEAR](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_BIRTH_DATE_YEAR).
/// * web: ["bday-year"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
birthdayYear
=
'birthdayYear'
;
/// The input field expects an
/// [ISO 3166-1-alpha-2](https://www.iso.org/standard/63545.html) country code.
///
/// This hint will be translated to the below values on different platforms:
///
/// * web: ["country"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
countryCode
=
'countryCode'
;
/// The input field expects a country name.
///
/// This hint will be translated to the below values on different platforms:
///
/// * Android: [AUTOFILL_HINT_POSTAL_ADDRESS_COUNTRY](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_POSTAL_ADDRESS_COUNTRY).
/// * iOS: [countryName](https://developer.apple.com/documentation/uikit/uitextcontenttype).
/// * web: ["country-name"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
countryName
=
'countryName'
;
/// The input field expects a credit card expiration date.
///
/// This hint will be translated to the below values on different platforms:
///
/// * Android: [AUTOFILL_HINT_CREDIT_CARD_NUMBER](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_CREDIT_CARD_NUMBER).
/// * web: ["cc-exp"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
creditCardExpirationDate
=
'creditCardExpirationDate'
;
/// The input field expects a credit card expiration day.
///
/// This hint will be translated to the below values on different platforms:
///
/// * Android: [AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY).
/// * Otherwise, the hint string will be used as-is.
static
const
String
creditCardExpirationDay
=
'creditCardExpirationDay'
;
/// The input field expects a credit card expiration month.
///
/// This hint will be translated to the below values on different platforms:
///
/// * Android: [AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH).
/// * web: ["cc-exp-month"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
creditCardExpirationMonth
=
'creditCardExpirationMonth'
;
/// The input field expects a credit card expiration year.
///
/// This hint will be translated to the below values on different platforms:
///
/// * Android: [AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR).
/// * web: ["cc-exp-year"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
creditCardExpirationYear
=
'creditCardExpirationYear'
;
/// The input field expects the holder's last/family name as given on a credit
/// card.
///
/// This hint will be translated to the below values on different platforms:
///
/// * web: ["cc-family-name"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
creditCardFamilyName
=
'creditCardFamilyName'
;
/// The input field expects the holder's first/given name as given on a credit
/// card.
///
/// This hint will be translated to the below values on different platforms:
///
/// * web: ["cc-given-name"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
creditCardGivenName
=
'creditCardGivenName'
;
/// The input field expects the holder's middle name as given on a credit
/// card.
///
/// This hint will be translated to the below values on different platforms:
///
/// * web: ["cc-additional-name"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
creditCardMiddleName
=
'creditCardMiddleName'
;
/// The input field expects the holder's full name as given on a credit card.
///
/// This hint will be translated to the below values on different platforms:
///
/// * web: ["cc-name"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
creditCardName
=
'creditCardName'
;
/// The input field expects a credit card number.
///
/// This hint will be translated to the below values on different platforms:
///
/// * Android: [AUTOFILL_HINT_CREDIT_CARD_NUMBER](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_CREDIT_CARD_NUMBER).
/// * iOS: [creditCardNumber](https://developer.apple.com/documentation/uikit/uitextcontenttype).
/// * web: ["cc-number"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
creditCardNumber
=
'creditCardNumber'
;
/// The input field expects a credit card security code.
///
/// This hint will be translated to the below values on different platforms:
///
/// * Android: [AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE).
/// * web: ["cc-csc"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
creditCardSecurityCode
=
'creditCardSecurityCode'
;
/// The input field expects the type of a credit card, for example "Visa".
///
/// This hint will be translated to the below values on different platforms:
///
/// * web: ["cc-type"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
creditCardType
=
'creditCardType'
;
/// The input field expects an email address.
///
/// This hint will be translated to the below values on different platforms:
///
/// * Android: [AUTOFILL_HINT_EMAIL_ADDRESS](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_EMAIL_ADDRESS).
/// * iOS: [emailAddress](https://developer.apple.com/documentation/uikit/uitextcontenttype).
/// * web: ["email"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
email
=
'email'
;
/// The input field expects a person's last/family name.
///
/// This hint will be translated to the below values on different platforms:
///
/// * Android: [AUTOFILL_HINT_PERSON_NAME_FAMILY](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PERSON_NAME_FAMILY).
/// * iOS: [familyName](https://developer.apple.com/documentation/uikit/uitextcontenttype).
/// * web: ["family-name"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
familyName
=
'familyName'
;
/// The input field expects a street address that fully identifies a location.
///
/// This hint will be translated to the below values on different platforms:
///
/// * Android: [AUTOFILL_HINT_POSTAL_ADDRESS_STREET_ADDRESS](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_POSTAL_ADDRESS_STREET_ADDRESS).
/// * iOS: [fullStreetAddress](https://developer.apple.com/documentation/uikit/uitextcontenttype).
/// * web: ["street-address"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
fullStreetAddress
=
'fullStreetAddress'
;
/// The input field expects a gender.
///
/// This hint will be translated to the below values on different platforms:
///
/// * Android: [AUTOFILL_HINT_GENDER](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_GENDER).
/// * web: ["sex"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
gender
=
'gender'
;
/// The input field expects a person's first/given name.
///
/// This hint will be translated to the below values on different platforms:
///
/// * Android: [AUTOFILL_HINT_PERSON_NAME_GIVEN](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PERSON_NAME_GIVEN).
/// * iOS: [givenName](https://developer.apple.com/documentation/uikit/uitextcontenttype).
/// * web: ["given-name"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
givenName
=
'givenName'
;
/// The input field expects a URL representing an instant messaging protocol
/// endpoint.
///
/// This hint will be translated to the below values on different platforms:
///
/// * web: ["impp"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
impp
=
'impp'
;
/// The input field expects a job title.
///
/// This hint will be translated to the below values on different platforms:
///
/// * iOS: [jobTitle](https://developer.apple.com/documentation/uikit/uitextcontenttype).
/// * web: ["organization-title"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
jobTitle
=
'jobTitle'
;
/// The input field expects the preferred language of the user.
///
/// This hint will be translated to the below values on different platforms:
///
/// * web: ["language"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
language
=
'language'
;
/// The input field expects a location, such as a point of interest, an
/// address,or another way to identify a location.
///
/// This hint will be translated to the below values on different platforms:
///
/// * iOS: [location](https://developer.apple.com/documentation/uikit/uitextcontenttype).
/// * Otherwise, the hint string will be used as-is.
static
const
String
location
=
'location'
;
/// The input field expects a person's middle initial.
///
/// This hint will be translated to the below values on different platforms:
///
/// * Android: [AUTOFILL_HINT_PERSON_NAME_MIDDLE_INITIAL](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PERSON_NAME_MIDDLE_INITIAL).
/// * Otherwise, the hint string will be used as-is.
static
const
String
middleInitial
=
'middleInitial'
;
/// The input field expects a person's middle name.
///
/// This hint will be translated to the below values on different platforms:
///
/// * Android: [AUTOFILL_HINT_PERSON_NAME_MIDDLE](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PERSON_NAME_MIDDLE).
/// * iOS: [middleName](https://developer.apple.com/documentation/uikit/uitextcontenttype).
/// * web: ["additional-name"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
middleName
=
'middleName'
;
/// The input field expects a person's full name.
///
/// This hint will be translated to the below values on different platforms:
///
/// * Android: [AUTOFILL_HINT_PERSON_NAME](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PERSON_NAME).
/// * iOS: [name](https://developer.apple.com/documentation/uikit/uitextcontenttype).
/// * web: ["name"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
name
=
'name'
;
/// The input field expects a person's name prefix or title, such as "Dr.".
///
/// This hint will be translated to the below values on different platforms:
///
/// * Android: [AUTOFILL_HINT_PERSON_NAME_PREFIX](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PERSON_NAME_PREFIX).
/// * iOS: [namePrefix](https://developer.apple.com/documentation/uikit/uitextcontenttype).
/// * web: ["honorific-prefix"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
namePrefix
=
'namePrefix'
;
/// The input field expects a person's name suffix, such as "Jr.".
///
/// This hint will be translated to the below values on different platforms:
///
/// * Android: [AUTOFILL_HINT_PERSON_NAME_SUFFIX](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PERSON_NAME_SUFFIX).
/// * iOS: [nameSuffix](https://developer.apple.com/documentation/uikit/uitextcontenttype).
/// * web: ["honorific-suffix"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
nameSuffix
=
'nameSuffix'
;
/// The input field expects a newly created password for save/update.
///
/// This hint will be translated to the below values on different platforms:
///
/// * Android: [AUTOFILL_HINT_NEW_PASSWORD](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_NEW_PASSWORD).
/// * iOS: [newPassword](https://developer.apple.com/documentation/uikit/uitextcontenttype).
/// * web: ["new-password"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
newPassword
=
'newPassword'
;
/// The input field expects a newly created username for save/update.
///
/// This hint will be translated to the below values on different platforms:
///
/// * Android: [AUTOFILL_HINT_NEW_USERNAME](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_NEW_USERNAME).
/// * Otherwise, the hint string will be used as-is.
static
const
String
newUsername
=
'newUsername'
;
/// The input field expects a nickname.
///
/// This hint will be translated to the below values on different platforms:
///
/// * iOS: [nickname](https://developer.apple.com/documentation/uikit/uitextcontenttype).
/// * web: ["nickname"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
nickname
=
'nickname'
;
/// The input field expects a single-factor SMS login code.
///
/// This hint will be translated to the below values on different platforms:
///
/// * Android: [AUTOFILL_HINT_SMS_OTP](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_SMS_OTP).
/// * iOS: [oneTimeCode](https://developer.apple.com/documentation/uikit/uitextcontenttype).
/// * web: ["one-time-code"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
oneTimeCode
=
'oneTimeCode'
;
/// The input field expects an organization name corresponding to the person,
/// address, or contact information in the other fields associated with this
/// field.
///
/// This hint will be translated to the below values on different platforms:
///
/// * iOS: [organizationName](https://developer.apple.com/documentation/uikit/uitextcontenttype).
/// * web: ["organization"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
organizationName
=
'organizationName'
;
/// The input field expects a password.
///
/// This hint will be translated to the below values on different platforms:
///
/// * Android: [AUTOFILL_HINT_PASSWORD](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PASSWORD).
/// * iOS: [password](https://developer.apple.com/documentation/uikit/uitextcontenttype).
/// * web: ["current-password"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
password
=
'password'
;
/// The input field expects a photograph, icon, or other image corresponding
/// to the company, person, address, or contact information in the other
/// fields associated with this field.
///
/// This hint will be translated to the below values on different platforms:
///
/// * web: ["photo"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
photo
=
'photo'
;
/// The input field expects a postal address.
///
/// This hint will be translated to the below values on different platforms:
///
/// * Android: [AUTOFILL_HINT_POSTAL_ADDRESS](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_POSTAL_ADDRESS).
/// * Otherwise, the hint string will be used as-is.
static
const
String
postalAddress
=
'postalAddress'
;
/// The input field expects an auxiliary address details.
///
/// This hint will be translated to the below values on different platforms:
///
/// * Android: [AUTOFILL_HINT_POSTAL_ADDRESS_EXTENDED_ADDRESS](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_POSTAL_ADDRESS_EXTENDED_ADDRESS).
/// * Otherwise, the hint string will be used as-is.
static
const
String
postalAddressExtended
=
'postalAddressExtended'
;
/// The input field expects an extended ZIP/POSTAL code.
///
/// This hint will be translated to the below values on different platforms:
///
/// * Android: [AUTOFILL_HINT_POSTAL_ADDRESS_EXTENDED_POSTAL_CODE](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_POSTAL_ADDRESS_EXTENDED_POSTAL_CODE).
/// * Otherwise, the hint string will be used as-is.
static
const
String
postalAddressExtendedPostalCode
=
'postalAddressExtendedPostalCode'
;
/// The input field expects a postal code.
///
/// This hint will be translated to the below values on different platforms:
///
/// * Android: [AUTOFILL_HINT_POSTAL_CODE](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_POSTAL_CODE).
/// * iOS: [postalCode](https://developer.apple.com/documentation/uikit/uitextcontenttype).
/// * web: ["postal-code"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
postalCode
=
'postalCode'
;
/// The first administrative level in the address. This is typically the
/// province in which the address is located. In the United States, this would
/// be the state. In Switzerland, the canton. In the United Kingdom, the post
/// town.
///
/// This hint will be translated to the below values on different platforms:
///
/// * web: ["address-level1"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
streetAddressLevel1
=
'streetAddressLevel1'
;
/// The second administrative level, in addresses with at least two of them.
/// In countries with two administrative levels, this would typically be the
/// city, town, village, or other locality in which the address is located.
///
/// This hint will be translated to the below values on different platforms:
///
/// * web: ["address-level2"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
streetAddressLevel2
=
'streetAddressLevel2'
;
/// The third administrative level, in addresses with at least three
/// administrative levels.
///
/// This hint will be translated to the below values on different platforms:
///
/// * web: ["address-level3"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
streetAddressLevel3
=
'streetAddressLevel3'
;
/// The finest-grained administrative level, in addresses which have four
/// levels.
///
/// This hint will be translated to the below values on different platforms:
///
/// * web: ["address-level4"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
streetAddressLevel4
=
'streetAddressLevel4'
;
/// The input field expects the first line of a street address.
///
/// This hint will be translated to the below values on different platforms:
///
/// * iOS: [streetAddressLine1](https://developer.apple.com/documentation/uikit/uitextcontenttype).
/// * web: ["address-line1"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
streetAddressLine1
=
'streetAddressLine1'
;
/// The input field expects the second line of a street address.
///
/// This hint will be translated to the below values on different platforms:
///
/// * iOS: [streetAddressLine2](https://developer.apple.com/documentation/uikit/uitextcontenttype).
/// * web: ["address-line2"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
streetAddressLine2
=
'streetAddressLine2'
;
/// The input field expects the third line of a street address.
///
/// This hint will be translated to the below values on different platforms:
///
/// * web: ["address-line3"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
streetAddressLine3
=
'streetAddressLine3'
;
/// The input field expects a sublocality.
///
/// This hint will be translated to the below values on different platforms:
///
/// * iOS: [sublocality](https://developer.apple.com/documentation/uikit/uitextcontenttype).
/// * Otherwise, the hint string will be used as-is.
static
const
String
sublocality
=
'sublocality'
;
/// The input field expects a telephone number.
///
/// This hint will be translated to the below values on different platforms:
///
/// * Android: [AUTOFILL_HINT_PHONE_NUMBER](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PHONE_NUMBER).
/// * iOS: [telephoneNumber](https://developer.apple.com/documentation/uikit/uitextcontenttype).
/// * web: ["tel"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
telephoneNumber
=
'telephoneNumber'
;
/// The input field expects a phone number's area code, with a country
/// -internal prefix applied if applicable.
///
/// This hint will be translated to the below values on different platforms:
///
/// * web: ["tel-area-code"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
telephoneNumberAreaCode
=
'telephoneNumberAreaCode'
;
/// The input field expects a phone number's country code.
///
/// This hint will be translated to the below values on different platforms:
///
/// * Android: [AUTOFILL_HINT_PHONE_COUNTRY_CODE](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PHONE_COUNTRY_CODE).
/// * web: ["tel-country-code"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
telephoneNumberCountryCode
=
'telephoneNumberCountryCode'
;
/// The input field expects the current device's phone number, usually for
/// Sign Up / OTP flows.
///
/// This hint will be translated to the below values on different platforms:
///
/// * Android: [AUTOFILL_HINT_PHONE_NUMBER_DEVICE](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PHONE_NUMBER_DEVICE).
/// * Otherwise, the hint string will be used as-is.
static
const
String
telephoneNumberDevice
=
'telephoneNumberDevice'
;
/// The input field expects a phone number's internal extension code.
///
/// This hint will be translated to the below values on different platforms:
///
/// * web: ["tel-extension"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
telephoneNumberExtension
=
'telephoneNumberExtension'
;
/// The input field expects a phone number without the country code and area
/// code components.
///
/// This hint will be translated to the below values on different platforms:
///
/// * web: ["tel-local"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
telephoneNumberLocal
=
'telephoneNumberLocal'
;
/// The input field expects the first part of the component of the telephone
/// number that follows the area code, when that component is split into two
/// components.
///
/// This hint will be translated to the below values on different platforms:
///
/// * web: ["tel-local-prefix"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
telephoneNumberLocalPrefix
=
'telephoneNumberLocalPrefix'
;
/// The input field expects the second part of the component of the telephone
/// number that follows the area code, when that component is split into two
/// components.
///
/// This hint will be translated to the below values on different platforms:
///
/// * web: ["tel-local-suffix"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
telephoneNumberLocalSuffix
=
'telephoneNumberLocalSuffix'
;
/// The input field expects a phone number without country code.
///
/// This hint will be translated to the below values on different platforms:
///
/// * Android: [AUTOFILL_HINT_PHONE_NATIONAL](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_PHONE_NATIONAL).
/// * web: ["tel-national"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
telephoneNumberNational
=
'telephoneNumberNational'
;
/// The amount that the user would like for the transaction (e.g. when
/// entering a bid or sale price).
///
/// This hint will be translated to the below values on different platforms:
///
/// * web: ["transaction-amount"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
transactionAmount
=
'transactionAmount'
;
/// The currency that the user would prefer the transaction to use, in [ISO
/// 4217 currency code](https://www.iso.org/iso-4217-currency-codes.html).
///
/// This hint will be translated to the below values on different platforms:
///
/// * web: ["transaction-currency"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
transactionCurrency
=
'transactionCurrency'
;
/// The input field expects a URL.
///
/// This hint will be translated to the below values on different platforms:
///
/// * iOS: [URL](https://developer.apple.com/documentation/uikit/uitextcontenttype).
/// * web: ["url"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
url
=
'url'
;
/// The input field expects a username or an account name.
///
/// This hint will be translated to the below values on different platforms:
///
/// * Android: [AUTOFILL_HINT_NEW_USERNAME](https://developer.android.com/reference/androidx/autofill/HintConstants#AUTOFILL_HINT_NEW_USERNAME).
/// * iOS: [username](https://developer.apple.com/documentation/uikit/uitextcontenttype).
/// * web: ["username"](https://www.w3.org/TR/html52/sec-forms.html#autofilling-form-controls-the-autocomplete-attribute).
/// * Otherwise, the hint string will be used as-is.
static
const
String
username
=
'username'
;
}
/// A collection of autofill related information that represents an [AutofillClient].
///
/// Typically used in [TextInputConfiguration.autofillConfiguration].
@immutable
class
AutofillConfiguration
{
/// Creates autofill related configuration information that can be sent to the
/// platform.
const
AutofillConfiguration
({
@required
this
.
uniqueIdentifier
,
@required
this
.
autofillHints
,
this
.
currentEditingValue
,
})
:
assert
(
uniqueIdentifier
!=
null
),
assert
(
autofillHints
!=
null
);
/// A string that uniquely identifies the current [AutofillClient].
///
/// The identifier needs to be unique within the [AutofillScope] for the
/// [AutofillClient] to receive the correct autofill value.
///
/// Must not be null.
final
String
uniqueIdentifier
;
/// A list of strings that helps the autofill service identify the type of the
/// [AutofillClient].
///
/// Must not be null or empty.
///
/// {@template flutter.services.autofill.autofillHints}
/// For the best results, hint strings need to be understood by the platform's
/// autofill service. The common values of hint strings can be found in
/// [AutofillHints], as well as the platforms that understand each of them.
///
/// If an autofillable input field needs to use a custom hint that translate to
/// different strings on different platforms, the easiest way to achieve that
/// is to return different hint strings based on the value of
/// [defaultTargetPlatform].
///
/// Each hint in the list, if not ignored, will be translated to the platform's
/// autofill hint type understood by its autofill services:
///
/// * On iOS, only the first hint in the list is accounted for. The hint will
/// be translated to a
/// [UITextContentType](https://developer.apple.com/documentation/uikit/uitextcontenttype).
///
/// * On Android, all hints in the list are translated to Android hint strings.
///
/// * On web, only the first hint is accounted for and will be translated to
/// an "autocomplete" string.
///
/// See also:
///
/// * [AutofillHints], a list of autofill hint strings that is predefined on at
/// least one platform.
///
/// * [UITextContentType](https://developer.apple.com/documentation/uikit/uitextcontenttype),
/// the iOS equivalent.
///
/// * Android [autofillHints](https://developer.android.com/reference/android/view/View#setAutofillHints(java.lang.String...)),
/// the Android equivalent.
///
/// * The [autocomplete](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete) attribute,
/// the web equivalent.
/// {@endtemplate}
final
List
<
String
>
autofillHints
;
/// The current [TextEditingValue] of the [AutofillClient].
final
TextEditingValue
currentEditingValue
;
/// Returns a representation of this object as a JSON object.
Map
<
String
,
dynamic
>
toJson
()
{
assert
(
autofillHints
.
isNotEmpty
);
return
<
String
,
dynamic
>{
'uniqueIdentifier'
:
uniqueIdentifier
,
'hints'
:
autofillHints
,
'editingValue'
:
currentEditingValue
.
toJSON
(),
};
}
}
/// An object that represents an autofillable input field in the autofill workflow.
///
/// An [AutofillClient] provides autofill-related information of the input field
/// it represents to the platform, and consumes autofill inputs from the platform.
abstract
class
AutofillClient
{
/// The unique identifier of this [AutofillClient].
///
/// Must not be null;
String
get
autofillId
;
/// The [TextInputConfiguration] that describes this [AutofillClient].
///
/// In order to participate in autofill, its
/// [TextInputConfiguration.autofillConfiguration] must not be null.
TextInputConfiguration
get
textInputConfiguration
;
/// Requests this [AutofillClient] update its [TextEditingState] to the given
/// state.
void
updateEditingValue
(
TextEditingValue
newEditingValue
);
}
/// An ordered group within which [AutofillClient]s are logically connected.
///
/// {@template flutter.services.autofill.AutofillScope}
/// [AutofillClient]s within the same [AutofillScope] are isolated from other
/// input fields during autofill. That is, when an autofillable [TextInputClient]
/// gains focus, only the [AutofillClient]s within the same [AutofillScope] will
/// be visible to the autofill service, in the same order as they appear in
/// [autofillClients].
///
/// [AutofillScope] also allows [TextInput] to redirect autofill values from the
/// platform to the [AutofillClient] with the given identifier, by calling
/// [getAutofillClient].
///
/// An [AutofillClient] that's not tied to any [AutofillScope] will only
/// participate in autofill if the autofill is directly triggered by its own
/// [TextInputClient].
/// {@endtemplate}
abstract
class
AutofillScope
{
/// Gets the [AutofillScope] associated with the given [autofillId], in
/// this [AutofillScope].
///
/// Returns null if there's no matching [AutofillClient].
AutofillClient
getAutofillClient
(
String
autofillId
);
/// The collection of [AutofillClient]s currently tied to this [AutofillScope].
///
/// Every [AutofillClient] in this list must have autofill enabled (i.e. its
/// [AutofillClient.textInputConfiguration] must have a non-null
/// [AutofillConfiguration].)
Iterable
<
AutofillClient
>
get
autofillClients
;
/// Allows a [TextInputClient] to attach to this scope. This method should be
/// called in lieu of [TextInput.attach], when the [TextInputClient] wishes to
/// participate in autofill.
TextInputConnection
attach
(
TextInputClient
trigger
,
TextInputConfiguration
configuration
);
}
@immutable
class
_AutofillScopeTextInputConfiguration
extends
TextInputConfiguration
{
_AutofillScopeTextInputConfiguration
({
@required
this
.
allConfigurations
,
@required
TextInputConfiguration
currentClientConfiguration
,
})
:
assert
(
allConfigurations
!=
null
),
assert
(
currentClientConfiguration
!=
null
),
super
(
inputType:
currentClientConfiguration
.
inputType
,
obscureText:
currentClientConfiguration
.
obscureText
,
autocorrect:
currentClientConfiguration
.
autocorrect
,
smartDashesType:
currentClientConfiguration
.
smartDashesType
,
smartQuotesType:
currentClientConfiguration
.
smartQuotesType
,
enableSuggestions:
currentClientConfiguration
.
enableSuggestions
,
inputAction:
currentClientConfiguration
.
inputAction
,
textCapitalization:
currentClientConfiguration
.
textCapitalization
,
keyboardAppearance:
currentClientConfiguration
.
keyboardAppearance
,
actionLabel:
currentClientConfiguration
.
actionLabel
,
autofillConfiguration:
currentClientConfiguration
.
autofillConfiguration
,
);
final
Iterable
<
TextInputConfiguration
>
allConfigurations
;
@override
Map
<
String
,
dynamic
>
toJson
()
{
final
Map
<
String
,
dynamic
>
result
=
super
.
toJson
();
result
[
'fields'
]
=
allConfigurations
.
map
((
TextInputConfiguration
configuration
)
=>
configuration
.
toJson
())
.
toList
(
growable:
false
);
return
result
;
}
}
/// A partial implementation of [AutofillScope].
///
/// The mixin provides a default implementation for [AutofillScope.attach].
mixin
AutofillScopeMixin
implements
AutofillScope
{
@override
TextInputConnection
attach
(
TextInputClient
trigger
,
TextInputConfiguration
configuration
)
{
assert
(
trigger
!=
null
);
assert
(
!
autofillClients
.
any
((
AutofillClient
client
)
=>
client
.
textInputConfiguration
.
autofillConfiguration
==
null
),
'Every client in AutofillScope.autofillClients must enable autofill'
,
);
return
TextInput
.
attach
(
trigger
,
_AutofillScopeTextInputConfiguration
(
allConfigurations:
autofillClients
.
map
((
AutofillClient
client
)
=>
client
.
textInputConfiguration
),
currentClientConfiguration:
configuration
,
),
);
}
}
packages/flutter/lib/src/services/text_input.dart
View file @
e31f7089
...
...
@@ -16,6 +16,7 @@ import 'dart:ui' show
import
'package:flutter/foundation.dart'
;
import
'package:vector_math/vector_math_64.dart'
show
Matrix4
;
import
'autofill.dart'
;
import
'message_codec.dart'
;
import
'platform_channel.dart'
;
import
'system_channels.dart'
;
...
...
@@ -441,6 +442,7 @@ class TextInputConfiguration {
this
.
inputAction
=
TextInputAction
.
done
,
this
.
keyboardAppearance
=
Brightness
.
light
,
this
.
textCapitalization
=
TextCapitalization
.
none
,
this
.
autofillConfiguration
,
})
:
assert
(
inputType
!=
null
),
assert
(
obscureText
!=
null
),
smartDashesType
=
smartDashesType
??
(
obscureText
?
SmartDashesType
.
disabled
:
SmartDashesType
.
enabled
),
...
...
@@ -464,6 +466,14 @@ class TextInputConfiguration {
/// Defaults to true.
final
bool
autocorrect
;
/// The configuration to use for autofill.
///
/// Defaults to null, in which case no autofill information will be provided
/// to the platform. This will prevent the corresponding input field from
/// participating in autofills triggered by other fields. Additionally, on
/// Android and web, setting [autofillConfiguration] to null disables autofill.
final
AutofillConfiguration
autofillConfiguration
;
/// {@template flutter.services.textInput.smartDashesType}
/// Whether to allow the platform to automatically format dashes.
///
...
...
@@ -565,6 +575,7 @@ class TextInputConfiguration {
'inputAction'
:
inputAction
.
toString
(),
'textCapitalization'
:
textCapitalization
.
toString
(),
'keyboardAppearance'
:
keyboardAppearance
.
toString
(),
if
(
autofillConfiguration
!=
null
)
'autofill'
:
autofillConfiguration
.
toJson
(),
};
}
}
...
...
@@ -745,6 +756,21 @@ abstract class TextInputClient {
/// const constructors so that they can be used in const expressions.
const
TextInputClient
();
/// The current state of the [TextEditingValue] held by this client.
TextEditingValue
get
currentTextEditingValue
;
/// The [AutofillScope] this [TextInputClient] belongs to, if any.
///
/// It should return null if this [TextInputClient] does not need autofill
/// support. For a [TextInputClient] that supports autofill, returning null
/// causes it to participate in autofill alone.
///
/// See also:
///
/// * [AutofillGroup], a widget that creates an [AutofillScope] for its
/// descendent autofillable [TextInputClient]s.
AutofillScope
get
currentAutofillScope
;
/// Requests that this client update its editing state to the given value.
void
updateEditingValue
(
TextEditingValue
value
);
...
...
@@ -754,9 +780,6 @@ abstract class TextInputClient {
/// Updates the floating cursor position and state.
void
updateFloatingCursor
(
RawFloatingCursorPoint
point
);
/// The current state of the [TextEditingValue] held by this client.
TextEditingValue
get
currentTextEditingValue
;
/// Requests that this client display a prompt rectangle for the given text range,
/// to indicate the range of text that will be changed by a pending autocorrection.
///
...
...
@@ -809,6 +832,17 @@ class TextInputConnection {
TextInput
.
_instance
.
_show
();
}
/// Requests the platform autofill UI to appear.
///
/// The call has no effect unless the currently attached client supports
/// autofill, and the platform has a standalone autofill UI (for example, this
/// call has no effect on iOS since its autofill UI is part of the software
/// keyboard).
void
requestAutofill
()
{
assert
(
attached
);
TextInput
.
_instance
.
_requestAutofill
();
}
/// Requests that the text input control change its internal state to match the given state.
void
setEditingState
(
TextEditingValue
value
)
{
assert
(
attached
);
...
...
@@ -1065,6 +1099,22 @@ class TextInput {
}
final
List
<
dynamic
>
args
=
methodCall
.
arguments
as
List
<
dynamic
>;
if
(
method
==
'TextInputClient.updateEditingStateWithTag'
)
{
final
TextInputClient
client
=
_currentConnection
.
_client
;
assert
(
client
!=
null
);
final
AutofillScope
scope
=
client
.
currentAutofillScope
;
final
Map
<
String
,
dynamic
>
editingValue
=
args
[
1
]
as
Map
<
String
,
dynamic
>;
for
(
final
String
tag
in
editingValue
.
keys
)
{
final
TextEditingValue
textEditingValue
=
TextEditingValue
.
fromJSON
(
editingValue
[
tag
]
as
Map
<
String
,
dynamic
>,
);
scope
?.
getAutofillClient
(
tag
)?.
updateEditingValue
(
textEditingValue
);
}
return
;
}
final
int
client
=
args
[
0
]
as
int
;
// The incoming message was for a different client.
if
(
client
!=
_currentConnection
.
_id
)
...
...
@@ -1128,6 +1178,10 @@ class TextInput {
_channel
.
invokeMethod
<
void
>(
'TextInput.show'
);
}
void
_requestAutofill
()
{
_channel
.
invokeMethod
<
void
>(
'TextInput.requestAutofill'
);
}
void
_setEditableSizeAndTransform
(
Map
<
String
,
dynamic
>
args
)
{
_channel
.
invokeMethod
<
void
>(
'TextInput.setEditableSizeAndTransform'
,
...
...
packages/flutter/lib/src/widgets/autofill.dart
0 → 100644
View file @
e31f7089
// Copyright 2014 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.
import
'package:flutter/services.dart'
;
import
'framework.dart'
;
export
'package:flutter/services.dart'
show
AutofillHints
;
/// An [AutofillScope] widget that groups [AutofillClient]s together.
///
/// [AutofillClient]s within the same [AutofillScope] must be built together, and
/// they be will be autofilled together.
///
/// {@macro flutter.services.autofill.AutofillScope}
///
/// The [AutofillGroup] widget only knows about [AutofillClient]s registered to
/// it using the [AutofillGroupState.register] API. Typically, [AutofillGroup]
/// will not pick up [AutofillClient]s that are not mounted, for example, an
/// [AutofillClient] within a [Scrollable] that has never been scrolled into the
/// viewport. To workaround this problem, ensure clients in the same [AutofillGroup]
/// are built together:
///
/// {@tool dartpad --template=stateful_widget_material}
///
/// An example form with autofillable fields grouped into different `AutofillGroup`s.
///
/// ```dart
/// bool isSameAddress = true;
/// final TextEditingController shippingAddress1 = TextEditingController();
/// final TextEditingController shippingAddress2 = TextEditingController();
/// final TextEditingController billingAddress1 = TextEditingController();
/// final TextEditingController billingAddress2 = TextEditingController();
///
/// final TextEditingController creditCardNumber = TextEditingController();
/// final TextEditingController creditCardSecurityCode = TextEditingController();
///
/// final TextEditingController phoneNumber = TextEditingController();
///
/// @override
/// Widget build(BuildContext context) {
/// return ListView(
/// children: <Widget>[
/// const Text('Shipping address'),
/// // The address fields are grouped together as some platforms are capable
/// // of autofilling all these fields in one go.
/// AutofillGroup(
/// child: Column(
/// children: <Widget>[
/// TextField(
/// controller: shippingAddress1,
/// autofillHints: <String>[AutofillHints.streetAddressLine1],
/// ),
/// TextField(
/// controller: shippingAddress2,
/// autofillHints: <String>[AutofillHints.streetAddressLine2],
/// ),
/// ],
/// ),
/// ),
/// const Text('Billing address'),
/// Checkbox(
/// value: isSameAddress,
/// onChanged: (bool newValue) {
/// setState(() { isSameAddress = newValue; });
/// },
/// ),
/// // Again the address fields are grouped together for the same reason.
/// if (!isSameAddress) AutofillGroup(
/// child: Column(
/// children: <Widget>[
/// TextField(
/// controller: billingAddress1,
/// autofillHints: <String>[AutofillHints.streetAddressLine1],
/// ),
/// TextField(
/// controller: billingAddress2,
/// autofillHints: <String>[AutofillHints.streetAddressLine2],
/// ),
/// ],
/// ),
/// ),
/// const Text('Credit Card Information'),
/// // The credit card number and the security code are grouped together as
/// // some platforms are capable of autofilling both fields.
/// AutofillGroup(
/// child: Column(
/// children: <Widget>[
/// TextField(
/// controller: creditCardNumber,
/// autofillHints: <String>[AutofillHints.creditCardNumber],
/// ),
/// TextField(
/// controller: creditCardSecurityCode,
/// autofillHints: <String>[AutofillHints.creditCardSecurityCode],
/// ),
/// ],
/// ),
/// ),
/// const Text('Contact Phone Number'),
/// // The phone number field can still be autofilled despite lacking an
/// // `AutofillScope`.
/// TextField(
/// controller: phoneNumber,
/// autofillHints: <String>[AutofillHints.telephoneNumber],
/// ),
/// ],
/// );
/// }
/// ```
/// {@end-tool}
class
AutofillGroup
extends
StatefulWidget
{
/// Creates a scope for autofillable input fields.
///
/// The [child] argument must not be null.
const
AutofillGroup
({
Key
key
,
@required
this
.
child
,
})
:
assert
(
child
!=
null
),
super
(
key:
key
);
/// Returns the closest [AutofillGroupState] which encloses the given context.
///
/// {@macro flutter.widgets.autofill.AutofillGroupState}
///
/// See also:
///
/// * [EditableTextState], where this method is used to retrive the closest
/// [AutofillGroupState].
static
AutofillGroupState
of
(
BuildContext
context
)
{
final
_AutofillScope
scope
=
context
.
dependOnInheritedWidgetOfExactType
<
_AutofillScope
>();
return
scope
?.
_scope
;
}
/// {@macro flutter.widgets.child}
final
Widget
child
;
@override
AutofillGroupState
createState
()
=>
AutofillGroupState
();
}
/// State associated with an [AutofillGroup] widget.
///
/// {@template flutter.widgets.autofill.AutofillGroupState}
/// An [AutofillGroupState] can be used to register an [AutofillClient] when it
/// enters this [AutofillGroup] (for example, when an [EditableText] is mounted or
/// reparented onto the [AutofillGroup]'s subtree), and unregister an
/// [AutofillClient] when it exits (for example, when an [EditableText] gets
/// unmounted or reparented out of the [AutofillGroup]'s subtree).
///
/// The [AutofillGroupState] class also provides an [attach] method that can be
/// called by [TextInputClient]s that support autofill, instead of
/// [TextInputClient.attach], to create a [TextInputConnection] to interact with
/// the platform's text input system.
/// {@endtemplate}
///
/// Typically obtained using [AutofillGroup.of].
class
AutofillGroupState
extends
State
<
AutofillGroup
>
with
AutofillScopeMixin
{
final
Map
<
String
,
AutofillClient
>
_clients
=
<
String
,
AutofillClient
>{};
@override
AutofillClient
getAutofillClient
(
String
tag
)
=>
_clients
[
tag
];
@override
Iterable
<
AutofillClient
>
get
autofillClients
{
return
_clients
.
values
.
where
((
AutofillClient
client
)
=>
client
?.
textInputConfiguration
?.
autofillConfiguration
!=
null
);
}
/// Adds the [AutofillClient] to this [AutofillGroup].
///
/// Typically, this is called by [TextInputClient]s that support autofill (for
/// example, [EditableTextState]) in [State.didChangeDependencies], when the
/// input field should be registered to a new [AutofillGroup].
///
/// See also:
///
/// * [EditableTextState.didChangeDependencies], where this method is called
/// to update the current [AutofillScope] when needed.
void
register
(
AutofillClient
client
)
{
assert
(
client
!=
null
);
_clients
.
putIfAbsent
(
client
.
autofillId
,
()
=>
client
);
}
/// Removes an [AutofillClient] with the given [autofillId] from this
/// [AutofillGroup].
///
/// Typically, this should be called by autofillable [TextInputClient]s in
/// [State.dispose] and [State.didChangeDependencies], when the input field
/// needs to be removed from the [AutofillGroup] it is currently registered to.
///
/// See also:
///
/// * [EditableTextState.didChangeDependencies], where this method is called
/// to unregister from the previous [AutofillScope].
/// * [EditableTextState.dispose], where this method is called to unregister
/// from the current [AutofillScope] when the widget is about to be removed
/// from the tree.
void
unregister
(
String
autofillId
)
{
assert
(
autofillId
!=
null
&&
_clients
.
containsKey
(
autofillId
));
_clients
.
remove
(
autofillId
);
}
@override
Widget
build
(
BuildContext
context
)
{
return
_AutofillScope
(
autofillScopeState:
this
,
child:
widget
.
child
,
);
}
}
class
_AutofillScope
extends
InheritedWidget
{
const
_AutofillScope
({
Key
key
,
Widget
child
,
AutofillGroupState
autofillScopeState
,
})
:
_scope
=
autofillScopeState
,
super
(
key:
key
,
child:
child
);
final
AutofillGroupState
_scope
;
AutofillGroup
get
client
=>
_scope
.
widget
;
@override
bool
updateShouldNotify
(
_AutofillScope
old
)
=>
_scope
!=
old
.
_scope
;
}
packages/flutter/lib/src/widgets/editable_text.dart
View file @
e31f7089
...
...
@@ -7,12 +7,13 @@ import 'dart:math' as math;
import
'dart:ui'
as
ui
hide
TextStyle
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/gestures.dart'
show
DragStartBehavior
;
import
'package:flutter/painting.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/scheduler.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter/gestures.dart'
show
DragStartBehavior
;
import
'autofill.dart'
;
import
'automatic_keep_alive.dart'
;
import
'basic.dart'
;
import
'binding.dart'
;
...
...
@@ -29,8 +30,8 @@ import 'scrollable.dart';
import
'text_selection.dart'
;
import
'ticker_provider.dart'
;
export
'package:flutter/services.dart'
show
TextEditingValue
,
TextSelection
,
TextInputType
,
SmartQuotesType
,
SmartDashesType
;
export
'package:flutter/rendering.dart'
show
SelectionChangedCause
;
export
'package:flutter/services.dart'
show
TextEditingValue
,
TextSelection
,
TextInputType
,
SmartQuotesType
,
SmartDashesType
;
/// Signature for the callback that reports when the user changes the selection
/// (including the cursor location).
...
...
@@ -409,6 +410,7 @@ class EditableText extends StatefulWidget {
paste:
true
,
selectAll:
true
,
),
this
.
autofillHints
,
})
:
assert
(
controller
!=
null
),
assert
(
focusNode
!=
null
),
assert
(
obscureText
!=
null
),
...
...
@@ -1079,6 +1081,23 @@ class EditableText extends StatefulWidget {
/// {@macro flutter.rendering.editable.selectionEnabled}
bool
get
selectionEnabled
=>
enableInteractiveSelection
;
/// {@template flutter.widgets.editableText.autofillHints}
/// A list of strings that helps the autofill service identify the type of this
/// text input.
///
/// When set to null or empty, the text input will not send any autofill related
/// information to the platform. As a result, it will not participate in
/// autofills triggered by a different [AutofillClient], even if they're in the
/// same [AutofillScope]. Additionally, on Android and web, setting this to null
/// or empty will disable autofill for this text field.
///
/// The minimum platform SDK version that supports Autofill is API level 26
/// for Android, and iOS 10.0 for iOS.
///
/// {@macro flutter.services.autofill.autofillHints}
/// {@endtemplate}
final
Iterable
<
String
>
autofillHints
;
@override
EditableTextState
createState
()
=>
EditableTextState
();
...
...
@@ -1104,11 +1123,12 @@ class EditableText extends StatefulWidget {
properties
.
add
(
DiagnosticsProperty
<
TextInputType
>(
'keyboardType'
,
keyboardType
,
defaultValue:
null
));
properties
.
add
(
DiagnosticsProperty
<
ScrollController
>(
'scrollController'
,
scrollController
,
defaultValue:
null
));
properties
.
add
(
DiagnosticsProperty
<
ScrollPhysics
>(
'scrollPhysics'
,
scrollPhysics
,
defaultValue:
null
));
properties
.
add
(
DiagnosticsProperty
<
Iterable
<
String
>>(
'autofillHints'
,
autofillHints
,
defaultValue:
null
));
}
}
/// State for a [EditableText].
class
EditableTextState
extends
State
<
EditableText
>
with
AutomaticKeepAliveClientMixin
<
EditableText
>,
WidgetsBindingObserver
,
TickerProviderStateMixin
<
EditableText
>
implements
Text
InputClient
,
TextSelectionDelegate
{
class
EditableTextState
extends
State
<
EditableText
>
with
AutomaticKeepAliveClientMixin
<
EditableText
>,
WidgetsBindingObserver
,
TickerProviderStateMixin
<
EditableText
>
implements
Text
SelectionDelegate
,
TextInputClient
,
AutofillClient
{
Timer
_cursorTimer
;
bool
_targetCursorVisibility
=
false
;
final
ValueNotifier
<
bool
>
_cursorVisibilityNotifier
=
ValueNotifier
<
bool
>(
true
);
...
...
@@ -1128,6 +1148,10 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
bool
_didAutoFocus
=
false
;
FocusAttachment
_focusAttachment
;
AutofillGroupState
_currentAutofillScope
;
@override
AutofillScope
get
currentAutofillScope
=>
_currentAutofillScope
;
// This value is an eyeball estimation of the time it takes for the iOS cursor
// to ease in and out.
static
const
Duration
_fadeDuration
=
Duration
(
milliseconds:
250
);
...
...
@@ -1175,6 +1199,14 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
@override
void
didChangeDependencies
()
{
super
.
didChangeDependencies
();
final
AutofillGroupState
newAutofillGroup
=
AutofillGroup
.
of
(
context
);
if
(
currentAutofillScope
!=
newAutofillGroup
)
{
_currentAutofillScope
?.
unregister
(
autofillId
);
_currentAutofillScope
=
newAutofillGroup
;
newAutofillGroup
?.
register
(
this
);
}
if
(!
_didAutoFocus
&&
widget
.
autofocus
)
{
_didAutoFocus
=
true
;
SchedulerBinding
.
instance
.
addPostFrameCallback
((
_
)
{
...
...
@@ -1210,6 +1242,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
if
(
oldWidget
.
readOnly
&&
_hasFocus
)
_openInputConnection
();
}
if
(
widget
.
style
!=
oldWidget
.
style
)
{
final
TextStyle
style
=
widget
.
style
;
// The _textInputConnection will pick up the new style when it attaches in
...
...
@@ -1228,6 +1261,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
@override
void
dispose
()
{
_currentAutofillScope
?.
unregister
(
autofillId
);
widget
.
controller
.
removeListener
(
_didChangeTextEditingValue
);
_cursorBlinkOpacityController
.
removeListener
(
_onCursorColorTick
);
_floatingCursorResetController
.
removeListener
(
_onFloatingCursorResetTick
);
...
...
@@ -1278,11 +1312,13 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
_formatAndSetValue
(
value
);
if
(
_hasInputConnection
)
{
// To keep the cursor from blinking while typing, we want to restart the
// cursor timer every time a new character is typed.
_stopCursorTimer
(
resetCharTicks:
false
);
_startCursorTimer
();
}
}
@override
void
performAction
(
TextInputAction
action
)
{
...
...
@@ -1465,26 +1501,16 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
if
(!
_hasInputConnection
)
{
final
TextEditingValue
localValue
=
_value
;
_lastFormattedUnmodifiedTextEditingValue
=
localValue
;
_textInputConnection
=
TextInput
.
attach
(
this
,
TextInputConfiguration
(
inputType:
widget
.
keyboardType
,
obscureText:
widget
.
obscureText
,
autocorrect:
widget
.
autocorrect
,
smartDashesType:
widget
.
smartDashesType
??
(
widget
.
obscureText
?
SmartDashesType
.
disabled
:
SmartDashesType
.
enabled
),
smartQuotesType:
widget
.
smartQuotesType
??
(
widget
.
obscureText
?
SmartQuotesType
.
disabled
:
SmartQuotesType
.
enabled
),
enableSuggestions:
widget
.
enableSuggestions
,
inputAction:
widget
.
textInputAction
??
(
widget
.
keyboardType
==
TextInputType
.
multiline
?
TextInputAction
.
newline
:
TextInputAction
.
done
),
textCapitalization:
widget
.
textCapitalization
,
keyboardAppearance:
widget
.
keyboardAppearance
,
),
);
_textInputConnection
.
show
();
_textInputConnection
=
(
widget
.
autofillHints
?.
isNotEmpty
??
false
)
&&
currentAutofillScope
!=
null
?
currentAutofillScope
.
attach
(
this
,
textInputConfiguration
)
:
TextInput
.
attach
(
this
,
textInputConfiguration
);
_textInputConnection
.
show
();
_updateSizeAndTransform
();
// Request autofill AFTER the size and the transform have been sent to the
// platform side.
_textInputConnection
.
requestAutofill
();
final
TextStyle
style
=
widget
.
style
;
_textInputConnection
..
setStyle
(
...
...
@@ -1904,6 +1930,33 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
}
}
@override
String
get
autofillId
=>
'EditableText-
$hashCode
'
;
@override
TextInputConfiguration
get
textInputConfiguration
{
final
bool
isAutofillEnabled
=
widget
.
autofillHints
?.
isNotEmpty
??
false
;
return
TextInputConfiguration
(
inputType:
widget
.
keyboardType
,
obscureText:
widget
.
obscureText
,
autocorrect:
widget
.
autocorrect
,
smartDashesType:
widget
.
smartDashesType
??
(
widget
.
obscureText
?
SmartDashesType
.
disabled
:
SmartDashesType
.
enabled
),
smartQuotesType:
widget
.
smartQuotesType
??
(
widget
.
obscureText
?
SmartQuotesType
.
disabled
:
SmartQuotesType
.
enabled
),
enableSuggestions:
widget
.
enableSuggestions
,
inputAction:
widget
.
textInputAction
??
(
widget
.
keyboardType
==
TextInputType
.
multiline
?
TextInputAction
.
newline
:
TextInputAction
.
done
),
textCapitalization:
widget
.
textCapitalization
,
keyboardAppearance:
widget
.
keyboardAppearance
,
autofillConfiguration:
!
isAutofillEnabled
?
null
:
AutofillConfiguration
(
uniqueIdentifier:
autofillId
,
autofillHints:
widget
.
autofillHints
.
toList
(
growable:
false
),
currentEditingValue:
currentTextEditingValue
,
),
);
}
// null if no promptRect should be shown.
TextRange
_currentPromptRectRange
;
...
...
packages/flutter/lib/widgets.dart
View file @
e31f7089
...
...
@@ -22,6 +22,7 @@ export 'src/widgets/animated_switcher.dart';
export
'src/widgets/annotated_region.dart'
;
export
'src/widgets/app.dart'
;
export
'src/widgets/async.dart'
;
export
'src/widgets/autofill.dart'
;
export
'src/widgets/automatic_keep_alive.dart'
;
export
'src/widgets/banner.dart'
;
export
'src/widgets/basic.dart'
;
...
...
packages/flutter/test/services/autofill_test.dart
0 → 100644
View file @
e31f7089
// Copyright 2014 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.
import
'dart:convert'
show
utf8
;
import
'package:flutter/services.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
void
main
(
)
{
TestWidgetsFlutterBinding
.
ensureInitialized
();
group
(
'TextInput message channels'
,
()
{
FakeTextChannel
fakeTextChannel
;
FakeAutofillScope
scope
;
setUp
(()
{
fakeTextChannel
=
FakeTextChannel
((
MethodCall
call
)
async
{});
TextInput
.
setChannel
(
fakeTextChannel
);
scope
??=
FakeAutofillScope
();
scope
.
clients
.
clear
();
});
tearDown
(()
{
TextInputConnection
.
debugResetId
();
TextInput
.
setChannel
(
SystemChannels
.
textInput
);
});
test
(
'mandatory fields are mandatory'
,
()
async
{
AutofillConfiguration
config
;
try
{
config
=
AutofillConfiguration
(
uniqueIdentifier:
null
,
autofillHints:
const
<
String
>[
'test'
],
);
}
catch
(
e
)
{
expect
(
e
.
toString
(),
contains
(
'uniqueIdentifier != null'
));
}
expect
(
config
,
isNull
);
try
{
config
=
AutofillConfiguration
(
uniqueIdentifier:
'id'
,
autofillHints:
null
,
);
}
catch
(
e
)
{
expect
(
e
.
toString
(),
contains
(
'autofillHints != null'
));
}
expect
(
config
,
isNull
);
});
test
(
'throws if the hint list is empty'
,
()
async
{
Map
<
String
,
dynamic
>
json
;
try
{
const
AutofillConfiguration
config
=
AutofillConfiguration
(
uniqueIdentifier:
'id'
,
autofillHints:
<
String
>[],
);
json
=
config
.
toJson
();
}
catch
(
e
)
{
expect
(
e
.
toString
(),
contains
(
'isNotEmpty'
));
}
expect
(
json
,
isNull
);
});
test
(
'AutofillClients send the correct configuration to the platform'
'and responds to updateEditingStateWithTag method correctly'
,
()
async
{
final
FakeAutofillClient
client1
=
FakeAutofillClient
(
const
TextEditingValue
(
text:
'test1'
));
final
FakeAutofillClient
client2
=
FakeAutofillClient
(
const
TextEditingValue
(
text:
'test2'
));
client1
.
textInputConfiguration
=
TextInputConfiguration
(
autofillConfiguration:
AutofillConfiguration
(
uniqueIdentifier:
client1
.
autofillId
,
autofillHints:
const
<
String
>[
'client1'
],
currentEditingValue:
client1
.
currentTextEditingValue
,
),
);
client2
.
textInputConfiguration
=
TextInputConfiguration
(
autofillConfiguration:
AutofillConfiguration
(
uniqueIdentifier:
client2
.
autofillId
,
autofillHints:
const
<
String
>[
'client2'
],
currentEditingValue:
client2
.
currentTextEditingValue
,
),
);
scope
.
register
(
client1
);
scope
.
register
(
client2
);
client1
.
currentAutofillScope
=
scope
;
client2
.
currentAutofillScope
=
scope
;
scope
.
attach
(
client1
,
client1
.
textInputConfiguration
);
final
Map
<
String
,
dynamic
>
expectedConfiguration
=
client1
.
textInputConfiguration
.
toJson
();
expectedConfiguration
[
'fields'
]
=
<
Map
<
String
,
dynamic
>>[
client1
.
textInputConfiguration
.
toJson
(),
client2
.
textInputConfiguration
.
toJson
(),
];
fakeTextChannel
.
validateOutgoingMethodCalls
(<
MethodCall
>[
MethodCall
(
'TextInput.setClient'
,
<
dynamic
>[
1
,
expectedConfiguration
]),
]);
const
TextEditingValue
text2
=
TextEditingValue
(
text:
'Text 2'
);
fakeTextChannel
.
incoming
(
MethodCall
(
'TextInputClient.updateEditingStateWithTag'
,
<
dynamic
>[
0
,
<
String
,
dynamic
>{
client2
.
autofillId
:
text2
.
toJSON
()
}],
));
expect
(
client2
.
currentTextEditingValue
,
text2
);
});
});
}
class
FakeAutofillClient
implements
TextInputClient
,
AutofillClient
{
FakeAutofillClient
(
this
.
currentTextEditingValue
);
@override
String
get
autofillId
=>
hashCode
.
toString
();
@override
TextInputConfiguration
textInputConfiguration
;
@override
void
updateEditingValue
(
TextEditingValue
newEditingValue
)
{
currentTextEditingValue
=
newEditingValue
;
latestMethodCall
=
'updateEditingValue'
;
}
@override
AutofillScope
currentAutofillScope
;
String
latestMethodCall
=
''
;
@override
TextEditingValue
currentTextEditingValue
;
@override
void
performAction
(
TextInputAction
action
)
{
latestMethodCall
=
'performAction'
;
}
@override
void
updateFloatingCursor
(
RawFloatingCursorPoint
point
)
{
latestMethodCall
=
'updateFloatingCursor'
;
}
@override
void
connectionClosed
()
{
latestMethodCall
=
'connectionClosed'
;
}
@override
void
showAutocorrectionPromptRect
(
int
start
,
int
end
)
{
latestMethodCall
=
'showAutocorrectionPromptRect'
;
}
}
class
FakeAutofillScope
with
AutofillScopeMixin
implements
AutofillScope
{
final
Map
<
String
,
AutofillClient
>
clients
=
<
String
,
AutofillClient
>{};
@override
Iterable
<
AutofillClient
>
get
autofillClients
=>
clients
.
values
;
@override
AutofillClient
getAutofillClient
(
String
autofillId
)
=>
clients
[
autofillId
];
void
register
(
AutofillClient
client
)
{
clients
.
putIfAbsent
(
client
.
autofillId
,
()
=>
client
);
}
}
class
FakeTextChannel
implements
MethodChannel
{
FakeTextChannel
(
this
.
outgoing
)
:
assert
(
outgoing
!=
null
);
Future
<
dynamic
>
Function
(
MethodCall
)
outgoing
;
Future
<
void
>
Function
(
MethodCall
)
incoming
;
List
<
MethodCall
>
outgoingCalls
=
<
MethodCall
>[];
@override
BinaryMessenger
get
binaryMessenger
=>
throw
UnimplementedError
();
@override
MethodCodec
get
codec
=>
const
JSONMethodCodec
();
@override
Future
<
List
<
T
>>
invokeListMethod
<
T
>(
String
method
,
[
dynamic
arguments
])
=>
throw
UnimplementedError
();
@override
Future
<
Map
<
K
,
V
>>
invokeMapMethod
<
K
,
V
>(
String
method
,
[
dynamic
arguments
])
=>
throw
UnimplementedError
();
@override
Future
<
T
>
invokeMethod
<
T
>(
String
method
,
[
dynamic
arguments
])
async
{
final
MethodCall
call
=
MethodCall
(
method
,
arguments
);
outgoingCalls
.
add
(
call
);
return
await
outgoing
(
call
)
as
T
;
}
@override
String
get
name
=>
'flutter/textinput'
;
@override
void
setMethodCallHandler
(
Future
<
void
>
Function
(
MethodCall
call
)
handler
)
{
incoming
=
handler
;
}
@override
void
setMockMethodCallHandler
(
Future
<
void
>
Function
(
MethodCall
call
)
handler
)
=>
throw
UnimplementedError
();
void
validateOutgoingMethodCalls
(
List
<
MethodCall
>
calls
)
{
expect
(
outgoingCalls
.
length
,
calls
.
length
);
bool
hasError
=
false
;
for
(
int
i
=
0
;
i
<
calls
.
length
;
i
++)
{
final
ByteData
outgoingData
=
codec
.
encodeMethodCall
(
outgoingCalls
[
i
]);
final
ByteData
expectedData
=
codec
.
encodeMethodCall
(
calls
[
i
]);
final
String
outgoingString
=
utf8
.
decode
(
outgoingData
.
buffer
.
asUint8List
());
final
String
expectedString
=
utf8
.
decode
(
expectedData
.
buffer
.
asUint8List
());
if
(
outgoingString
!=
expectedString
)
{
print
(
'Index
$i
did not match:
\n
'
' actual:
${outgoingCalls[i]}
\n
'
' expected:
${calls[i]}
'
);
hasError
=
true
;
}
}
if
(
hasError
)
{
fail
(
'Calls did not match.'
);
}
}
}
packages/flutter/test/services/text_input_test.dart
View file @
e31f7089
...
...
@@ -200,6 +200,9 @@ class FakeTextInputClient implements TextInputClient {
@override
TextEditingValue
currentTextEditingValue
;
@override
AutofillScope
get
currentAutofillScope
=>
null
;
@override
void
performAction
(
TextInputAction
action
)
{
latestMethodCall
=
'performAction'
;
...
...
packages/flutter/test/widgets/autofill_group_test.dart
0 → 100644
View file @
e31f7089
// Copyright 2014 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.
import
'package:flutter/material.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
void
main
(
)
{
testWidgets
(
'AutofillGroup has the right clients'
,
(
WidgetTester
tester
)
async
{
const
Key
outerKey
=
Key
(
'outer'
);
const
Key
innerKey
=
Key
(
'inner'
);
const
TextField
client1
=
TextField
(
autofillHints:
<
String
>[
'1'
]);
const
TextField
client2
=
TextField
(
autofillHints:
<
String
>[
'2'
]);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Scaffold
(
body:
AutofillGroup
(
key:
outerKey
,
child:
Column
(
children:
<
Widget
>[
client1
,
AutofillGroup
(
key:
innerKey
,
child:
Column
(
children:
const
<
Widget
>[
client2
,
TextField
()]),
),
]),
),
),
),
);
final
AutofillGroupState
innerState
=
tester
.
state
<
AutofillGroupState
>(
find
.
byKey
(
innerKey
));
final
AutofillGroupState
outerState
=
tester
.
state
<
AutofillGroupState
>(
find
.
byKey
(
outerKey
));
final
EditableTextState
clientState1
=
tester
.
state
<
EditableTextState
>(
find
.
descendant
(
of:
find
.
byWidget
(
client1
),
matching:
find
.
byType
(
EditableText
)),
);
final
EditableTextState
clientState2
=
tester
.
state
<
EditableTextState
>(
find
.
descendant
(
of:
find
.
byWidget
(
client2
),
matching:
find
.
byType
(
EditableText
)),
);
expect
(
outerState
.
autofillClients
,
<
EditableTextState
>[
clientState1
]);
expect
(
innerState
.
autofillClients
,
<
EditableTextState
>[
clientState2
]);
});
testWidgets
(
'new clients can be added & removed to a scope'
,
(
WidgetTester
tester
)
async
{
const
Key
scopeKey
=
Key
(
'scope'
);
final
List
<
String
>
hints
=
<
String
>[];
const
TextField
client1
=
TextField
(
autofillHints:
<
String
>[
'1'
]);
final
TextField
client2
=
TextField
(
autofillHints:
hints
);
StateSetter
setState
;
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Scaffold
(
body:
AutofillGroup
(
key:
scopeKey
,
child:
StatefulBuilder
(
builder:
(
BuildContext
context
,
StateSetter
setter
)
{
setState
=
setter
;
return
Column
(
children:
<
Widget
>[
client1
,
client2
]);
},
),
),
),
),
);
final
AutofillGroupState
scopeState
=
tester
.
state
<
AutofillGroupState
>(
find
.
byKey
(
scopeKey
));
final
EditableTextState
clientState1
=
tester
.
state
<
EditableTextState
>(
find
.
descendant
(
of:
find
.
byWidget
(
client1
),
matching:
find
.
byType
(
EditableText
)),
);
final
EditableTextState
clientState2
=
tester
.
state
<
EditableTextState
>(
find
.
descendant
(
of:
find
.
byWidget
(
client2
),
matching:
find
.
byType
(
EditableText
)),
);
expect
(
scopeState
.
autofillClients
,
<
EditableTextState
>[
clientState1
]);
// Add to scope.
setState
(()
{
hints
.
add
(
'2'
);
});
await
tester
.
pump
();
expect
(
scopeState
.
autofillClients
.
length
,
2
);
expect
(
scopeState
.
autofillClients
,
contains
(
clientState1
));
expect
(
scopeState
.
autofillClients
,
contains
(
clientState2
));
// Remove from scope again.
setState
(()
{
hints
.
clear
();
});
await
tester
.
pump
();
expect
(
scopeState
.
autofillClients
,
<
EditableTextState
>[
clientState1
]);
});
testWidgets
(
'AutofillGroup has the right clients after reparenting'
,
(
WidgetTester
tester
)
async
{
const
Key
outerKey
=
Key
(
'outer'
);
const
Key
innerKey
=
Key
(
'inner'
);
final
GlobalKey
keyClient3
=
GlobalKey
();
const
TextField
client1
=
TextField
(
autofillHints:
<
String
>[
'1'
]);
const
TextField
client2
=
TextField
(
autofillHints:
<
String
>[
'2'
]);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Scaffold
(
body:
AutofillGroup
(
key:
outerKey
,
child:
Column
(
children:
<
Widget
>[
client1
,
AutofillGroup
(
key:
innerKey
,
child:
Column
(
children:
<
Widget
>[
client2
,
TextField
(
key:
keyClient3
,
autofillHints:
const
<
String
>[
'3'
]),
]),
),
]),
),
),
),
);
final
AutofillGroupState
innerState
=
tester
.
state
<
AutofillGroupState
>(
find
.
byKey
(
innerKey
));
final
AutofillGroupState
outerState
=
tester
.
state
<
AutofillGroupState
>(
find
.
byKey
(
outerKey
));
final
EditableTextState
clientState1
=
tester
.
state
<
EditableTextState
>(
find
.
descendant
(
of:
find
.
byWidget
(
client1
),
matching:
find
.
byType
(
EditableText
)),
);
final
EditableTextState
clientState2
=
tester
.
state
<
EditableTextState
>(
find
.
descendant
(
of:
find
.
byWidget
(
client2
),
matching:
find
.
byType
(
EditableText
)),
);
final
EditableTextState
clientState3
=
tester
.
state
<
EditableTextState
>(
find
.
descendant
(
of:
find
.
byKey
(
keyClient3
),
matching:
find
.
byType
(
EditableText
)),
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Scaffold
(
body:
AutofillGroup
(
key:
outerKey
,
child:
Column
(
children:
<
Widget
>[
client1
,
TextField
(
key:
keyClient3
,
autofillHints:
const
<
String
>[
'3'
]),
AutofillGroup
(
key:
innerKey
,
child:
Column
(
children:
const
<
Widget
>[
client2
]),
),
]),
),
),
),
);
expect
(
outerState
.
autofillClients
.
length
,
2
);
expect
(
outerState
.
autofillClients
,
contains
(
clientState1
));
expect
(
outerState
.
autofillClients
,
contains
(
clientState3
));
expect
(
innerState
.
autofillClients
,
<
EditableTextState
>[
clientState2
]);
});
}
packages/flutter/test/widgets/editable_text_test.dart
View file @
e31f7089
...
...
@@ -4116,8 +4116,17 @@ void main() {
await
tester
.
showKeyboard
(
find
.
byType
(
EditableText
));
// TextInput.show should be before TextInput.setEditingState
final
List
<
String
>
logOrder
=
<
String
>[
'TextInput.setClient'
,
'TextInput.show'
,
'TextInput.setEditableSizeAndTransform'
,
'TextInput.setStyle'
,
'TextInput.setEditingState'
,
'TextInput.setEditingState'
,
'TextInput.show'
];
expect
(
tester
.
testTextInput
.
log
.
length
,
7
);
final
List
<
String
>
logOrder
=
<
String
>[
'TextInput.setClient'
,
'TextInput.show'
,
'TextInput.setEditableSizeAndTransform'
,
'TextInput.requestAutofill'
,
'TextInput.setStyle'
,
'TextInput.setEditingState'
,
'TextInput.setEditingState'
,
'TextInput.show'
,
];
expect
(
tester
.
testTextInput
.
log
.
length
,
8
);
int
index
=
0
;
for
(
final
MethodCall
m
in
tester
.
testTextInput
.
log
)
{
expect
(
m
.
method
,
logOrder
[
index
]);
...
...
@@ -4156,6 +4165,7 @@ void main() {
'TextInput.setClient'
,
'TextInput.show'
,
'TextInput.setEditableSizeAndTransform'
,
'TextInput.requestAutofill'
,
'TextInput.setStyle'
,
'TextInput.setEditingState'
,
'TextInput.setEditingState'
,
...
...
@@ -4203,6 +4213,7 @@ void main() {
'TextInput.setClient'
,
'TextInput.show'
,
'TextInput.setEditableSizeAndTransform'
,
'TextInput.requestAutofill'
,
'TextInput.setStyle'
,
'TextInput.setEditingState'
,
'TextInput.setEditingState'
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment