Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Android] Wire up text for SearchBar and Editor #2454

Merged
merged 10 commits into from Sep 11, 2021
33 changes: 33 additions & 0 deletions src/Core/src/Core/Extensions/ITextInputExtensions.cs
@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.Maui
{
public static class ITextInputExtensions
{
public static void UpdateText(this ITextInput textInput, string? text)
{
// Even though <null> is technically different to "", it has no
// functional difference to apps. Thus, hide it.
var mauiText = textInput.Text ?? string.Empty;
var nativeText = text ?? string.Empty;
if (mauiText != nativeText)
textInput.Text = nativeText;
}

#if __ANDROID__
public static void UpdateText(this ITextInput textInput, Android.Text.TextChangedEventArgs e)
{
if (e.BeforeCount == 0 && e.AfterCount == 0)
return;

if (e.Text is Java.Lang.ICharSequence cs)
textInput.UpdateText(cs.ToString());
else if (e.Text != null)
textInput.UpdateText(String.Concat(e.Text));
else
textInput.UpdateText((string?)null);
}
#endif
}
}
7 changes: 5 additions & 2 deletions src/Core/src/Handlers/Editor/EditorHandler.Android.cs
Expand Up @@ -34,20 +34,23 @@ protected override void ConnectHandler(AppCompatEditText nativeView)
{
FocusChangeListener.Handler = this;

nativeView.TextChanged += OnTextChanged;
nativeView.OnFocusChangeListener = FocusChangeListener;
}

protected override void DisconnectHandler(AppCompatEditText nativeView)
{
nativeView.OnFocusChangeListener = null;

nativeView.TextChanged -= OnTextChanged;
FocusChangeListener.Handler = null;
}

void OnTextChanged(object? sender, Android.Text.TextChangedEventArgs e) =>
VirtualView.UpdateText(e);

void SetupDefaults(AppCompatEditText nativeView)
{


DefaultTextColors = nativeView.TextColors;
DefaultPlaceholderTextColors = nativeView.HintTextColors;
DefaultBackground = nativeView.Background;
Expand Down
16 changes: 9 additions & 7 deletions src/Core/src/Handlers/Editor/EditorHandler.iOS.cs
Expand Up @@ -25,6 +25,7 @@ protected override void ConnectHandler(MauiTextView nativeView)
nativeView.Changed += OnChanged;
nativeView.ShouldChangeText += OnShouldChangeText;
nativeView.Ended += OnEnded;
nativeView.TextPropertySet += OnTextPropertySet;
}

protected override void DisconnectHandler(MauiTextView nativeView)
Expand All @@ -34,6 +35,7 @@ protected override void DisconnectHandler(MauiTextView nativeView)
nativeView.Changed -= OnChanged;
nativeView.ShouldChangeText -= OnShouldChangeText;
nativeView.Ended -= OnEnded;
nativeView.TextPropertySet -= OnTextPropertySet;
}

