Browse Source

Android - Input Connection fixes (#16666)

* send enter key event on soft keyboard action

* reset composition region when text buffer is updated

* update input connection state when batch edit ends
Emmanuel Hansen 1 year ago
parent
commit
7cced30e77

+ 2 - 36
src/Android/Avalonia.Android/Platform/Input/AndroidInputMethod.cs

@@ -82,21 +82,7 @@ namespace Avalonia.Android.Platform.Input
 
                 _imm.ShowSoftInput(_host, ShowFlags.Implicit);
 
-                var selection = Client.Selection;
-
-                _imm.UpdateSelection(_host, selection.Start, selection.End, selection.Start, selection.End);
-
-                var surroundingText = _client.SurroundingText ?? "";
-
-                var extractedText = new ExtractedText
-                {
-                    Text = new Java.Lang.String(surroundingText),
-                    SelectionStart = selection.Start,
-                    SelectionEnd = selection.End,
-                    PartialEndOffset = surroundingText.Length
-                };
-
-                _imm.UpdateExtractedText(_host, _inputConnection?.ExtractedTextToken ?? 0, extractedText);
+                _inputConnection?.UpdateState();
 
                 _client.SurroundingTextChanged += _client_SurroundingTextChanged;
                 _client.SelectionChanged += _client_SelectionChanged;
@@ -150,27 +136,7 @@ namespace Avalonia.Android.Platform.Input
 
         private void OnSurroundingTextChanged()
         {
-            if (_client is null || _inputConnection is null || _inputConnection.IsInUpdate)
-            {
-                return;
-            }
-
-            if (_inputConnection.IsInMonitorMode)
-            {
-                var surroundingText = _client.SurroundingText ?? "";
-
-                var selection = _client.Selection;
-
-                var extractedText = new ExtractedText
-                {
-                    Text = new Java.Lang.String(surroundingText),
-                    SelectionStart = selection.Start,
-                    SelectionEnd = selection.End,
-                    PartialEndOffset = surroundingText.Length
-                };
-
-                _imm.UpdateExtractedText(_host, _inputConnection.ExtractedTextToken, extractedText);
-            }
+            _inputConnection?.UpdateState();
         }
 
         public void SetCursorRect(Rect rect)

+ 37 - 13
src/Android/Avalonia.Android/Platform/Input/AvaloniaInputConnection.cs

@@ -44,6 +44,20 @@ namespace Avalonia.Android.Platform.Input
 
         public bool IsInUpdate { get; set; }
 
+        internal void UpdateState()
+        {
+            var selection = _editBuffer.Selection;
+
+            if (IsInMonitorMode && InputMethod.Client is { } client)
+            {
+                InputMethod.IMM.UpdateExtractedText(InputMethod.View, ExtractedTextToken,
+                    _editBuffer.ExtractedText);
+            }
+
+            var composition = _editBuffer.HasComposition ? _editBuffer.Composition!.Value : new TextSelection(-1, -1);
+            InputMethod.IMM.UpdateSelection(InputMethod.View, selection.Start, selection.End, composition.Start, composition.End);
+        }
+
         public bool SetComposingRegion(int start, int end)
         {
             if (InputMethod.IsActive)
@@ -101,6 +115,8 @@ namespace Avalonia.Android.Platform.Input
                 }
                 IsInUpdate = false;
             }
+
+            UpdateState();
             return IsInBatchEdit;
         }
 
@@ -147,6 +163,26 @@ namespace Avalonia.Android.Platform.Input
                     }
             }
 
+            var eventTime = SystemClock.UptimeMillis();
+            SendKeyEvent(new KeyEvent(eventTime,
+                                      eventTime,
+                                      KeyEventActions.Down,
+                                      Keycode.Enter,
+                                      0,
+                                      0,
+                                      0,
+                                      0,
+                                      KeyEventFlags.SoftKeyboard | KeyEventFlags.KeepTouchMode | KeyEventFlags.EditorAction));
+            SendKeyEvent(new KeyEvent(eventTime,
+                                      eventTime,
+                                      KeyEventActions.Up,
+                                      Keycode.Enter,
+                                      0,
+                                      0,
+                                      0,
+                                      0,
+                                      KeyEventFlags.SoftKeyboard | KeyEventFlags.KeepTouchMode | KeyEventFlags.EditorAction));
+
             return InputMethod.IsActive;
         }
 
@@ -161,19 +197,7 @@ namespace Avalonia.Android.Platform.Input
                 return null;
             }
 
-            var extract = new ExtractedText
-            {
-                Flags = 0,
-                PartialStartOffset = -1,
-                PartialEndOffset = -1,
-                SelectionStart = _editBuffer.Selection.Start,
-                SelectionEnd = _editBuffer.Selection.End,
-                StartOffset = 0
-            };
-
-            extract.Text = new SpannableString(_editBuffer.Text);
-
-            return extract;
+            return _editBuffer.ExtractedText;
         }
 
         public bool PerformContextMenuAction(int id)

