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
33755f20
Unverified
Commit
33755f20
authored
Sep 05, 2021
by
LongCatIsLooong
Committed by
GitHub
Sep 05, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[autofill] opt-out instead of opt-in (#86312)
parent
81142c1f
Changes
11
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
260 additions
and
104 deletions
+260
-104
text_field.dart
packages/flutter/lib/src/cupertino/text_field.dart
+26
-4
selectable_text.dart
packages/flutter/lib/src/material/selectable_text.dart
+1
-0
text_field.dart
packages/flutter/lib/src/material/text_field.dart
+26
-3
autofill.dart
packages/flutter/lib/src/services/autofill.dart
+49
-12
text_input.dart
packages/flutter/lib/src/services/text_input.dart
+40
-4
autofill.dart
packages/flutter/lib/src/widgets/autofill.dart
+3
-4
editable_text.dart
packages/flutter/lib/src/widgets/editable_text.dart
+50
-36
text_field_test.dart
packages/flutter/test/cupertino/text_field_test.dart
+16
-0
text_field_test.dart
packages/flutter/test/material/text_field_test.dart
+23
-0
autofill_test.dart
packages/flutter/test/services/autofill_test.dart
+9
-8
autofill_group_test.dart
packages/flutter/test/widgets/autofill_group_test.dart
+17
-33
No files found.
packages/flutter/lib/src/cupertino/text_field.dart
View file @
33755f20
...
...
@@ -293,7 +293,7 @@ class CupertinoTextField extends StatefulWidget {
this
.
onTap
,
this
.
scrollController
,
this
.
scrollPhysics
,
this
.
autofillHints
,
this
.
autofillHints
=
const
<
String
>[]
,
this
.
restorationId
,
this
.
enableIMEPersonalizedLearning
=
true
,
})
:
assert
(
textAlign
!=
null
),
...
...
@@ -449,7 +449,7 @@ class CupertinoTextField extends StatefulWidget {
this
.
onTap
,
this
.
scrollController
,
this
.
scrollPhysics
,
this
.
autofillHints
,
this
.
autofillHints
=
const
<
String
>[]
,
this
.
restorationId
,
this
.
enableIMEPersonalizedLearning
=
true
,
})
:
assert
(
textAlign
!=
null
),
...
...
@@ -837,7 +837,7 @@ class CupertinoTextField extends StatefulWidget {
}
}
class
_CupertinoTextFieldState
extends
State
<
CupertinoTextField
>
with
RestorationMixin
,
AutomaticKeepAliveClientMixin
<
CupertinoTextField
>
implements
TextSelectionGestureDetectorBuilderDelegate
{
class
_CupertinoTextFieldState
extends
State
<
CupertinoTextField
>
with
RestorationMixin
,
AutomaticKeepAliveClientMixin
<
CupertinoTextField
>
implements
TextSelectionGestureDetectorBuilderDelegate
,
AutofillClient
{
final
GlobalKey
_clearGlobalKey
=
GlobalKey
();
RestorableTextEditingController
?
_controller
;
...
...
@@ -1098,6 +1098,28 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with Restoratio
},
);
}
// AutofillClient implementation start.
@override
String
get
autofillId
=>
_editableText
.
autofillId
;
@override
void
autofill
(
TextEditingValue
newEditingValue
)
=>
_editableText
.
autofill
(
newEditingValue
);
@override
TextInputConfiguration
get
textInputConfiguration
{
final
List
<
String
>?
autofillHints
=
widget
.
autofillHints
?.
toList
(
growable:
false
);
final
AutofillConfiguration
autofillConfiguration
=
autofillHints
!=
null
?
AutofillConfiguration
(
uniqueIdentifier:
autofillId
,
autofillHints:
autofillHints
,
currentEditingValue:
_effectiveController
.
value
,
hintText:
widget
.
placeholder
,
)
:
AutofillConfiguration
.
disabled
;
return
_editableText
.
textInputConfiguration
.
copyWith
(
autofillConfiguration:
autofillConfiguration
);
}
// AutofillClient implementation end.
@override
Widget
build
(
BuildContext
context
)
{
...
...
@@ -1242,7 +1264,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with Restoratio
scrollController:
widget
.
scrollController
,
scrollPhysics:
widget
.
scrollPhysics
,
enableInteractiveSelection:
widget
.
enableInteractiveSelection
,
autofill
Hints:
widget
.
autofillHint
s
,
autofill
Client:
thi
s
,
restorationId:
'editable'
,
enableIMEPersonalizedLearning:
widget
.
enableIMEPersonalizedLearning
,
),
...
...
packages/flutter/lib/src/material/selectable_text.dart
View file @
33755f20
...
...
@@ -692,6 +692,7 @@ class _SelectableTextState extends State<SelectableText> with AutomaticKeepAlive
enableInteractiveSelection:
widget
.
enableInteractiveSelection
,
dragStartBehavior:
widget
.
dragStartBehavior
,
scrollPhysics:
widget
.
scrollPhysics
,
autofillHints:
null
,
),
);
...
...
packages/flutter/lib/src/material/text_field.dart
View file @
33755f20
...
...
@@ -345,7 +345,7 @@ class TextField extends StatefulWidget {
this
.
buildCounter
,
this
.
scrollController
,
this
.
scrollPhysics
,
this
.
autofillHints
,
this
.
autofillHints
=
const
<
String
>[]
,
this
.
restorationId
,
this
.
enableIMEPersonalizedLearning
=
true
,
})
:
assert
(
textAlign
!=
null
),
...
...
@@ -827,7 +827,7 @@ class TextField extends StatefulWidget {
}
}
class
_TextFieldState
extends
State
<
TextField
>
with
RestorationMixin
implements
TextSelectionGestureDetectorBuilderDelegate
{
class
_TextFieldState
extends
State
<
TextField
>
with
RestorationMixin
implements
TextSelectionGestureDetectorBuilderDelegate
,
AutofillClient
{
RestorableTextEditingController
?
_controller
;
TextEditingController
get
_effectiveController
=>
widget
.
controller
??
_controller
!.
value
;
...
...
@@ -1094,6 +1094,29 @@ class _TextFieldState extends State<TextField> with RestorationMixin implements
}
}
// AutofillClient implementation start.
@override
String
get
autofillId
=>
_editableText
!.
autofillId
;
@override
void
autofill
(
TextEditingValue
newEditingValue
)
=>
_editableText
!.
autofill
(
newEditingValue
);
@override
TextInputConfiguration
get
textInputConfiguration
{
final
List
<
String
>?
autofillHints
=
widget
.
autofillHints
?.
toList
(
growable:
false
);
final
AutofillConfiguration
autofillConfiguration
=
autofillHints
!=
null
?
AutofillConfiguration
(
uniqueIdentifier:
autofillId
,
autofillHints:
autofillHints
,
currentEditingValue:
_effectiveController
.
value
,
hintText:
(
widget
.
decoration
??
const
InputDecoration
()).
hintText
,
)
:
AutofillConfiguration
.
disabled
;
return
_editableText
!.
textInputConfiguration
.
copyWith
(
autofillConfiguration:
autofillConfiguration
);
}
// AutofillClient implementation end.
@override
Widget
build
(
BuildContext
context
)
{
assert
(
debugCheckHasMaterial
(
context
));
...
...
@@ -1240,7 +1263,7 @@ class _TextFieldState extends State<TextField> with RestorationMixin implements
dragStartBehavior:
widget
.
dragStartBehavior
,
scrollController:
widget
.
scrollController
,
scrollPhysics:
widget
.
scrollPhysics
,
autofill
Hints:
widget
.
autofillHint
s
,
autofill
Client:
thi
s
,
autocorrectionTextRectColor:
autocorrectionTextRectColor
,
restorationId:
'editable'
,
enableIMEPersonalizedLearning:
widget
.
enableIMEPersonalizedLearning
,
...
...
packages/flutter/lib/src/services/autofill.dart
View file @
33755f20
...
...
@@ -631,12 +631,40 @@ class AutofillConfiguration {
/// Creates autofill related configuration information that can be sent to the
/// platform.
const
AutofillConfiguration
({
required
String
uniqueIdentifier
,
required
List
<
String
>
autofillHints
,
required
TextEditingValue
currentEditingValue
,
String
?
hintText
,
})
:
this
.
_
(
enabled:
true
,
uniqueIdentifier:
uniqueIdentifier
,
autofillHints:
autofillHints
,
currentEditingValue:
currentEditingValue
,
hintText:
hintText
,
);
const
AutofillConfiguration
.
_
({
required
this
.
enabled
,
required
this
.
uniqueIdentifier
,
required
this
.
autofillHints
,
this
.
autofillHints
=
const
<
String
>[],
this
.
hintText
,
required
this
.
currentEditingValue
,
})
:
assert
(
uniqueIdentifier
!=
null
),
assert
(
autofillHints
!=
null
);
/// An [AutofillConfiguration] that indicates the [AutofillClient] does not
/// wish to be autofilled.
static
const
AutofillConfiguration
disabled
=
AutofillConfiguration
.
_
(
enabled:
false
,
uniqueIdentifier:
''
,
currentEditingValue:
TextEditingValue
.
empty
,
);
/// Whether autofill should be enabled for the [AutofillClient].
///
/// To retrieve a disabled [AutofillConfiguration], use [disabled].
final
bool
enabled
;
/// A string that uniquely identifies the current [AutofillClient].
///
/// The identifier needs to be unique within the [AutofillScope] for the
...
...
@@ -648,7 +676,7 @@ class AutofillConfiguration {
/// A list of strings that helps the autofill service identify the type of the
/// [AutofillClient].
///
/// Must not be null
or empty
.
/// Must not be null.
///
/// {@template flutter.services.AutofillConfiguration.autofillHints}
/// For the best results, hint strings need to be understood by the platform's
...
...
@@ -697,14 +725,23 @@ class AutofillConfiguration {
/// The current [TextEditingValue] of the [AutofillClient].
final
TextEditingValue
currentEditingValue
;
/// The optional hint text placed on the view that typically suggests what
/// sort of input the field accepts, for example "enter your password here".
///
/// If the developer does not specify any [autofillHints], the [hintText] can
/// be a useful indication to the platform autofill service.
final
String
?
hintText
;
/// 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
(),
};
Map
<
String
,
dynamic
>?
toJson
()
{
return
enabled
?
<
String
,
dynamic
>{
'uniqueIdentifier'
:
uniqueIdentifier
,
'hints'
:
autofillHints
,
'editingValue'
:
currentEditingValue
.
toJSON
(),
if
(
hintText
!=
null
)
'hintText'
:
hintText
,
}
:
null
;
}
}
...
...
@@ -715,7 +752,7 @@ class AutofillConfiguration {
abstract
class
AutofillClient
{
/// The unique identifier of this [AutofillClient].
///
/// Must not be null.
/// Must not be null
and the identifier must not be changed
.
String
get
autofillId
;
/// The [TextInputConfiguration] that describes this [AutofillClient].
...
...
@@ -726,7 +763,7 @@ abstract class AutofillClient {
/// Requests this [AutofillClient] update its [TextEditingValue] to the given
/// value.
void
updateEditingValue
(
TextEditingValue
newEditingValue
);
void
autofill
(
TextEditingValue
newEditingValue
);
}
/// An ordered group within which [AutofillClient]s are logically connected.
...
...
@@ -806,7 +843,7 @@ mixin AutofillScopeMixin implements AutofillScope {
TextInputConnection
attach
(
TextInputClient
trigger
,
TextInputConfiguration
configuration
)
{
assert
(
trigger
!=
null
);
assert
(
!
autofillClients
.
any
((
AutofillClient
client
)
=>
client
.
textInputConfiguration
.
autofillConfiguration
==
null
),
!
autofillClients
.
any
((
AutofillClient
client
)
=>
!
client
.
textInputConfiguration
.
autofillConfiguration
.
enabled
),
'Every client in AutofillScope.autofillClients must enable autofill'
,
);
...
...
packages/flutter/lib/src/services/text_input.dart
View file @
33755f20
...
...
@@ -466,7 +466,7 @@ class TextInputConfiguration {
this
.
inputAction
=
TextInputAction
.
done
,
this
.
keyboardAppearance
=
Brightness
.
light
,
this
.
textCapitalization
=
TextCapitalization
.
none
,
this
.
autofillConfiguration
,
this
.
autofillConfiguration
=
AutofillConfiguration
.
disabled
,
this
.
enableIMEPersonalizedLearning
=
true
,
})
:
assert
(
inputType
!=
null
),
assert
(
obscureText
!=
null
),
...
...
@@ -503,7 +503,7 @@ class TextInputConfiguration {
/// 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
;
final
AutofillConfiguration
autofillConfiguration
;
/// {@template flutter.services.TextInputConfiguration.smartDashesType}
/// Whether to allow the platform to automatically format dashes.
...
...
@@ -607,8 +607,41 @@ class TextInputConfiguration {
/// {@endtemplate}
final
bool
enableIMEPersonalizedLearning
;
/// Creates a copy of this [TextInputConfiguration] with the given fields
/// replaced with new values.
TextInputConfiguration
copyWith
({
TextInputType
?
inputType
,
bool
?
readOnly
,
bool
?
obscureText
,
bool
?
autocorrect
,
SmartDashesType
?
smartDashesType
,
SmartQuotesType
?
smartQuotesType
,
bool
?
enableSuggestions
,
String
?
actionLabel
,
TextInputAction
?
inputAction
,
Brightness
?
keyboardAppearance
,
TextCapitalization
?
textCapitalization
,
bool
?
enableIMEPersonalizedLearning
,
AutofillConfiguration
?
autofillConfiguration
,
})
{
return
TextInputConfiguration
(
inputType:
inputType
??
this
.
inputType
,
readOnly:
readOnly
??
this
.
readOnly
,
obscureText:
obscureText
??
this
.
obscureText
,
autocorrect:
autocorrect
??
this
.
autocorrect
,
smartDashesType:
smartDashesType
??
this
.
smartDashesType
,
smartQuotesType:
smartQuotesType
??
this
.
smartQuotesType
,
enableSuggestions:
enableSuggestions
??
this
.
enableSuggestions
,
inputAction:
inputAction
??
this
.
inputAction
,
textCapitalization:
textCapitalization
??
this
.
textCapitalization
,
keyboardAppearance:
keyboardAppearance
??
this
.
keyboardAppearance
,
enableIMEPersonalizedLearning:
enableIMEPersonalizedLearning
??
this
.
enableIMEPersonalizedLearning
,
autofillConfiguration:
autofillConfiguration
??
this
.
autofillConfiguration
,
);
}
/// Returns a representation of this object as a JSON object.
Map
<
String
,
dynamic
>
toJson
()
{
final
Map
<
String
,
dynamic
>?
autofill
=
autofillConfiguration
.
toJson
();
return
<
String
,
dynamic
>{
'inputType'
:
inputType
.
toJson
(),
'readOnly'
:
readOnly
,
...
...
@@ -622,7 +655,7 @@ class TextInputConfiguration {
'textCapitalization'
:
textCapitalization
.
toString
(),
'keyboardAppearance'
:
keyboardAppearance
.
toString
(),
'enableIMEPersonalizedLearning'
:
enableIMEPersonalizedLearning
,
if
(
autofill
Configuration
!=
null
)
'autofill'
:
autofillConfiguration
!.
toJson
()
,
if
(
autofill
!=
null
)
'autofill'
:
autofill
,
};
}
}
...
...
@@ -1470,7 +1503,10 @@ class TextInput {
final
TextEditingValue
textEditingValue
=
TextEditingValue
.
fromJSON
(
editingValue
[
tag
]
as
Map
<
String
,
dynamic
>,
);
scope
?.
getAutofillClient
(
tag
)?.
updateEditingValue
(
textEditingValue
);
final
AutofillClient
?
client
=
scope
?.
getAutofillClient
(
tag
);
if
(
client
!=
null
&&
client
.
textInputConfiguration
.
autofillConfiguration
.
enabled
)
{
client
.
autofill
(
textEditingValue
);
}
}
return
;
...
...
packages/flutter/lib/src/widgets/autofill.dart
View file @
33755f20
...
...
@@ -134,7 +134,7 @@ class AutofillGroupState extends State<AutofillGroup> with AutofillScopeMixin {
@override
Iterable
<
AutofillClient
>
get
autofillClients
{
return
_clients
.
values
.
where
((
AutofillClient
client
)
=>
client
.
textInputConfiguration
.
autofillConfiguration
!=
null
);
.
where
((
AutofillClient
client
)
=>
client
.
textInputConfiguration
.
autofillConfiguration
.
enabled
);
}
/// Adds the [AutofillClient] to this [AutofillGroup].
...
...
@@ -155,9 +155,8 @@ class AutofillGroupState extends State<AutofillGroup> with AutofillScopeMixin {
/// 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.
/// Typically, this should be called by a text field when it's being disposed,
/// or before it's registered with a different [AutofillGroup].
///
/// See also:
///
...
...
packages/flutter/lib/src/widgets/editable_text.dart
View file @
33755f20
...
...
@@ -456,7 +456,8 @@ class EditableText extends StatefulWidget {
paste:
true
,
selectAll:
true
,
),
this
.
autofillHints
,
this
.
autofillHints
=
const
<
String
>[],
this
.
autofillClient
,
this
.
clipBehavior
=
Clip
.
hardEdge
,
this
.
restorationId
,
this
.
scrollBehavior
,
...
...
@@ -499,10 +500,6 @@ class EditableText extends StatefulWidget {
assert
(
dragStartBehavior
!=
null
),
assert
(
toolbarOptions
!=
null
),
assert
(
clipBehavior
!=
null
),
assert
(
!
readOnly
||
autofillHints
==
null
,
"Read-only fields can't have autofill hints."
,
),
assert
(
enableIMEPersonalizedLearning
!=
null
),
_strutStyle
=
strutStyle
,
keyboardType
=
keyboardType
??
_inferKeyboardType
(
autofillHints:
autofillHints
,
maxLines:
maxLines
),
...
...
@@ -1163,15 +1160,17 @@ class EditableText extends StatefulWidget {
/// A list of strings that helps the autofill service identify the type of this
/// text input.
///
/// When set to null
or empty, this text input will not send its autofill
///
information to the platform, preventing it from participating in
///
autofills triggered by a different [AutofillClient], even if they're in th
e
///
same [AutofillScope]. Additionally, on Android and web, setting this to
///
null or empty
will disable autofill for this text field.
/// When set to null
, this text input will not send its autofill information
///
to the platform, preventing it from participating in autofills triggered
///
by a different [AutofillClient], even if they're in the sam
e
///
[AutofillScope]. Additionally, on Android and web, setting this to null
/// 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.
///
/// Defaults to an empty list.
///
/// ### Setting up iOS autofill:
///
/// To provide the best user experience and ensure your app fully supports
...
...
@@ -1229,6 +1228,12 @@ class EditableText extends StatefulWidget {
/// {@macro flutter.services.AutofillConfiguration.autofillHints}
final
Iterable
<
String
>?
autofillHints
;
/// The [AutofillClient] that controls this input field's autofill behavior.
///
/// When null, this widget's [EditableTextState] will be used as the
/// [AutofillClient]. This property may override [autofillHints].
final
AutofillClient
?
autofillClient
;
/// {@macro flutter.material.Material.clipBehavior}
///
/// Defaults to [Clip.hardEdge].
...
...
@@ -1278,12 +1283,11 @@ class EditableText extends StatefulWidget {
required
Iterable
<
String
>?
autofillHints
,
required
int
?
maxLines
,
})
{
if
(
autofillHints
?.
isEmpty
??
true
)
{
if
(
autofillHints
==
null
||
autofillHints
.
isEmpty
)
{
return
maxLines
==
1
?
TextInputType
.
text
:
TextInputType
.
multiline
;
}
TextInputType
?
returnValue
;
final
String
effectiveHint
=
autofillHints
!.
first
;
final
String
effectiveHint
=
autofillHints
.
first
;
// On iOS oftentimes specifying a text content type is not enough to qualify
// the input field for autofill. The keyboard type also needs to be compatible
...
...
@@ -1328,7 +1332,10 @@ class EditableText extends StatefulWidget {
AutofillHints
.
username
:
TextInputType
.
text
,
};
returnValue
=
iOSKeyboardType
[
effectiveHint
];
final
TextInputType
?
keyboardType
=
iOSKeyboardType
[
effectiveHint
];
if
(
keyboardType
!=
null
)
{
return
keyboardType
;
}
break
;
case
TargetPlatform
.
android
:
case
TargetPlatform
.
fuchsia
:
...
...
@@ -1338,8 +1345,9 @@ class EditableText extends StatefulWidget {
}
}
if
(
returnValue
!=
null
||
maxLines
!=
1
)
return
returnValue
??
TextInputType
.
multiline
;
if
(
maxLines
!=
1
)
{
return
TextInputType
.
multiline
;
}
const
Map
<
String
,
TextInputType
>
inferKeyboardType
=
<
String
,
TextInputType
>
{
AutofillHints
.
addressCity
:
TextInputType
.
streetAddress
,
...
...
@@ -1474,8 +1482,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
@override
AutofillScope
?
get
currentAutofillScope
=>
_currentAutofillScope
;
// Is this field in the current autofill context.
bool
_isInAutofillContext
=
false
;
AutofillClient
get
_effectiveAutofillClient
=>
widget
.
autofillClient
??
this
;
/// Whether to create an input connection with the platform for text editing
/// or not.
...
...
@@ -1548,8 +1555,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
if
(
currentAutofillScope
!=
newAutofillGroup
)
{
_currentAutofillScope
?.
unregister
(
autofillId
);
_currentAutofillScope
=
newAutofillGroup
;
newAutofillGroup
?.
register
(
this
);
_isInAutofillContext
=
_isInAutofillContext
||
_shouldBeInAutofillContext
;
_currentAutofillScope
?.
register
(
_effectiveAutofillClient
);
}
if
(!
_didAutoFocus
&&
widget
.
autofocus
)
{
...
...
@@ -1574,7 +1580,11 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
_selectionOverlay
?.
update
(
_value
);
}
_selectionOverlay
?.
handlesVisible
=
widget
.
showSelectionHandles
;
_isInAutofillContext
=
_isInAutofillContext
||
_shouldBeInAutofillContext
;
if
(
widget
.
autofillClient
!=
oldWidget
.
autofillClient
)
{
_currentAutofillScope
?.
unregister
(
oldWidget
.
autofillClient
?.
autofillId
??
autofillId
);
_currentAutofillScope
?.
register
(
_effectiveAutofillClient
);
}
if
(
widget
.
focusNode
!=
oldWidget
.
focusNode
)
{
oldWidget
.
focusNode
.
removeListener
(
_handleFocusChanged
);
...
...
@@ -1597,7 +1607,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
if
(
kIsWeb
&&
_hasInputConnection
)
{
if
(
oldWidget
.
readOnly
!=
widget
.
readOnly
)
{
_textInputConnection
!.
updateConfig
(
textInputConfiguration
);
_textInputConnection
!.
updateConfig
(
_effectiveAutofillClient
.
textInputConfiguration
);
}
}
...
...
@@ -1980,8 +1990,9 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
}
bool
get
_hasInputConnection
=>
_textInputConnection
?.
attached
??
false
;
bool
get
_needsAutofill
=>
widget
.
autofillHints
?.
isNotEmpty
??
false
;
bool
get
_shouldBeInAutofillContext
=>
_needsAutofill
&&
currentAutofillScope
!=
null
;
/// Whether to send the autofill information to the autofill service. True by
/// default.
bool
get
_needsAutofill
=>
widget
.
autofillHints
?.
isNotEmpty
??
true
;
void
_openInputConnection
()
{
if
(!
_shouldCreateInputConnection
)
{
...
...
@@ -1999,8 +2010,8 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
// notified to exclude this field from the autofill context. So we need to
// provide the autofillId.
_textInputConnection
=
_needsAutofill
&&
currentAutofillScope
!=
null
?
currentAutofillScope
!.
attach
(
this
,
textInputConfiguration
)
:
TextInput
.
attach
(
this
,
_
createTextInputConfiguration
(
_isInAutofillContext
||
_needsAutofill
)
);
?
currentAutofillScope
!.
attach
(
this
,
_effectiveAutofillClient
.
textInputConfiguration
)
:
TextInput
.
attach
(
this
,
_
effectiveAutofillClient
.
textInputConfiguration
);
_textInputConnection
!.
show
();
_updateSizeAndTransform
();
_updateComposingRectIfNeeded
();
...
...
@@ -2523,8 +2534,17 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
@override
String
get
autofillId
=>
'EditableText-
$hashCode
'
;
TextInputConfiguration
_createTextInputConfiguration
(
bool
needsAutofillConfiguration
)
{
assert
(
needsAutofillConfiguration
!=
null
);
@override
TextInputConfiguration
get
textInputConfiguration
{
final
List
<
String
>?
autofillHints
=
widget
.
autofillHints
?.
toList
(
growable:
false
);
final
AutofillConfiguration
autofillConfiguration
=
autofillHints
!=
null
?
AutofillConfiguration
(
uniqueIdentifier:
autofillId
,
autofillHints:
autofillHints
,
currentEditingValue:
currentTextEditingValue
,
)
:
AutofillConfiguration
.
disabled
;
return
TextInputConfiguration
(
inputType:
widget
.
keyboardType
,
readOnly:
widget
.
readOnly
,
...
...
@@ -2539,19 +2559,13 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
),
textCapitalization:
widget
.
textCapitalization
,
keyboardAppearance:
widget
.
keyboardAppearance
,
autofillConfiguration:
!
needsAutofillConfiguration
?
null
:
AutofillConfiguration
(
uniqueIdentifier:
autofillId
,
autofillHints:
widget
.
autofillHints
?.
toList
(
growable:
false
)
??
<
String
>[],
currentEditingValue:
currentTextEditingValue
,
),
autofillConfiguration:
autofillConfiguration
,
enableIMEPersonalizedLearning:
widget
.
enableIMEPersonalizedLearning
,
);
}
@override
TextInputConfiguration
get
textInputConfiguration
{
return
_createTextInputConfiguration
(
_needsAutofill
);
}
void
autofill
(
TextEditingValue
value
)
=>
updateEditingValue
(
value
);
// null if no promptRect should be shown.
TextRange
?
_currentPromptRectRange
;
...
...
packages/flutter/test/cupertino/text_field_test.dart
View file @
33755f20
...
...
@@ -4776,6 +4776,22 @@ void main() {
},
);
testWidgets
(
'autofill info has placeholder text'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
CupertinoApp
(
home:
CupertinoTextField
(
placeholder:
'placeholder text'
,
),
),
);
await
tester
.
tap
(
find
.
byType
(
CupertinoTextField
));
expect
(
tester
.
testTextInput
.
setClientArgs
?[
'autofill'
],
containsPair
(
'hintText'
,
'placeholder text'
),
);
});
testWidgets
(
'textDirection is passed to EditableText'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
CupertinoApp
(
...
...
packages/flutter/test/material/text_field_test.dart
View file @
33755f20
...
...
@@ -9924,4 +9924,27 @@ void main() {
expect
(
prefixTapCount
,
1
);
expect
(
suffixTapCount
,
1
);
});
testWidgets
(
'autofill info has hint text'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
MaterialApp
(
home:
Material
(
child:
Center
(
child:
TextField
(
decoration:
InputDecoration
(
hintText:
'placeholder text'
),
),
),
),
),
);
await
tester
.
tap
(
find
.
byType
(
TextField
));
expect
(
tester
.
testTextInput
.
setClientArgs
?[
'autofill'
],
containsPair
(
'hintText'
,
'placeholder text'
),
);
});
}
packages/flutter/test/services/autofill_test.dart
View file @
33755f20
...
...
@@ -10,7 +10,7 @@ import 'package:flutter_test/flutter_test.dart';
void
main
(
)
{
TestWidgetsFlutterBinding
.
ensureInitialized
();
group
(
'
TextInput message channels
'
,
()
{
group
(
'
AutofillClient
'
,
()
{
late
FakeTextChannel
fakeTextChannel
;
final
FakeAutofillScope
scope
=
FakeAutofillScope
();
...
...
@@ -25,21 +25,19 @@ void main() {
TextInput
.
setChannel
(
SystemChannels
.
textInput
);
});
test
(
'
throws
if the hint list is empty'
,
()
async
{
Map
<
String
,
dynamic
>?
js
on
;
test
(
'
Does not throw
if the hint list is empty'
,
()
async
{
Object
?
excepti
on
;
try
{
const
AutofillConfiguration
config
=
AutofillConfiguration
(
const
AutofillConfiguration
(
uniqueIdentifier:
'id'
,
autofillHints:
<
String
>[],
currentEditingValue:
TextEditingValue
.
empty
,
);
json
=
config
.
toJson
();
}
catch
(
e
)
{
ex
pect
(
e
.
toString
(),
contains
(
'isNotEmpty'
))
;
ex
ception
=
e
;
}
expect
(
js
on
,
isNull
);
expect
(
excepti
on
,
isNull
);
});
test
(
...
...
@@ -140,6 +138,9 @@ class FakeAutofillClient implements TextInputClient, AutofillClient {
void
showAutocorrectionPromptRect
(
int
start
,
int
end
)
{
latestMethodCall
=
'showAutocorrectionPromptRect'
;
}
@override
void
autofill
(
TextEditingValue
newEditingValue
)
=>
updateEditingValue
(
newEditingValue
);
}
class
FakeAutofillScope
with
AutofillScopeMixin
implements
AutofillScope
{
...
...
packages/flutter/test/widgets/autofill_group_test.dart
View file @
33755f20
...
...
@@ -25,7 +25,7 @@ void main() {
client1
,
AutofillGroup
(
key:
innerKey
,
child:
Column
(
children:
const
<
Widget
>[
client2
,
TextField
()]),
child:
Column
(
children:
const
<
Widget
>[
client2
,
TextField
(
autofillHints:
null
)]),
),
]),
),
...
...
@@ -36,23 +36,19 @@ void main() {
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
State
<
TextField
>
clientState1
=
tester
.
state
<
State
<
TextField
>>(
find
.
byWidget
(
client1
));
final
State
<
TextField
>
clientState2
=
tester
.
state
<
State
<
TextField
>>(
find
.
byWidget
(
client2
));
expect
(
outerState
.
autofillClients
,
<
EditableTextState
>[
clientState1
]);
// The second TextField doesn't have autofill enabled.
expect
(
innerState
.
autofillClients
,
<
EditableTextState
>[
clientState2
]);
expect
(
outerState
.
autofillClients
.
toList
(),
<
State
<
TextField
>
>[
clientState1
]);
// The second TextField
in the AutofillGroup
doesn't have autofill enabled.
expect
(
innerState
.
autofillClients
.
toList
(),
<
State
<
TextField
>
>[
clientState2
]);
});
testWidgets
(
'new clients can be added & removed to a scope'
,
(
WidgetTester
tester
)
async
{
const
Key
scopeKey
=
Key
(
'scope'
);
const
TextField
client1
=
TextField
(
autofillHints:
<
String
>[
'1'
]);
TextField
client2
=
const
TextField
(
autofillHints:
<
String
>[]
);
TextField
client2
=
const
TextField
(
autofillHints:
null
);
late
StateSetter
setState
;
...
...
@@ -74,14 +70,10 @@ void main() {
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
)),
);
final
State
<
TextField
>
clientState1
=
tester
.
state
<
State
<
TextField
>>(
find
.
byWidget
(
client1
));
final
State
<
TextField
>
clientState2
=
tester
.
state
<
State
<
TextField
>>(
find
.
byWidget
(
client2
));
expect
(
scopeState
.
autofillClients
,
<
EditableTextState
>[
clientState1
]);
expect
(
scopeState
.
autofillClients
.
toList
(),
<
State
<
TextField
>
>[
clientState1
]);
// Add to scope.
setState
(()
{
client2
=
const
TextField
(
autofillHints:
<
String
>[
'2'
]);
});
...
...
@@ -93,11 +85,11 @@ void main() {
expect
(
scopeState
.
autofillClients
.
length
,
2
);
// Remove from scope again.
setState
(()
{
client2
=
const
TextField
(
autofillHints:
<
String
>[]
);
});
setState
(()
{
client2
=
const
TextField
(
autofillHints:
null
);
});
await
tester
.
pump
();
expect
(
scopeState
.
autofillClients
,
<
EditableTextState
>[
clientState1
]);
expect
(
scopeState
.
autofillClients
,
<
State
<
TextField
>
>[
clientState1
]);
});
testWidgets
(
'AutofillGroup has the right clients after reparenting'
,
(
WidgetTester
tester
)
async
{
...
...
@@ -131,16 +123,9 @@ void main() {
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
)),
);
final
State
<
TextField
>
clientState1
=
tester
.
state
<
State
<
TextField
>>(
find
.
byWidget
(
client1
));
final
State
<
TextField
>
clientState2
=
tester
.
state
<
State
<
TextField
>>(
find
.
byWidget
(
client2
));
final
State
<
TextField
>
clientState3
=
tester
.
state
<
State
<
TextField
>>(
find
.
byKey
(
keyClient3
));
await
tester
.
pumpWidget
(
MaterialApp
(
...
...
@@ -163,7 +148,7 @@ void main() {
expect
(
outerState
.
autofillClients
.
length
,
2
);
expect
(
outerState
.
autofillClients
,
contains
(
clientState1
));
expect
(
outerState
.
autofillClients
,
contains
(
clientState3
));
expect
(
innerState
.
autofillClients
,
<
EditableTextState
>[
clientState2
]);
expect
(
innerState
.
autofillClients
,
<
State
<
TextField
>
>[
clientState2
]);
});
testWidgets
(
'disposing AutofillGroups'
,
(
WidgetTester
tester
)
async
{
...
...
@@ -270,8 +255,7 @@ void main() {
// Remove the topmosts group group3. Should commit.
setState
(()
{
children
=
const
<
Widget
>
[
];
children
=
const
<
Widget
>
[];
});
await
tester
.
pump
();
...
...
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