public override Size GetDesiredSize(double widthConstraint, double heightConstraint) =>
Expand Down Expand Up @@ -128,19 +130,19 @@ bool OnShouldChangeText(UITextView textView, NSRange range, string replacementSt

void OnEnded(object? sender, EventArgs eventArgs)
{
if (VirtualView == null || NativeView == null)
return;

if (NativeView.Text != VirtualView.Text)
VirtualView.Text = NativeView.Text ?? string.Empty;

// TODO: Update IsFocused property
VirtualView.Completed();
}

private void OnTextPropertySet(object? sender, EventArgs e)
{
VirtualView.UpdateText(NativeView.Text);
}


public static void MapKeyboard(EditorHandler handler, IEditor editor)
{
handler.NativeView?.UpdateKeyboard(editor);
}
}
}
}
39 changes: 6 additions & 33 deletions src/Core/src/Handlers/Entry/EntryHandler.Android.cs
Expand Up @@ -14,7 +14,6 @@ namespace Microsoft.Maui.Handlers
{
public partial class EntryHandler : ViewHandler<IEntry, AppCompatEditText>
{
readonly TextWatcher _watcher = new();
readonly EntryTouchListener _touchListener = new();
readonly EntryFocusChangeListener _focusChangeListener = new();
readonly EditorActionListener _actionListener = new();
Expand All @@ -37,13 +36,12 @@ protected override AppCompatEditText CreateNativeView()

protected override void ConnectHandler(AppCompatEditText nativeView)
{
_watcher.Handler = this;
_touchListener.Handler = this;
_focusChangeListener.Handler = this;
_actionListener.Handler = this;

nativeView.TextChanged += OnTextChanged;
nativeView.OnFocusChangeListener = _focusChangeListener;
nativeView.AddTextChangedListener(_watcher);
nativeView.SetOnTouchListener(_touchListener);
nativeView.SetOnEditorActionListener(_actionListener);
}
Expand All @@ -52,17 +50,19 @@ protected override void DisconnectHandler(AppCompatEditText nativeView)
{
_clearButtonDrawable = null;

nativeView.RemoveTextChangedListener(_watcher);
nativeView.TextChanged -= OnTextChanged;
nativeView.SetOnTouchListener(null);
nativeView.OnFocusChangeListener = null;
nativeView.SetOnEditorActionListener(null);

_focusChangeListener.Handler = null;
_watcher.Handler = null;
_touchListener.Handler = null;
_actionListener.Handler = null;
}

void OnTextChanged(object? sender, Android.Text.TextChangedEventArgs e) =>
VirtualView.UpdateText(e);

// This is a Android-specific mapping
public static void MapBackground(EntryHandler handler, IEntry entry)
{
Expand Down Expand Up @@ -180,12 +180,7 @@ void OnTextChanged(string? text)
if (VirtualView == null || NativeView == null)
return;

// Even though <null> is technically different to "", it has no
// functional difference to apps. Thus, hide it.
var mauiText = VirtualView.Text ?? string.Empty;
var nativeText = text ?? string.Empty;
if (mauiText != nativeText)
VirtualView.Text = nativeText;
VirtualView.UpdateText(text);

// Text changed should trigger clear button visibility.
UpdateValue(nameof(VirtualView.ClearButtonVisibility));
Expand Down Expand Up @@ -235,28 +230,6 @@ bool HandleClearButtonTouched(MotionEvent? motionEvent)
return false;
}

class TextWatcher : Java.Lang.Object, ITextWatcher
{
public EntryHandler? Handler { get; set; }

void ITextWatcher.AfterTextChanged(IEditable? s)
{
}

void ITextWatcher.BeforeTextChanged(Java.Lang.ICharSequence? s, int start, int count, int after)
{
}

void ITextWatcher.OnTextChanged(Java.Lang.ICharSequence? s, int start, int before, int count)
{
// We are replacing 0 characters with 0 characters, so skip
if (before == 0 && count == 0)
return;

Handler?.OnTextChanged(s?.ToString());
}
}

// TODO: Maybe better to have generic version in INativeViewHandler?
class EntryTouchListener : Java.Lang.Object, IOnTouchListener
{
Expand Down
7 changes: 1 addition & 6 deletions src/Core/src/Handlers/Entry/EntryHandler.iOS.cs
Expand Up @@ -173,12 +173,7 @@ void OnTextChanged()
if (VirtualView == null || NativeView == null)
return;

// Even though <null> is technically different to "", it has no
// functional difference to apps. Thus, hide it.
var mauiText = VirtualView!.Text ?? string.Empty;
var nativeText = NativeView.Text ?? string.Empty;
if (mauiText != nativeText)
VirtualView.Text = nativeText;
VirtualView.UpdateText(NativeView.Text);
}

bool OnShouldChangeCharacters(UITextField textField, NSRange range, string replacementString)
Expand Down
37 changes: 14 additions & 23 deletions src/Core/src/Handlers/SearchBar/SearchBarHandler.Android.cs
Expand Up @@ -8,8 +8,6 @@ namespace Microsoft.Maui.Handlers
{
public partial class SearchBarHandler : ViewHandler<ISearchBar, SearchView>
{
QueryTextListener QueryListener { get; } = new QueryTextListener();

static Drawable? DefaultBackground;
static ColorStateList? DefaultPlaceholderTextColors { get; set; }

Expand All @@ -28,16 +26,14 @@ protected override SearchView CreateNativeView()

protected override void ConnectHandler(SearchView nativeView)
{
QueryListener.Handler = this;

nativeView.SetOnQueryTextListener(QueryListener);
nativeView.QueryTextChange += OnQueryTextChange;
nativeView.QueryTextSubmit += OnQueryTextSubmit;
}

protected override void DisconnectHandler(SearchView nativeView)
{
nativeView.SetOnQueryTextListener(null);

QueryListener.Handler = null;
nativeView.QueryTextChange -= OnQueryTextChange;
nativeView.QueryTextSubmit -= OnQueryTextSubmit;
}

void SetupDefaults(SearchView nativeView)
Expand Down Expand Up @@ -104,23 +100,18 @@ public static void MapCancelButtonColor(SearchBarHandler handler, ISearchBar sea
handler.NativeView?.UpdateCancelButtonColor(searchBar);
}

public class QueryTextListener : Java.Lang.Object, IOnQueryTextListener
{
public SearchBarHandler? Handler { get; set; }

public bool OnQueryTextChange(string newText)
{
return true;
}

public bool OnQueryTextSubmit(string newText)
{
Handler?.VirtualView?.SearchButtonPressed();

// TODO: Clear focus
void OnQueryTextSubmit(object? sender, QueryTextSubmitEventArgs e)
{
VirtualView.SearchButtonPressed();
// TODO: Clear focus
e.Handled = true;
}

return true;
}
void OnQueryTextChange(object? sender, QueryTextChangeEventArgs e)
{
VirtualView.UpdateText(e.NewText);
e.Handled = true;
}
}
}
27 changes: 14 additions & 13 deletions src/Core/src/Handlers/SearchBar/SearchBarHandler.iOS.cs
Expand Up @@ -6,7 +6,7 @@

namespace Microsoft.Maui.Handlers
{
public partial class SearchBarHandler : ViewHandler<ISearchBar, UISearchBar>
public partial class SearchBarHandler : ViewHandler<ISearchBar, MauiSearchBar>
{
UIColor? _defaultTextColor;

Expand All @@ -18,32 +18,33 @@ public partial class SearchBarHandler : ViewHandler<ISearchBar, UISearchBar>

public UITextField? QueryEditor => _editor;

protected override UISearchBar CreateNativeView()
protected override MauiSearchBar CreateNativeView()
{
var searchBar = new UISearchBar(RectangleF.Empty) { ShowsCancelButton = true, BarStyle = UIBarStyle.Default };
var searchBar = new MauiSearchBar() { ShowsCancelButton = true, BarStyle = UIBarStyle.Default };

_editor = searchBar.FindDescendantView<UITextField>();
if (NativeVersion.IsAtLeast(13))
_editor = searchBar.SearchTextField;
else
_editor = searchBar.FindDescendantView<UITextField>();

return searchBar;
}

protected override void ConnectHandler(UISearchBar nativeView)
protected override void ConnectHandler(MauiSearchBar nativeView)
{
nativeView.CancelButtonClicked += OnCancelClicked;
nativeView.SearchButtonClicked += OnSearchButtonClicked;
nativeView.TextChanged += OnTextChanged;
nativeView.TextPropertySet += OnTextPropertySet;
nativeView.ShouldChangeTextInRange += ShouldChangeText;

base.ConnectHandler(nativeView);
}

protected override void DisconnectHandler(UISearchBar nativeView)
protected override void DisconnectHandler(MauiSearchBar nativeView)
{
nativeView.CancelButtonClicked -= OnCancelClicked;
nativeView.SearchButtonClicked -= OnSearchButtonClicked;
nativeView.TextChanged -= OnTextChanged;
nativeView.TextPropertySet -= OnTextPropertySet;
nativeView.ShouldChangeTextInRange -= ShouldChangeText;

base.DisconnectHandler(nativeView);
}

Expand Down Expand Up @@ -146,10 +147,10 @@ void OnSearchButtonClicked(object? sender, EventArgs e)
NativeView?.ResignFirstResponder();
}

void OnTextChanged(object? sender, UISearchBarTextChangedEventArgs a)
void OnTextPropertySet(object? sender, EventArgs e)
{
if (VirtualView != null)
VirtualView.Text = a.SearchText;
VirtualView.UpdateText(NativeView?.Text);
}

bool ShouldChangeText(UISearchBar searchBar, NSRange range, string text)
Expand All @@ -158,4 +159,4 @@ bool ShouldChangeText(UISearchBar searchBar, NSRange range, string text)
return newLength <= VirtualView?.MaxLength;
}
}
}
}
47 changes: 47 additions & 0 deletions src/Core/src/Platform/iOS/MauiSearchBar.cs
@@ -0,0 +1,47 @@
using System;
using System.Drawing;
using CoreGraphics;
using Foundation;
using UIKit;

namespace Microsoft.Maui.Handlers
{
public class MauiSearchBar : UISearchBar
{
public MauiSearchBar() : this(RectangleF.Empty)
{
}

public MauiSearchBar(NSCoder coder) : base(coder)
{
}

public MauiSearchBar(CGRect frame) : base(frame)
{
}

protected MauiSearchBar(NSObjectFlag t) : base(t)
{
}

protected internal MauiSearchBar(IntPtr handle) : base(handle)
{
}

public override string? Text
{
get => base.Text;
set
{
var old = base.Text;

base.Text = value;

if (old != value)
TextPropertySet?.Invoke(this, EventArgs.Empty);
}
}

public event EventHandler? TextPropertySet;
}
}