+ 5 - 6
src/Android/Avalonia.Android/Platform/Input/EditCommand.cs

@@ -21,7 +21,9 @@ namespace Avalonia.Android.Platform.Input
 
         public override void Apply(TextEditBuffer buffer)
         {
-            buffer.Selection = new TextSelection(Math.Max(_start, 0), Math.Min(_end, buffer.Text.Length));
+            var start = Math.Clamp(_start, 0, buffer.Text.Length);
+            var end = Math.Clamp(_end, 0, buffer.Text.Length);
+            buffer.Selection = new TextSelection(start, end);
         }
     }
 
@@ -157,14 +159,11 @@ namespace Avalonia.Android.Platform.Input
         {
             if (buffer.HasComposition)
             {
-                buffer.Remove(buffer.Composition!.Value.Start, buffer.Composition!.Value.End - buffer.Composition!.Value.Start);
-                buffer.Insert(buffer.Composition!.Value.Start, _text);
+                buffer.Replace(buffer.Composition!.Value.Start, buffer.Composition!.Value.End, _text);
             }
             else
             {
-                buffer.Remove(buffer.Selection.Start, buffer.Selection.End - buffer.Selection.Start);
-                buffer.Insert(buffer.Selection.Start, _text);
-
+                buffer.Replace(buffer.Selection.Start, buffer.Selection.End, _text);
             }
             var newCursor = _newCursorPosition > 0 ? buffer.Selection.Start + _newCursorPosition - 1 : buffer.Selection.Start + _newCursorPosition - _text.Length;
             buffer.Selection = new TextSelection(newCursor, newCursor);

+ 30 - 10
src/Android/Avalonia.Android/Platform/Input/TextEditBuffer.cs

@@ -1,5 +1,7 @@
 using System;
+using Android.Text;
 using Android.Views;
+using Android.Views.InputMethods;
 using Avalonia.Android.Platform.SkiaPlatform;
 using Avalonia.Input.TextInput;
 
@@ -64,15 +66,13 @@ namespace Avalonia.Android.Platform.Input
                 if (HasComposition)
                 {
                     var start = Composition!.Value.Start;
-                    Remove(start, Composition!.Value.End - start);
-                    Insert(start, value ?? "");
+                    Replace(Composition!.Value.Start, Composition!.Value.End, value ?? "");
                     Composition = new TextSelection(start, start  + (value?.Length ?? 0));
                 }
                 else
                 {
                     var start = Selection.Start;
-                    Remove(start, Selection.End - start);
-                    Insert(start, value ?? "");
+                    Replace(start, Selection.End, value ?? "");
                     Composition = new TextSelection(start, start + (value?.Length ?? 0));
                 }
             }
@@ -80,22 +80,42 @@ namespace Avalonia.Android.Platform.Input
 
         public string Text => _textInputMethod.Client?.SurroundingText ?? "";
 
-        internal void Insert(int index, string text)
+        public ExtractedText? ExtractedText => new ExtractedText
+        {
+            Flags = Text.Contains('\n') ? 0 : ExtractedTextFlags.SingleLine,
+            PartialStartOffset = -1,
+            PartialEndOffset = Text.Length,
+            SelectionStart = Selection.Start,
+            SelectionEnd = Selection.End,
+            StartOffset = 0,
+            Text = new SpannableString(Text)
+        };
+
+        internal void Remove(int index, int length)
         {
             if (_textInputMethod.Client is { } client)
             {
-                client.Selection = new TextSelection(index, index);
-                _topLevel.TextInput(text);
+                client.Selection = new TextSelection(index, index + length);
+                if (length > 0)
+                    _textInputMethod?.View.DispatchKeyEvent(new KeyEvent(KeyEventActions.Down, Keycode.ForwardDel));
             }
         }
 
-        internal void Remove(int index, int length)
+        internal void Replace(int start, int end, string text)
         {
             if (_textInputMethod.Client is { } client)
             {
-                client.Selection = new TextSelection(index, index + length);
-                if (length > 0)
+                var realStart = Math.Min(start, end);
+                var realEnd = Math.Max(start, end);
+                if (realEnd > realStart)
+                {
+                    client.Selection = new TextSelection(realStart, realEnd);
                     _textInputMethod?.View.DispatchKeyEvent(new KeyEvent(KeyEventActions.Down, Keycode.ForwardDel));
+                }
+                _topLevel.TextInput(text);
+                var index = realStart + text.Length;
+                client.Selection = new TextSelection(index, index);
+                Composition = null;
             }
         }
     }