parent
cd351aec2d
commit
09cc1c4cda
@ -4510,7 +4510,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
|||||||
userUpdateTextEditingValue(value, SelectionChangedCause.keyboard);
|
userUpdateTextEditingValue(value, SelectionChangedCause.keyboard);
|
||||||
},
|
},
|
||||||
shouldChangeUndoStack: (TextEditingValue? oldValue, TextEditingValue newValue) {
|
shouldChangeUndoStack: (TextEditingValue? oldValue, TextEditingValue newValue) {
|
||||||
if (newValue == TextEditingValue.empty) {
|
if (!newValue.selection.isValid) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,7 +100,19 @@ class UndoHistoryState<T> extends State<UndoHistory<T>> with UndoManagerClient {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void undo() {
|
void 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());
|
_update(_stack.undo());
|
||||||
|
}
|
||||||
_updateState();
|
_updateState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -455,27 +467,17 @@ typedef _Throttled<T> = Timer Function(T currentArg);
|
|||||||
_Throttled<T> _throttle<T>({
|
_Throttled<T> _throttle<T>({
|
||||||
required Duration duration,
|
required Duration duration,
|
||||||
required _Throttleable<T> function,
|
required _Throttleable<T> function,
|
||||||
// If true, calls at the start of the timer.
|
|
||||||
bool leadingEdge = false,
|
|
||||||
}) {
|
}) {
|
||||||
Timer? timer;
|
Timer? timer;
|
||||||
bool calledDuringTimer = false;
|
|
||||||
late T arg;
|
late T arg;
|
||||||
|
|
||||||
return (T currentArg) {
|
return (T currentArg) {
|
||||||
arg = currentArg;
|
arg = currentArg;
|
||||||
if (timer != null) {
|
if (timer != null && timer!.isActive) {
|
||||||
calledDuringTimer = true;
|
|
||||||
return timer!;
|
return timer!;
|
||||||
}
|
}
|
||||||
if (leadingEdge) {
|
|
||||||
function(arg);
|
|
||||||
}
|
|
||||||
calledDuringTimer = false;
|
|
||||||
timer = Timer(duration, () {
|
timer = Timer(duration, () {
|
||||||
if (!leadingEdge || calledDuringTimer) {
|
|
||||||
function(arg);
|
function(arg);
|
||||||
}
|
|
||||||
timer = null;
|
timer = null;
|
||||||
});
|
});
|
||||||
return timer!;
|
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.
|
// On web, these keyboard shortcuts are handled by the browser.
|
||||||
}, variant: TargetPlatformVariant.only(TargetPlatform.android), skip: kIsWeb); // [intended]
|
}, 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 {
|
testWidgets('Can make changes in the middle of the history', (WidgetTester tester) async {
|
||||||
final TextEditingController controller = TextEditingController();
|
final TextEditingController controller = TextEditingController();
|
||||||
final FocusNode focusNode = FocusNode();
|
final FocusNode focusNode = FocusNode();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user