mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[EditableText] honor the "brieflyShowPassword" system setting (#97769)
This commit is contained in:
parent
9eb94c59b8
commit
63f48d1ff8
@ -1923,13 +1923,13 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
||||
hideToolbar();
|
||||
_currentPromptRectRange = null;
|
||||
|
||||
if (_hasInputConnection) {
|
||||
if (widget.obscureText && value.text.length == _value.text.length + 1) {
|
||||
_obscureShowCharTicksPending = _kObscureShowLatestCharCursorTicks;
|
||||
_obscureLatestCharIndex = _value.selection.baseOffset;
|
||||
}
|
||||
}
|
||||
final bool revealObscuredInput = _hasInputConnection
|
||||
&& widget.obscureText
|
||||
&& WidgetsBinding.instance!.window.brieflyShowPassword
|
||||
&& value.text.length == _value.text.length + 1;
|
||||
|
||||
_obscureShowCharTicksPending = revealObscuredInput ? _kObscureShowLatestCharCursorTicks : 0;
|
||||
_obscureLatestCharIndex = revealObscuredInput ? _value.selection.baseOffset : null;
|
||||
_formatAndSetValue(value, SelectionChangedCause.keyboard);
|
||||
}
|
||||
|
||||
@ -2621,7 +2621,9 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
||||
|
||||
if (_obscureShowCharTicksPending > 0) {
|
||||
setState(() {
|
||||
_obscureShowCharTicksPending--;
|
||||
_obscureShowCharTicksPending = WidgetsBinding.instance!.window.brieflyShowPassword
|
||||
? _obscureShowCharTicksPending - 1
|
||||
: 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -3245,11 +3247,13 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
||||
String text = _value.text;
|
||||
text = widget.obscuringCharacter * text.length;
|
||||
// Reveal the latest character in an obscured field only on mobile.
|
||||
if (defaultTargetPlatform == TargetPlatform.android ||
|
||||
defaultTargetPlatform == TargetPlatform.iOS ||
|
||||
defaultTargetPlatform == TargetPlatform.fuchsia) {
|
||||
final int? o =
|
||||
_obscureShowCharTicksPending > 0 ? _obscureLatestCharIndex : null;
|
||||
const Set<TargetPlatform> mobilePlatforms = <TargetPlatform> {
|
||||
TargetPlatform.android, TargetPlatform.iOS, TargetPlatform.fuchsia,
|
||||
};
|
||||
final bool breiflyShowPassword = WidgetsBinding.instance!.window.brieflyShowPassword
|
||||
&& mobilePlatforms.contains(defaultTargetPlatform);
|
||||
if (breiflyShowPassword) {
|
||||
final int? o = _obscureShowCharTicksPending > 0 ? _obscureLatestCharIndex : null;
|
||||
if (o != null && o >= 0 && o < text.length)
|
||||
text = text.replaceRange(o, o + 1, _value.text.substring(o, o + 1));
|
||||
}
|
||||
|
||||
@ -3611,6 +3611,72 @@ void main() {
|
||||
expect((findRenderEditable(tester).text! as TextSpan).text, expectedValue);
|
||||
});
|
||||
|
||||
testWidgets('password briefly shows last character when entered on mobile', (WidgetTester tester) async {
|
||||
final bool debugDeterministicCursor = EditableText.debugDeterministicCursor;
|
||||
EditableText.debugDeterministicCursor = false;
|
||||
addTearDown(() {
|
||||
EditableText.debugDeterministicCursor = debugDeterministicCursor;
|
||||
});
|
||||
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: EditableText(
|
||||
backgroundCursorColor: Colors.grey,
|
||||
controller: controller,
|
||||
obscureText: true,
|
||||
focusNode: focusNode,
|
||||
style: textStyle,
|
||||
cursorColor: cursorColor,
|
||||
),
|
||||
));
|
||||
|
||||
await tester.enterText(find.byType(EditableText), 'AA');
|
||||
await tester.pump();
|
||||
await tester.enterText(find.byType(EditableText), 'AAA');
|
||||
await tester.pump();
|
||||
|
||||
expect((findRenderEditable(tester).text! as TextSpan).text, '••A');
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
expect((findRenderEditable(tester).text! as TextSpan).text, '•••');
|
||||
});
|
||||
|
||||
testWidgets('password briefly does not show last character on Android if turned off', (WidgetTester tester) async {
|
||||
final bool debugDeterministicCursor = EditableText.debugDeterministicCursor;
|
||||
EditableText.debugDeterministicCursor = false;
|
||||
addTearDown(() {
|
||||
EditableText.debugDeterministicCursor = debugDeterministicCursor;
|
||||
});
|
||||
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: EditableText(
|
||||
backgroundCursorColor: Colors.grey,
|
||||
controller: controller,
|
||||
obscureText: true,
|
||||
focusNode: focusNode,
|
||||
style: textStyle,
|
||||
cursorColor: cursorColor,
|
||||
),
|
||||
));
|
||||
|
||||
await tester.enterText(find.byType(EditableText), 'AA');
|
||||
await tester.pump();
|
||||
await tester.enterText(find.byType(EditableText), 'AAA');
|
||||
await tester.pump();
|
||||
|
||||
tester.binding.window.brieflyShowPasswordTestValue = false;
|
||||
addTearDown(() {
|
||||
tester.binding.window.brieflyShowPasswordTestValue = true;
|
||||
});
|
||||
expect((findRenderEditable(tester).text! as TextSpan).text, '••A');
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
expect((findRenderEditable(tester).text! as TextSpan).text, '•••');
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
expect((findRenderEditable(tester).text! as TextSpan).text, '•••');
|
||||
});
|
||||
|
||||
group('a11y copy/cut/paste', () {
|
||||
Future<void> _buildApp(MockTextSelectionControls controls, WidgetTester tester) {
|
||||
return tester.pumpWidget(MaterialApp(
|
||||
|
||||
@ -271,6 +271,15 @@ class TestWindow implements ui.SingletonFlutterWindow {
|
||||
platformDispatcher.onTextScaleFactorChanged = callback;
|
||||
}
|
||||
|
||||
@override
|
||||
bool get brieflyShowPassword => _brieflyShowPasswordTestValue ?? platformDispatcher.brieflyShowPassword;
|
||||
bool? _brieflyShowPasswordTestValue;
|
||||
/// Hides the real [brieflyShowPassword] and reports the given
|
||||
/// `brieflyShowPasswordTestValue` instead.
|
||||
set brieflyShowPasswordTestValue(bool brieflyShowPasswordTestValue) { // ignore: avoid_setters_without_getters
|
||||
_brieflyShowPasswordTestValue = brieflyShowPasswordTestValue;
|
||||
}
|
||||
|
||||
@override
|
||||
ui.FrameCallback? get onBeginFrame => platformDispatcher.onBeginFrame;
|
||||
@override
|
||||
|
||||
@ -127,6 +127,18 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('TestWindow can fake brieflyShowPassword', (WidgetTester tester) async {
|
||||
verifyThatTestWindowCanFakeProperty<bool>(
|
||||
tester: tester,
|
||||
realValue: ui.window.brieflyShowPassword,
|
||||
fakeValue: !ui.window.brieflyShowPassword,
|
||||
propertyRetriever: () => WidgetsBinding.instance!.window.brieflyShowPassword,
|
||||
propertyFaker: (TestWidgetsFlutterBinding binding, bool fakeValue) {
|
||||
binding.window.brieflyShowPasswordTestValue = fakeValue;
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('TestWindow can fake default route name', (WidgetTester tester) async {
|
||||
verifyThatTestWindowCanFakeProperty<String>(
|
||||
tester: tester,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user