Finalize editing when hitting the enter key on a single line TextField (#23015)

Behavior remains the same for multiline TextFields. This behavior already happens on iOS and Android, but not desktop. #23014
diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart
index 96785c0..c374cc4 100644
--- a/packages/flutter/lib/src/widgets/editable_text.dart
+++ b/packages/flutter/lib/src/widgets/editable_text.dart
@@ -511,44 +511,43 @@
   void performAction(TextInputAction action) {
     switch (action) {
       case TextInputAction.newline:
-        // Do nothing for a "newline" action: the newline is already inserted.
+        // If this is a multiline EditableText, do nothing for a "newline"
+        // action; The newline is already inserted. Otherwise, finalize
+        // editing.
+        if (widget.maxLines == 1)
+          _finalizeEditing(true);
         break;
       case TextInputAction.done:
       case TextInputAction.go:
       case TextInputAction.send:
       case TextInputAction.search:
-        // Take any actions necessary now that the user has completed editing.
-        if (widget.onEditingComplete != null) {
-          widget.onEditingComplete();
-        } else {
-          // Default behavior if the developer did not provide an
-          // onEditingComplete callback: Finalize editing and remove focus.
-          widget.controller.clearComposing();
-          widget.focusNode.unfocus();
-        }
-
-        // Invoke optional callback with the user's submitted content.
-        if (widget.onSubmitted != null)
-          widget.onSubmitted(_value.text);
+        _finalizeEditing(true);
         break;
       default:
-        if (widget.onEditingComplete != null) {
-          widget.onEditingComplete();
-        } else {
-          // Default behavior if the developer did not provide an
-          // onEditingComplete callback: Finalize editing, but don't give up
-          // focus because this keyboard action does not imply the user is done
-          // inputting information.
-          widget.controller.clearComposing();
-        }
-
-        // Invoke optional callback with the user's submitted content.
-        if (widget.onSubmitted != null)
-          widget.onSubmitted(_value.text);
+        // Finalize editing, but don't give up focus because this keyboard
+        //  action does not imply the user is done inputting information.
+        _finalizeEditing(false);
         break;
     }
   }
 
+  void _finalizeEditing(bool shouldUnfocus) {
+    // Take any actions necessary now that the user has completed editing.
+    if (widget.onEditingComplete != null) {
+      widget.onEditingComplete();
+    } else {
+      // Default behavior if the developer did not provide an
+      // onEditingComplete callback: Finalize editing and remove focus.
+      widget.controller.clearComposing();
+      if (shouldUnfocus)
+        widget.focusNode.unfocus();
+    }
+
+    // Invoke optional callback with the user's submitted content.
+    if (widget.onSubmitted != null)
+      widget.onSubmitted(_value.text);
+  }
+
   void _updateRemoteEditingValueIfNeeded() {
     if (!_hasInputConnection)
       return;
diff --git a/packages/flutter/test/widgets/editable_text_test.dart b/packages/flutter/test/widgets/editable_text_test.dart
index 8b502d8..f4a1b86 100644
--- a/packages/flutter/test/widgets/editable_text_test.dart
+++ b/packages/flutter/test/widgets/editable_text_test.dart
@@ -745,6 +745,94 @@
     // and onSubmission callbacks.
   });
 
+  testWidgets(
+      'When "newline" action is called on a Editable text with maxLines == 1 callbacks are invoked: onEditingComplete > onSubmitted',
+      (WidgetTester tester) async {
+    final GlobalKey<EditableTextState> editableTextKey =
+        GlobalKey<EditableTextState>();
+    final FocusNode focusNode = FocusNode();
+
+    bool onEditingCompleteCalled = false;
+    bool onSubmittedCalled = false;
+
+    final Widget widget = MaterialApp(
+      home: EditableText(
+        key: editableTextKey,
+        controller: TextEditingController(),
+        focusNode: focusNode,
+        style: Typography(platform: TargetPlatform.android).black.subhead,
+        cursorColor: Colors.blue,
+        maxLines: 1,
+        onEditingComplete: () {
+          onEditingCompleteCalled = true;
+          assert(!onSubmittedCalled);
+        },
+        onSubmitted: (String value) {
+          onSubmittedCalled = true;
+          assert(onEditingCompleteCalled);
+        },
+      ),
+    );
+    await tester.pumpWidget(widget);
+
+    // Select EditableText to give it focus.
+    final Finder textFinder = find.byKey(editableTextKey);
+    await tester.tap(textFinder);
+    await tester.pump();
+
+    assert(focusNode.hasFocus);
+
+    // The execution path starting with receiveAction() will trigger the
+    // onEditingComplete and onSubmission callbacks.
+    await tester.testTextInput.receiveAction(TextInputAction.newline);
+    // The expectations we care about are up above in the onEditingComplete
+    // and onSubmission callbacks.
+  });
+
+testWidgets(
+      'When "newline" action is called on a Editable text with maxLines != 1, onEditingComplete and onSubmitted callbacks are not invoked.',
+      (WidgetTester tester) async {
+    final GlobalKey<EditableTextState> editableTextKey =
+        GlobalKey<EditableTextState>();
+    final FocusNode focusNode = FocusNode();
+
+    bool onEditingCompleteCalled = false;
+    bool onSubmittedCalled = false;
+
+    final Widget widget = MaterialApp(
+      home: EditableText(
+        key: editableTextKey,
+        controller: TextEditingController(),
+        focusNode: focusNode,
+        style: Typography(platform: TargetPlatform.android).black.subhead,
+        cursorColor: Colors.blue,
+        maxLines: 3,
+        onEditingComplete: () {
+          onEditingCompleteCalled = true;
+        },
+        onSubmitted: (String value) {
+          onSubmittedCalled = true;
+        },
+      ),
+    );
+    await tester.pumpWidget(widget);
+
+    // Select EditableText to give it focus.
+    final Finder textFinder = find.byKey(editableTextKey);
+    await tester.tap(textFinder);
+    await tester.pump();
+
+    assert(focusNode.hasFocus);
+
+    // The execution path starting with receiveAction() will trigger the
+    // onEditingComplete and onSubmission callbacks.
+    await tester.testTextInput.receiveAction(TextInputAction.newline);
+
+    // These callbacks shouldn't have been triggered.
+    assert(!onSubmittedCalled);
+    assert(!onEditingCompleteCalled);
+  });
+
   testWidgets('Changing controller updates EditableText',
       (WidgetTester tester) async {
     final GlobalKey<EditableTextState> editableTextKey =