Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit 63f48d1

Browse files
[EditableText] honor the "brieflyShowPassword" system setting (#97769)
1 parent 9eb94c5 commit 63f48d1

File tree

4 files changed

+103
-12
lines changed

4 files changed

+103
-12
lines changed

packages/flutter/lib/src/widgets/editable_text.dart

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1923,13 +1923,13 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
19231923
hideToolbar();
19241924
_currentPromptRectRange = null;
19251925

1926-
if (_hasInputConnection) {
1927-
if (widget.obscureText && value.text.length == _value.text.length + 1) {
1928-
_obscureShowCharTicksPending = _kObscureShowLatestCharCursorTicks;
1929-
_obscureLatestCharIndex = _value.selection.baseOffset;
1930-
}
1931-
}
1926+
final bool revealObscuredInput = _hasInputConnection
1927+
&& widget.obscureText
1928+
&& WidgetsBinding.instance!.window.brieflyShowPassword
1929+
&& value.text.length == _value.text.length + 1;
19321930

1931+
_obscureShowCharTicksPending = revealObscuredInput ? _kObscureShowLatestCharCursorTicks : 0;
1932+
_obscureLatestCharIndex = revealObscuredInput ? _value.selection.baseOffset : null;
19331933
_formatAndSetValue(value, SelectionChangedCause.keyboard);
19341934
}
19351935

@@ -2621,7 +2621,9 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
26212621

26222622
if (_obscureShowCharTicksPending > 0) {
26232623
setState(() {
2624-
_obscureShowCharTicksPending--;
2624+
_obscureShowCharTicksPending = WidgetsBinding.instance!.window.brieflyShowPassword
2625+
? _obscureShowCharTicksPending - 1
2626+
: 0;
26252627
});
26262628
}
26272629
}
@@ -3245,11 +3247,13 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
32453247
String text = _value.text;
32463248
text = widget.obscuringCharacter * text.length;
32473249
// Reveal the latest character in an obscured field only on mobile.
3248-
if (defaultTargetPlatform == TargetPlatform.android ||
3249-
defaultTargetPlatform == TargetPlatform.iOS ||
3250-
defaultTargetPlatform == TargetPlatform.fuchsia) {
3251-
final int? o =
3252-
_obscureShowCharTicksPending > 0 ? _obscureLatestCharIndex : null;
3250+
const Set<TargetPlatform> mobilePlatforms = <TargetPlatform> {
3251+
TargetPlatform.android, TargetPlatform.iOS, TargetPlatform.fuchsia,
3252+
};
3253+
final bool breiflyShowPassword = WidgetsBinding.instance!.window.brieflyShowPassword
3254+
&& mobilePlatforms.contains(defaultTargetPlatform);
3255+
if (breiflyShowPassword) {
3256+
final int? o = _obscureShowCharTicksPending > 0 ? _obscureLatestCharIndex : null;
32533257
if (o != null && o >= 0 && o < text.length)
32543258
text = text.replaceRange(o, o + 1, _value.text.substring(o, o + 1));
32553259
}

packages/flutter/test/widgets/editable_text_test.dart

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3611,6 +3611,72 @@ void main() {
36113611
expect((findRenderEditable(tester).text! as TextSpan).text, expectedValue);
36123612
});
36133613

