This change broke some internal tests that set the text editing value to the same thing (the empty string) twice in a row. Note that in that case, the developer had subclassed EditableTextState and overridden the updateEditingValue method, which may or may not be relevant to the failure. This reverts commit 83d4d63a716ec8551f0d59f852a40925ddd73d8a.
This commit is contained in:
parent
26619b3c35
commit
b236465bed
@ -1204,13 +1204,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
||||
|
||||
// TextInputClient implementation:
|
||||
|
||||
// _lastFormattedUnmodifiedTextEditingValue tracks the last value
|
||||
// that the formatter ran on and is used to prevent double-formatting.
|
||||
TextEditingValue _lastFormattedUnmodifiedTextEditingValue;
|
||||
// _receivedRemoteTextEditingValue is the direct value last passed in
|
||||
// updateEditingValue. This value does not get updated with the formatted
|
||||
// version.
|
||||
TextEditingValue _receivedRemoteTextEditingValue;
|
||||
TextEditingValue _lastKnownRemoteTextEditingValue;
|
||||
|
||||
@override
|
||||
TextEditingValue get currentTextEditingValue => _value;
|
||||
@ -1222,7 +1216,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
||||
if (widget.readOnly) {
|
||||
return;
|
||||
}
|
||||
_receivedRemoteTextEditingValue = value;
|
||||
if (value.text != _value.text) {
|
||||
hideToolbar();
|
||||
_showCaretOnScreen();
|
||||
@ -1231,7 +1224,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
||||
_obscureLatestCharIndex = _value.selection.baseOffset;
|
||||
}
|
||||
}
|
||||
|
||||
_lastKnownRemoteTextEditingValue = value;
|
||||
_formatAndSetValue(value);
|
||||
|
||||
// To keep the cursor from blinking while typing, we want to restart the
|
||||
@ -1258,7 +1251,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
||||
break;
|
||||
default:
|
||||
// Finalize editing, but don't give up focus because this keyboard
|
||||
// action does not imply the user is done inputting information.
|
||||
// action does not imply the user is done inputting information.
|
||||
_finalizeEditing(false);
|
||||
break;
|
||||
}
|
||||
@ -1358,8 +1351,9 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
||||
if (!_hasInputConnection)
|
||||
return;
|
||||
final TextEditingValue localValue = _value;
|
||||
if (localValue == _receivedRemoteTextEditingValue)
|
||||
if (localValue == _lastKnownRemoteTextEditingValue)
|
||||
return;
|
||||
_lastKnownRemoteTextEditingValue = localValue;
|
||||
_textInputConnection.setEditingState(localValue);
|
||||
}
|
||||
|
||||
@ -1420,7 +1414,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
||||
}
|
||||
if (!_hasInputConnection) {
|
||||
final TextEditingValue localValue = _value;
|
||||
_lastFormattedUnmodifiedTextEditingValue = localValue;
|
||||
_lastKnownRemoteTextEditingValue = localValue;
|
||||
_textInputConnection = TextInput.attach(
|
||||
this,
|
||||
TextInputConfiguration(
|
||||
@ -1460,8 +1454,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
||||
if (_hasInputConnection) {
|
||||
_textInputConnection.close();
|
||||
_textInputConnection = null;
|
||||
_lastFormattedUnmodifiedTextEditingValue = null;
|
||||
_receivedRemoteTextEditingValue = null;
|
||||
_lastKnownRemoteTextEditingValue = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1479,8 +1472,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
||||
if (_hasInputConnection) {
|
||||
_textInputConnection.connectionClosedReceived();
|
||||
_textInputConnection = null;
|
||||
_lastFormattedUnmodifiedTextEditingValue = null;
|
||||
_receivedRemoteTextEditingValue = null;
|
||||
_lastKnownRemoteTextEditingValue = null;
|
||||
_finalizeEditing(true);
|
||||
}
|
||||
}
|
||||
@ -1624,21 +1616,17 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
||||
}
|
||||
|
||||
void _formatAndSetValue(TextEditingValue value) {
|
||||
// Check if the new value is the same as the current local value, or is the same
|
||||
// as the post-formatting value of the previous pass.
|
||||
final bool textChanged = _value?.text != value?.text;
|
||||
final bool isRepeat = value?.text == _lastFormattedUnmodifiedTextEditingValue?.text;
|
||||
if (textChanged && !isRepeat && widget.inputFormatters != null && widget.inputFormatters.isNotEmpty) {
|
||||
if (textChanged && widget.inputFormatters != null && widget.inputFormatters.isNotEmpty) {
|
||||
for (final TextInputFormatter formatter in widget.inputFormatters)
|
||||
value = formatter.formatEditUpdate(_value, value);
|
||||
_value = value;
|
||||
_updateRemoteEditingValueIfNeeded();
|
||||
} else if (!isRepeat || !textChanged) {
|
||||
} else {
|
||||
_value = value;
|
||||
}
|
||||
if (textChanged && widget.onChanged != null)
|
||||
widget.onChanged(value.text);
|
||||
_lastFormattedUnmodifiedTextEditingValue = _receivedRemoteTextEditingValue;
|
||||
}
|
||||
|
||||
void _onCursorColorTick() {
|
||||
|
@ -4178,114 +4178,6 @@ void main() {
|
||||
}
|
||||
expect(tester.testTextInput.editingState['text'], 'flutter is the best!...');
|
||||
});
|
||||
|
||||
testWidgets('updateEditingValue filters multiple calls from formatter', (WidgetTester tester) async {
|
||||
final MockTextFormatter formatter = MockTextFormatter();
|
||||
await tester.pumpWidget(
|
||||
MediaQuery(
|
||||
data: const MediaQueryData(devicePixelRatio: 1.0),
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: FocusScope(
|
||||
node: focusScopeNode,
|
||||
autofocus: true,
|
||||
child: EditableText(
|
||||
backgroundCursorColor: Colors.grey,
|
||||
controller: controller,
|
||||
focusNode: focusNode,
|
||||
maxLines: 1, // Sets text keyboard implicitly.
|
||||
style: textStyle,
|
||||
cursorColor: cursorColor,
|
||||
inputFormatters: <TextInputFormatter>[formatter],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.byType(EditableText));
|
||||
await tester.showKeyboard(find.byType(EditableText));
|
||||
controller.text = '';
|
||||
await tester.idle();
|
||||
|
||||
final EditableTextState state =
|
||||
tester.state<EditableTextState>(find.byType(EditableText));
|
||||
expect(tester.testTextInput.editingState['text'], equals(''));
|
||||
expect(state.wantKeepAlive, true);
|
||||
|
||||
state.updateEditingValue(const TextEditingValue(text: ''));
|
||||
state.updateEditingValue(const TextEditingValue(text: 'a'));
|
||||
state.updateEditingValue(const TextEditingValue(text: 'aa'));
|
||||
state.updateEditingValue(const TextEditingValue(text: 'aaa'));
|
||||
state.updateEditingValue(const TextEditingValue(text: 'aa'));
|
||||
state.updateEditingValue(const TextEditingValue(text: 'aaa'));
|
||||
state.updateEditingValue(const TextEditingValue(text: 'aaaa'));
|
||||
state.updateEditingValue(const TextEditingValue(text: 'aa'));
|
||||
state.updateEditingValue(const TextEditingValue(text: 'aaaaaaa'));
|
||||
state.updateEditingValue(const TextEditingValue(text: 'aa'));
|
||||
state.updateEditingValue(const TextEditingValue(text: 'aaaaaaaaa'));
|
||||
state.updateEditingValue(const TextEditingValue(text: 'aaaaaaaaa')); // Skipped
|
||||
|
||||
const List<String> referenceLog = <String>[
|
||||
'[1]: , a',
|
||||
'[1]: normal aa',
|
||||
'[2]: aa, aaa',
|
||||
'[2]: normal aaaa',
|
||||
'[3]: aaaa, aa',
|
||||
'[3]: deleting a',
|
||||
'[4]: a, aaa',
|
||||
'[4]: normal aaaaaaaa',
|
||||
'[5]: aaaaaaaa, aaaa',
|
||||
'[5]: deleting aaa',
|
||||
'[6]: aaa, aa',
|
||||
'[6]: deleting aaaa',
|
||||
'[7]: aaaa, aaaaaaa',
|
||||
'[7]: normal aaaaaaaaaaaaaa',
|
||||
'[8]: aaaaaaaaaaaaaa, aa',
|
||||
'[8]: deleting aaaaaa',
|
||||
'[9]: aaaaaa, aaaaaaaaa',
|
||||
'[9]: normal aaaaaaaaaaaaaaaaaa',
|
||||
];
|
||||
|
||||
expect(formatter.log, referenceLog);
|
||||
});
|
||||
}
|
||||
|
||||
class MockTextFormatter extends TextInputFormatter {
|
||||
MockTextFormatter() : _counter = 0, log = <String>[];
|
||||
|
||||
int _counter;
|
||||
List<String> log;
|
||||
|
||||
@override
|
||||
TextEditingValue formatEditUpdate(
|
||||
TextEditingValue oldValue,
|
||||
TextEditingValue newValue,
|
||||
) {
|
||||
_counter++;
|
||||
log.add('[$_counter]: ${oldValue.text}, ${newValue.text}');
|
||||
TextEditingValue finalValue;
|
||||
if (newValue.text.length < oldValue.text.length) {
|
||||
finalValue = _handleTextDeletion(oldValue, newValue);
|
||||
} else {
|
||||
finalValue = _formatText(newValue);
|
||||
}
|
||||
return finalValue;
|
||||
}
|
||||
|
||||
|
||||
TextEditingValue _handleTextDeletion(
|
||||
TextEditingValue oldValue, TextEditingValue newValue) {
|
||||
final String result = 'a' * (_counter - 2);
|
||||
log.add('[$_counter]: deleting $result');
|
||||
return TextEditingValue(text: result);
|
||||
}
|
||||
|
||||
TextEditingValue _formatText(TextEditingValue value) {
|
||||
final String result = 'a' * _counter * 2;
|
||||
log.add('[$_counter]: normal $result');
|
||||
return TextEditingValue(text: result);
|
||||
}
|
||||
}
|
||||
|
||||
class MockTextSelectionControls extends Mock implements TextSelectionControls {
|
||||
|
Loading…
x
Reference in New Issue
Block a user