Skip to content

Commit

Permalink
[SuperTextField][iOS] Ignore auto-corrections after handling input ac…
Browse files Browse the repository at this point in the history
…tions (Resolves #2004) (#2012)
  • Loading branch information
angelosilvestre authored and matthew-carroll committed May 21, 2024
1 parent fa72e08 commit 7082b08
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ class ImeAttributedTextEditingController extends AttributedTextEditingController
/// Used to determine whether or not we need to send our editing value to the IME.
TextEditingValue _osCurrentTextEditingValue = const TextEditingValue();

/// Whether or not a `TextInputAction` differente from `TextInputAction.newLine` was performed on the current frame.
bool _hasPerformedNonNewLineTextInputActionThisFrame = false;

void attachToIme({
bool autocorrect = true,
bool enableSuggestions = true,
Expand Down Expand Up @@ -306,6 +309,16 @@ class ImeAttributedTextEditingController extends AttributedTextEditingController
return;
}

if (_hasPerformedNonNewLineTextInputActionThisFrame &&
defaultTargetPlatform == TargetPlatform.iOS &&
deltas.every((e) => e is TextEditingDeltaReplacement)) {
// On iOS, pressing the action button can trigger the IME to try to apply auto-corrections
// after we have already processed the input action. Ignore replacement deltas on the same frame
// and forcefully update the IME with our current state.
_sendEditingValueToPlatform();
return;
}

// Update our view from the OS editing value.
for (final delta in deltas) {
_osCurrentTextEditingValue = delta.apply(_osCurrentTextEditingValue);
Expand Down Expand Up @@ -380,8 +393,22 @@ class ImeAttributedTextEditingController extends AttributedTextEditingController
AutofillScope? get currentAutofillScope => null;

@override
@mustCallSuper
void performAction(TextInputAction action) {
_onPerformActionPressed?.call(action);

// Keep track that we have performed a text input action on this frame so we can ignore auto-corrections
// reported after we handled the text input action.
//
// We don't ignore TextInputAction.newline because the insertion of the new line happens after the action
// is reported, and we need to handle the new line insertion to let users replace the selected content
// with a new line.
//
// See https://github.com/superlistapp/super_editor/issues/2004 for more information.
_hasPerformedNonNewLineTextInputActionThisFrame = action != TextInputAction.newline;
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_hasPerformedNonNewLineTextInputActionThisFrame = false;
});
}

@override
Expand Down
30 changes: 30 additions & 0 deletions super_editor/test/super_textfield/super_textfield_ime_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,36 @@ void main() {
expect(enableDeltaModel, true);
expect(keyboardAppearance, 'Brightness.dark');
});

group('on iPhone 15 (iOS 17.5)', () {
testWidgetsOnIos('ignores keyboard autocorrections when pressing the action button', (tester) async {
await _pumpEmptySuperTextField(tester);

// Place the caret at the start of the text field.
await tester.placeCaretInSuperTextField(0);

// Type some text.
await tester.typeImeText('run tom');

// Press the "Done" button.
await tester.testTextInput.receiveAction(TextInputAction.done);

// Simulate the IME sending a delta replacing "tom" with "Tom".
await tester.ime.sendDeltas([
const TextEditingDeltaReplacement(
oldText: '. run tom',
replacementText: 'Tom',
replacedRange: TextRange(start: 6, end: 9),
selection: TextSelection.collapsed(offset: 9),
composing: TextRange(start: -1, end: -1),
),
], getter: imeClientGetter);
await tester.pump();

// Ensure the correction was ignored.
expect(SuperTextFieldInspector.findText().text, 'run tom');
});
});
});

testWidgetsOnAllPlatforms('updates IME configuration when it changes', (tester) async {
Expand Down

0 comments on commit 7082b08

Please sign in to comment.