3614+
testWidgets('password briefly shows last character when entered on mobile', (WidgetTester tester) async {
3615+
final bool debugDeterministicCursor = EditableText.debugDeterministicCursor;
3616+
EditableText.debugDeterministicCursor = false;
3617+
addTearDown(() {
3618+
EditableText.debugDeterministicCursor = debugDeterministicCursor;
3619+
});
3620+
3621+
await tester.pumpWidget(MaterialApp(
3622+
home: EditableText(
3623+
backgroundCursorColor: Colors.grey,
3624+
controller: controller,
3625+
obscureText: true,
3626+
focusNode: focusNode,
3627+
style: textStyle,
3628+
cursorColor: cursorColor,
3629+
),
3630+
));
3631+
3632+
await tester.enterText(find.byType(EditableText), 'AA');
3633+
await tester.pump();
3634+
await tester.enterText(find.byType(EditableText), 'AAA');
3635+
await tester.pump();
3636+
3637+
expect((findRenderEditable(tester).text! as TextSpan).text, '••A');
3638+
await tester.pump(const Duration(milliseconds: 500));
3639+
await tester.pump(const Duration(milliseconds: 500));
3640+
await tester.pump(const Duration(milliseconds: 500));
3641+
expect((findRenderEditable(tester).text! as TextSpan).text, '•••');
3642+
});
3643+
3644+
testWidgets('password briefly does not show last character on Android if turned off', (WidgetTester tester) async {
3645+
final bool debugDeterministicCursor = EditableText.debugDeterministicCursor;
3646+
EditableText.debugDeterministicCursor = false;
3647+
addTearDown(() {
3648+
EditableText.debugDeterministicCursor = debugDeterministicCursor;
3649+
});
3650+
3651+
await tester.pumpWidget(MaterialApp(
3652+
home: EditableText(
3653+
backgroundCursorColor: Colors.grey,
3654+
controller: controller,
3655+
obscureText: true,
3656+
focusNode: focusNode,
3657+
style: textStyle,
3658+
cursorColor: cursorColor,
3659+
),
3660+
));
3661+
3662+
await tester.enterText(find.byType(EditableText), 'AA');
3663+
await tester.pump();
3664+
await tester.enterText(find.byType(EditableText), 'AAA');
3665+
await tester.pump();
3666+
3667+
tester.binding.window.brieflyShowPasswordTestValue = false;
3668+
addTearDown(() {
3669+
tester.binding.window.brieflyShowPasswordTestValue = true;
3670+
});
3671+
expect((findRenderEditable(tester).text! as TextSpan).text, '••A');
3672+
await tester.pump(const Duration(milliseconds: 500));
3673+
expect((findRenderEditable(tester).text! as TextSpan).text, '•••');
3674+
await tester.pump(const Duration(milliseconds: 500));
3675+
await tester.pump(const Duration(milliseconds: 500));
3676+
await tester.pump(const Duration(milliseconds: 500));
3677+
expect((findRenderEditable(tester).text! as TextSpan).text, '•••');
3678+
});
3679+
36143680
group('a11y copy/cut/paste', () {
36153681
Future<void> _buildApp(MockTextSelectionControls controls, WidgetTester tester) {
36163682
return tester.pumpWidget(MaterialApp(

packages/flutter_test/lib/src/window.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,15 @@ class TestWindow implements ui.SingletonFlutterWindow {
271271
platformDispatcher.onTextScaleFactorChanged = callback;
272272
}
273273

274+
@override
275+
bool get brieflyShowPassword => _brieflyShowPasswordTestValue ?? platformDispatcher.brieflyShowPassword;
276+
bool? _brieflyShowPasswordTestValue;
277+
/// Hides the real [brieflyShowPassword] and reports the given
278+
/// `brieflyShowPasswordTestValue` instead.
279+
set brieflyShowPasswordTestValue(bool brieflyShowPasswordTestValue) { // ignore: avoid_setters_without_getters
280+
_brieflyShowPasswordTestValue = brieflyShowPasswordTestValue;
281+
}
282+
274283
@override
275284
ui.FrameCallback? get onBeginFrame => platformDispatcher.onBeginFrame;
276285
@override

packages/flutter_test/test/window_test.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,18 @@ void main() {
127127
);
128128
});
129129

130+
testWidgets('TestWindow can fake brieflyShowPassword', (WidgetTester tester) async {
131+
verifyThatTestWindowCanFakeProperty<bool>(
132+
tester: tester,
133+
realValue: ui.window.brieflyShowPassword,
134+
fakeValue: !ui.window.brieflyShowPassword,
135+
propertyRetriever: () => WidgetsBinding.instance!.window.brieflyShowPassword,
136+
propertyFaker: (TestWidgetsFlutterBinding binding, bool fakeValue) {
137+
binding.window.brieflyShowPasswordTestValue = fakeValue;
138+
},
139+
);
140+
});
141+
130142
testWidgets('TestWindow can fake default route name', (WidgetTester tester) async {
131143
verifyThatTestWindowCanFakeProperty<String>(
132144
tester: tester,

0 commit comments

Comments
 (0)