Unverified Commit 09cc1c4c authored by Bruno Leroux's avatar Bruno Leroux Committed by GitHub

Fix cursor disappear on undo. (#122402)

Fix cursor disappear on undo.
parent cd351aec
......@@ -4510,7 +4510,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
userUpdateTextEditingValue(value, SelectionChangedCause.keyboard);
},
shouldChangeUndoStack: (TextEditingValue? oldValue, TextEditingValue newValue) {
if (newValue == TextEditingValue.empty) {
if (!newValue.selection.isValid) {
return false;
}
......
......@@ -100,7 +100,19 @@ class UndoHistoryState<T> extends State<UndoHistory<T>> with UndoManagerClient {
@override
void undo() {
_update(_stack.undo());
if (_stack.currentValue == null) {
// Returns early if there is not a first value registered in the history.
// This is important because, if an undo is received while the initial
// value is being pushed (a.k.a when the field gets the focus but the
// throttling delay is pending), the initial push should not be canceled.
return;
}
if (_throttleTimer?.isActive ?? false) {
_throttleTimer?.cancel(); // Cancel ongoing push, if any.
_update(_stack.currentValue);
} else {
_update(_stack.undo());
}
_updateState();
}
......@@ -455,27 +467,17 @@ typedef _Throttled<T> = Timer Function(T currentArg);
_Throttled<T> _throttle<T>({
required Duration duration,
required _Throttleable<T> function,
// If true, calls at the start of the timer.
bool leadingEdge = false,
}) {
Timer? timer;
bool calledDuringTimer = false;
late T arg;
return (T currentArg) {
arg = currentArg;
if (timer != null) {
calledDuringTimer = true;
if (timer != null && timer!.isActive) {
return timer!;
}
if (leadingEdge) {
function(arg);
}
calledDuringTimer = false;
timer = Timer(duration, () {
if (!leadingEdge || calledDuringTimer) {
function(arg);
}
function(arg);
timer = null;
});
return timer!;
......
......@@ -13011,6 +13011,58 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async
// On web, these keyboard shortcuts are handled by the browser.
}, variant: TargetPlatformVariant.only(TargetPlatform.android), skip: kIsWeb); // [intended]
// Regression test for https://github.com/flutter/flutter/issues/120194.
testWidgets('Cursor does not jump after undo', (WidgetTester tester) async {
// Initialize the controller with a non empty text.
final TextEditingController controller = TextEditingController(text: textA);
final FocusNode focusNode = FocusNode();
await tester.pumpWidget(boilerplate(controller, focusNode));
// Focus the field and wait for throttling delay to get the initial
// state saved in text editing history.
focusNode.requestFocus();
await tester.pump();
await waitForThrottling(tester);
expect(controller.value, textACollapsedAtEnd);
// Insert some text.
await tester.enterText(find.byType(EditableText), textAB);
expect(controller.value, textABCollapsedAtEnd);
// Undo the insertion without waiting for the throttling delay.
await sendUndo(tester);
expect(controller.value.selection.isValid, true);
expect(controller.value, textACollapsedAtEnd);
// On web, these keyboard shortcuts are handled by the browser.
}, variant: TargetPlatformVariant.all(), skip: kIsWeb); // [intended]
testWidgets('Initial value is recorded when an undo is received just after getting the focus', (WidgetTester tester) async {
// Initialize the controller with a non empty text.
final TextEditingController controller = TextEditingController(text: textA);
final FocusNode focusNode = FocusNode();
await tester.pumpWidget(boilerplate(controller, focusNode));
// Focus the field and do not wait for throttling delay before calling undo.
focusNode.requestFocus();
await tester.pump();
await sendUndo(tester);
await waitForThrottling(tester);
expect(controller.value, textACollapsedAtEnd);
// Insert some text.
await tester.enterText(find.byType(EditableText), textAB);
expect(controller.value, textABCollapsedAtEnd);
// Undo the insertion.
await sendUndo(tester);
// Initial text should have been recorded and restored.
expect(controller.value, textACollapsedAtEnd);
// On web, these keyboard shortcuts are handled by the browser.
}, variant: TargetPlatformVariant.all(), skip: kIsWeb); // [intended]
testWidgets('Can make changes in the middle of the history', (WidgetTester tester) async {
final TextEditingController controller = TextEditingController();
final FocusNode focusNode = FocusNode();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment