From 81ba0331f55694959d3f5824f6a7cfa78f8c047b Mon Sep 17 00:00:00 2001
From: justin <noborder@qq.com>
Date: Tue, 26 May 2020 17:15:36 +0800
Subject: [PATCH] =?UTF-8?q?=E6=8A=8AXTextinputplugin=E6=94=B9=E6=88=90?=
 =?UTF-8?q?=E5=8D=95=E4=BE=8B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../idlefish/flutterboost/XFlutterView.java   | 55 ++----------
 .../flutterboost/XInputConnectionAdaptor.java |  2 +-
 .../flutterboost/XTextInputPlugin.java        | 88 ++++++++++++++-----
 example/lib/simple_page_widgets.dart          | 38 +++++---
 4 files changed, 100 insertions(+), 83 deletions(-)

diff --git a/android/src/main/java/com/idlefish/flutterboost/XFlutterView.java b/android/src/main/java/com/idlefish/flutterboost/XFlutterView.java
index 6bf4465..79e3f50 100644
--- a/android/src/main/java/com/idlefish/flutterboost/XFlutterView.java
+++ b/android/src/main/java/com/idlefish/flutterboost/XFlutterView.java
@@ -43,27 +43,7 @@ import io.flutter.plugin.editing.TextInputPlugin;
 import io.flutter.plugin.platform.PlatformViewsController;
 import io.flutter.view.AccessibilityBridge;
 
-/**
- * Displays a Flutter UI on an Android device.
- * <p>
- * A {@code FlutterView}'s UI is painted by a corresponding {@link FlutterEngine}.
- * <p>
- * A {@code FlutterView} can operate in 2 different {@link RenderMode}s:
- * <ol>
- *   <li>{@link RenderMode#surface}, which paints a Flutter UI to a {@link android.view.SurfaceView}.
- *   This mode has the best performance, but a {@code FlutterView} in this mode cannot be positioned
- *   between 2 other Android {@code View}s in the z-index, nor can it be animated/transformed.
- *   Unless the special capabilities of a {@link android.graphics.SurfaceTexture} are required,
- *   developers should strongly prefer this render mode.</li>
- *   <li>{@link RenderMode#texture}, which paints a Flutter UI to a {@link android.graphics.SurfaceTexture}.
- *   This mode is not as performant as {@link RenderMode#surface}, but a {@code FlutterView} in this
- *   mode can be animated and transformed, as well as positioned in the z-index between 2+ other
- *   Android {@code Views}. Unless the special capabilities of a {@link android.graphics.SurfaceTexture}
- *   are required, developers should strongly prefer the {@link RenderMode#surface} render mode.</li>
- * </ol>
- * See <a>https://source.android.com/devices/graphics/arch-tv#surface_or_texture</a> for more
- * information comparing {@link android.view.SurfaceView} and {@link android.view.TextureView}.
- */
+
 public class XFlutterView extends FrameLayout {
   private static final String TAG = "FlutterView";
 
@@ -150,26 +130,12 @@ public class XFlutterView extends FrameLayout {
     this(context, null, null, null);
   }
 
-  /**
-   * Constructs a {@code FlutterView} programmatically, without any XML attributes,
-   * and allows selection of a {@link #renderMode}.
-   * <p>
-   * {@link #transparencyMode} defaults to {@link TransparencyMode#opaque}.
-   * <p>
-   * {@code FlutterView} requires an {@code Activity} instead of a generic {@code Context}
-   * to be compatible with {@link PlatformViewsController}.
-   */
+
   public XFlutterView(@NonNull Context context, @NonNull FlutterView.RenderMode renderMode) {
     this(context, null, renderMode, null);
   }
 
-  /**
-   * Constructs a {@code FlutterView} programmatically, without any XML attributes,
-   * assumes the use of {@link RenderMode#surface}, and allows selection of a {@link #transparencyMode}.
-   * <p>
-   * {@code FlutterView} requires an {@code Activity} instead of a generic {@code Context}
-   * to be compatible with {@link PlatformViewsController}.
-   */
+
   public XFlutterView(@NonNull Context context, @NonNull FlutterView.TransparencyMode transparencyMode) {
     this(context, null, FlutterView.RenderMode.surface, transparencyMode);
   }
@@ -609,16 +575,9 @@ public class XFlutterView extends FrameLayout {
     this.flutterEngine.getPlatformViewsController().attachToView(this);
 
 
-
-    if(textInputPlugin==null){
-      textInputPlugin = new XTextInputPlugin(
-              this,
-              flutterEngine.getTextInputChannel(),
-              this.flutterEngine.getPlatformViewsController()
-      );
-    }
-
-    textInputPlugin.setTextInputMethodHandler();
+    textInputPlugin= XTextInputPlugin.getTextInputPlugin(  this.flutterEngine.getDartExecutor(),
+            this.flutterEngine.getPlatformViewsController());
+    textInputPlugin.updateView(this);
     textInputPlugin.getInputMethodManager().restartInput(this);
 
 
@@ -717,7 +676,7 @@ public class XFlutterView extends FrameLayout {
   }
   public void release(){
     if(textInputPlugin!=null){
-      textInputPlugin.release();
+//      textInputPlugin.release();
     }
   }
 
diff --git a/android/src/main/java/com/idlefish/flutterboost/XInputConnectionAdaptor.java b/android/src/main/java/com/idlefish/flutterboost/XInputConnectionAdaptor.java
index 3500c47..68f0f55 100644
--- a/android/src/main/java/com/idlefish/flutterboost/XInputConnectionAdaptor.java
+++ b/android/src/main/java/com/idlefish/flutterboost/XInputConnectionAdaptor.java
@@ -31,6 +31,7 @@ import io.flutter.plugin.common.ErrorLogResult;
 import io.flutter.plugin.common.MethodChannel;
 
 class XInputConnectionAdaptor extends BaseInputConnection {
+
     private final View mFlutterView;
     private final int mClient;
     private final TextInputChannel textInputChannel;
@@ -221,7 +222,6 @@ class XInputConnectionAdaptor extends BaseInputConnection {
                         mEditable.delete(selStart, selEnd);
                     mEditable.insert(selStart, String.valueOf((char) character));
                     setSelection(selStart + 1, selStart + 1);
-                    updateEditingState();
                 }
                 return true;
             }
diff --git a/android/src/main/java/com/idlefish/flutterboost/XTextInputPlugin.java b/android/src/main/java/com/idlefish/flutterboost/XTextInputPlugin.java
index 37c9795..a55f454 100644
--- a/android/src/main/java/com/idlefish/flutterboost/XTextInputPlugin.java
+++ b/android/src/main/java/com/idlefish/flutterboost/XTextInputPlugin.java
@@ -1,8 +1,16 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
 package com.idlefish.flutterboost;
 
+
+import android.annotation.SuppressLint;
 import android.content.Context;
+import android.os.Build;
+import android.provider.Settings;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
 import android.text.Editable;
 import android.text.InputType;
 import android.text.Selection;
@@ -11,6 +19,7 @@ import android.view.inputmethod.BaseInputConnection;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
 
 import io.flutter.embedding.engine.dart.DartExecutor;
 import io.flutter.embedding.engine.systemchannels.TextInputChannel;
@@ -21,11 +30,11 @@ import io.flutter.plugin.platform.PlatformViewsController;
  */
 public class XTextInputPlugin {
     @NonNull
-    private View mView;
+    private  View mView;
     @NonNull
-    private final InputMethodManager mImm;
+    private  InputMethodManager mImm;
     @NonNull
-    private final TextInputChannel textInputChannel;
+    private  TextInputChannel textInputChannel;
     @NonNull
     private InputTarget inputTarget = new InputTarget(InputTarget.Type.NO_TARGET, 0);
     @Nullable
@@ -37,26 +46,34 @@ public class XTextInputPlugin {
     private InputConnection lastInputConnection;
     @NonNull
     private PlatformViewsController platformViewsController;
+    private  boolean restartAlwaysRequired;
 
     // When true following calls to createInputConnection will return the cached lastInputConnection if the input
     // target is a platform view. See the comments on lockPlatformViewInputConnection for more details.
     private boolean isInputConnectionLocked;
+    private static  XTextInputPlugin xTextInputPlugin;
+
+    public  static  XTextInputPlugin getTextInputPlugin( DartExecutor dartExecutor, @NonNull PlatformViewsController platformViewsController){
+        if(xTextInputPlugin!=null) return xTextInputPlugin;
+        xTextInputPlugin =new XTextInputPlugin(dartExecutor,platformViewsController);
+        return  xTextInputPlugin;
+    }
+
+    public XTextInputPlugin(@NonNull DartExecutor dartExecutor, @NonNull PlatformViewsController platformViewsController) {
 
-    public XTextInputPlugin(View view, @NonNull TextInputChannel textInputChannel, @NonNull PlatformViewsController platformViewsController) {
-        mView = view;
-        mImm = (InputMethodManager) view.getContext().getSystemService(
-                Context.INPUT_METHOD_SERVICE);
-        this.textInputChannel = textInputChannel;
+
+        textInputChannel = new TextInputChannel(dartExecutor);
+
+
+        textInputChannel.requestExistingInputState();
 
         this.platformViewsController = platformViewsController;
 //        this.platformViewsController.attachTextInputPlugin(this);
     }
-
-    public void release() {
-        mView = null;
-    }
-
-    public void setTextInputMethodHandler() {
+    public  void updateView(View view){
+        mView = view;
+        mImm = (InputMethodManager) view.getContext().getSystemService(
+                Context.INPUT_METHOD_SERVICE);
 
         textInputChannel.setTextInputMethodHandler(new TextInputChannel.TextInputMethodHandler() {
             @Override
@@ -89,8 +106,12 @@ public class XTextInputPlugin {
                 clearTextInputClient();
             }
         });
+        restartAlwaysRequired = isRestartAlwaysRequired();
+
     }
 
+
+
     @NonNull
     public InputMethodManager getInputMethodManager() {
         return mImm;
@@ -114,7 +135,7 @@ public class XTextInputPlugin {
 
     /**
      * Unlocks the input connection.
-     * <p>
+     *
      * See also: @{link lockPlatformViewInputConnection}.
      */
     public void unlockPlatformViewInputConnection() {
@@ -123,7 +144,7 @@ public class XTextInputPlugin {
 
     /**
      * Detaches the text input plugin from the platform views controller.
-     * <p>
+     *
      * The TextInputPlugin instance should not be used after calling this.
      */
     public void destroy() {
@@ -134,6 +155,7 @@ public class XTextInputPlugin {
             TextInputChannel.InputType type,
             boolean obscureText,
             boolean autocorrect,
+            boolean enableSuggestions,
             TextInputChannel.TextCapitalization textCapitalization
     ) {
         if (type.type == TextInputChannel.TextInputType.DATETIME) {
@@ -168,6 +190,7 @@ public class XTextInputPlugin {
             textType |= InputType.TYPE_TEXT_VARIATION_PASSWORD;
         } else {
             if (autocorrect) textType |= InputType.TYPE_TEXT_FLAG_AUTO_CORRECT;
+            if (!enableSuggestions) textType |= InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
         }
 
         if (textCapitalization == TextInputChannel.TextCapitalization.CHARACTERS) {
@@ -199,6 +222,7 @@ public class XTextInputPlugin {
                 configuration.inputType,
                 configuration.obscureText,
                 configuration.autocorrect,
+                configuration.enableSuggestions,
                 configuration.textCapitalization
         );
         outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN;
@@ -238,7 +262,7 @@ public class XTextInputPlugin {
 
     /**
      * Clears a platform view text input client if it is the current input target.
-     * <p>
+     *
      * This is called when a platform view is disposed to make sure we're not hanging to a stale input
      * connection.
      */
@@ -264,7 +288,7 @@ public class XTextInputPlugin {
         mImm.hideSoftInputFromWindow(view.getApplicationWindowToken(), 0);
     }
 
-    private void setTextInputClient(int client, TextInputChannel.Configuration configuration) {
+    @VisibleForTesting void setTextInputClient(int client, TextInputChannel.Configuration configuration) {
         inputTarget = new InputTarget(InputTarget.Type.FRAMEWORK_CLIENT, client);
         this.configuration = configuration;
         mEditable = Editable.Factory.getInstance().newEditable("");
@@ -296,8 +320,8 @@ public class XTextInputPlugin {
         }
     }
 
-    private void setTextInputEditingState(View view, TextInputChannel.TextEditState state) {
-        if (!mRestartInputPending && state.text.equals(mEditable.toString())) {
+    @VisibleForTesting void setTextInputEditingState(View view, TextInputChannel.TextEditState state) {
+        if (!restartAlwaysRequired && !mRestartInputPending && state.text.equals(mEditable.toString())) {
             applyStateToSelection(state);
             mImm.updateSelection(mView, Math.max(Selection.getSelectionStart(mEditable), 0),
                     Math.max(Selection.getSelectionEnd(mEditable), 0),
@@ -311,6 +335,28 @@ public class XTextInputPlugin {
         }
     }
 
+    // Samsung's Korean keyboard has a bug where it always attempts to combine characters based on
+    // its internal state, ignoring if and when the cursor is moved programmatically. The same bug
+    // also causes non-korean keyboards to occasionally duplicate text when tapping in the middle
+    // of existing text to edit it.
+    //
+    // Fully restarting the IMM works around this because it flushes the keyboard's internal state
+    // and stops it from trying to incorrectly combine characters. However this also has some
+    // negative performance implications, so we don't want to apply this workaround in every case.
+    @SuppressLint("NewApi") // New API guard is inline, the linter can't see it.
+    @SuppressWarnings("deprecation")
+    private boolean isRestartAlwaysRequired() {
+        InputMethodSubtype subtype = mImm.getCurrentInputMethodSubtype();
+        // Impacted devices all shipped with Android Lollipop or newer.
+        if (subtype == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP || !Build.MANUFACTURER.equals("samsung")) {
+            return false;
+        }
+        String keyboardName = Settings.Secure.getString(mView.getContext().getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
+        // The Samsung keyboard is called "com.sec.android.inputmethod/.SamsungKeypad" but look
+        // for "Samsung" just in case Samsung changes the name of the keyboard.
+        return keyboardName.contains("Samsung");
+    }
+
     private void clearTextInputClient() {
         if (inputTarget.type == InputTarget.Type.PLATFORM_VIEW) {
             // Focus changes in the framework tree have no guarantees on the order focus nodes are notified. A node
@@ -353,4 +399,4 @@ public class XTextInputPlugin {
         // For platform views this is the platform view's ID.
         int id;
     }
-}
\ No newline at end of file
+}
diff --git a/example/lib/simple_page_widgets.dart b/example/lib/simple_page_widgets.dart
index 5249dd6..407ca82 100644
--- a/example/lib/simple_page_widgets.dart
+++ b/example/lib/simple_page_widgets.dart
@@ -350,20 +350,32 @@ class _FlutterRouteWidgetState extends State<FlutterRouteWidget> {
                   alignment: AlignmentDirectional.center,
                 ),
 //                Expanded(child: Container()),
-                const CupertinoTextField(
-                  prefix: Icon(
-                    CupertinoIcons.person_solid,
-                    color: CupertinoColors.lightBackgroundGray,
-                    size: 28.0,
-                  ),
-                  padding: EdgeInsets.symmetric(horizontal: 6.0, vertical: 12.0),
-                  clearButtonMode: OverlayVisibilityMode.editing,
-                  textCapitalization: TextCapitalization.words,
+        const CupertinoTextField(
+          prefix: Icon(
+            CupertinoIcons.person_solid,
+            color: CupertinoColors.lightBackgroundGray,
+            size: 28.0,
+          ),
+          padding: EdgeInsets.symmetric(horizontal: 6.0, vertical: 12.0),
+          clearButtonMode: OverlayVisibilityMode.editing,
+          textCapitalization: TextCapitalization.words,
+        ),
+                new TextField(
+                  enabled: true,
+                  autocorrect: true,
+                  style: const TextStyle(
+                      fontSize: 20.0,
+                      color: const Color(0xFF222222),
+                      fontWeight: FontWeight.w500),
+                ), new TextField(
+                  controller: new TextEditingController(),
+                  focusNode:FocusNode(),
+                  enabled: true,
                   autocorrect: false,
-                  decoration: BoxDecoration(
-                    border: Border(bottom: BorderSide(width: 0.0, color: CupertinoColors.inactiveGray)),
-                  ),
-                  placeholder: 'Name',
+                  style: const TextStyle(
+                      fontSize: 20.0,
+                      color: const Color(0xFF222222),
+                      fontWeight: FontWeight.w500),
                 ),
                 InkWell(
                   child: Container(
-- 
2.26.2