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

[Entry] Enable support for text events #460

Merged
merged 3 commits into from
Mar 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ void ITextWatcher.BeforeTextChanged(ICharSequence s, int start, int count, int a
{
}

[PortHandler]
void ITextWatcher.OnTextChanged(ICharSequence s, int start, int before, int count)
{
Internals.TextTransformUtilites.SetPlainText(Element, s?.ToString());
Expand Down Expand Up @@ -524,6 +525,7 @@ protected virtual void UpdateIsReadOnly()
EditText.Focusable = isReadOnly;
}

[PortHandler("Ported Text setter")]
void UpdateText()
{
var text = Element.UpdateFormsText(Element.Text, Element.TextTransform);
Expand Down
3 changes: 3 additions & 0 deletions src/Compatibility/Core/src/iOS/Renderers/EntryRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -220,12 +220,14 @@ void OnEditingBegan(object sender, EventArgs e)
ElementController.SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, true);
}

[PortHandler("Ported Text setter")]
void OnEditingChanged(object sender, EventArgs eventArgs)
{
ElementController.SetValueFromRenderer(Entry.TextProperty, Control.Text);
UpdateCursorFromControl(null);
}

[PortHandler("Ported Text setter")]
void OnEditingEnded(object sender, EventArgs e)
{
// Typing aid changes don't always raise EditingChanged event
Expand Down Expand Up @@ -361,6 +363,7 @@ protected virtual void UpdatePlaceholder()
protected virtual void UpdateAttributedPlaceholder(NSAttributedString nsAttributedString) =>
Control.AttributedPlaceholder = nsAttributedString;

[PortHandler]
void UpdateText()
{
var text = Element.UpdateFormsText(Element.Text, Element.TextTransform);
Expand Down
7 changes: 6 additions & 1 deletion src/Controls/samples/Controls.Sample/Pages/MainPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@ void SetupMauiLayout()

verticalStack.Add(horizontalStack);

verticalStack.Add(new Entry());
var entry = new Entry();
entry.TextChanged += (sender, e) =>
{
System.Console.WriteLine($"Text Changed from '{e.OldTextValue}' to '{e.NewTextValue}'");
};
verticalStack.Add(entry);
verticalStack.Add(new Entry { Text = "Entry", TextColor = Color.DarkRed });
verticalStack.Add(new Entry { IsPassword = true, TextColor = Color.Black });
verticalStack.Add(new Entry { IsTextPredictionEnabled = false });
Expand Down
2 changes: 1 addition & 1 deletion src/Core/src/Core/IEntry.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace Microsoft.Maui
{
public interface IEntry : IView, IText
public interface IEntry : IView, IText, ITextInput
{
bool IsPassword { get; }
bool IsTextPredictionEnabled { get; }
Expand Down
9 changes: 1 addition & 8 deletions src/Core/src/Core/ITextInput.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
using Microsoft.Maui;

namespace Microsoft.Maui
{
public interface ITextInput : IText
{
Keyboard Keyboard { get; }
bool IsSpellCheckEnabled { get; }
int MaxLength { get; }
string Placeholder { get; }
Color PlaceholderColor { get; }
bool IsReadOnly { get; }
new string Text { get; set; }
}
}
46 changes: 46 additions & 0 deletions src/Core/src/Handlers/Entry/EntryHandler.Android.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,32 @@
using Android.Content.Res;
using Android.Text;
using Android.Widget;

namespace Microsoft.Maui.Handlers
{
public partial class EntryHandler : AbstractViewHandler<IEntry, EditText>
{
TextWatcher Watcher { get; } = new TextWatcher();

static ColorStateList? DefaultTextColors { get; set; }

protected override EditText CreateNativeView()
{
return new EditText(Context);
}

protected override void ConnectHandler(EditText nativeView)
{
Watcher.Handler = this;
nativeView.AddTextChangedListener(Watcher);
}

protected override void DisconnectHandler(EditText nativeView)
{
nativeView.RemoveTextChangedListener(Watcher);
Watcher.Handler = null;
}

protected override void SetupDefaults(EditText nativeView)
{
base.SetupDefaults(nativeView);
Expand All @@ -37,5 +52,36 @@ public static void MapIsTextPredictionEnabled(EntryHandler handler, IEntry entry
{
handler.TypedNativeView?.UpdateIsTextPredictionEnabled(entry);
}

void OnTextChanged(string? text)
{
if (VirtualView == null || TypedNativeView == 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;
}

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)
{
Handler?.OnTextChanged(s?.ToString());
}
}
}
}
44 changes: 39 additions & 5 deletions src/Core/src/Handlers/Entry/EntryHandler.iOS.cs
Original file line number Diff line number Diff line change
@@ -1,24 +1,39 @@
using CoreGraphics;
using System;
using Microsoft.Maui.Platform.iOS;
using UIKit;

namespace Microsoft.Maui.Handlers
{
public partial class EntryHandler : AbstractViewHandler<IEntry, UITextField>
public partial class EntryHandler : AbstractViewHandler<IEntry, MauiTextField>
{
static readonly int BaseHeight = 30;

static UIColor? DefaultTextColor;

protected override UITextField CreateNativeView()
protected override MauiTextField CreateNativeView()
{
return new UITextField(CGRect.Empty)
return new MauiTextField
{
BorderStyle = UITextBorderStyle.RoundedRect,
ClipsToBounds = true
};
}

protected override void SetupDefaults(UITextField nativeView)
protected override void ConnectHandler(MauiTextField nativeView)
{
nativeView.EditingChanged += OnEditingChanged;
nativeView.EditingDidEnd += OnEditingEnded;
nativeView.TextPropertySet += OnTextPropertySet;
}

protected override void DisconnectHandler(MauiTextField nativeView)
{
nativeView.EditingChanged -= OnEditingChanged;
nativeView.EditingDidEnd -= OnEditingEnded;
nativeView.TextPropertySet -= OnTextPropertySet;
}

protected override void SetupDefaults(MauiTextField nativeView)
{
DefaultTextColor = nativeView.TextColor;
}
Expand All @@ -45,5 +60,24 @@ public static void MapIsTextPredictionEnabled(EntryHandler handler, IEntry entry
{
handler.TypedNativeView?.UpdateIsTextPredictionEnabled(entry);
}

void OnEditingChanged(object? sender, EventArgs e) => OnTextChanged();

void OnEditingEnded(object? sender, EventArgs e) => OnTextChanged();

void OnTextPropertySet(object? sender, EventArgs e) => OnTextChanged();

void OnTextChanged()
{
if (VirtualView == null || TypedNativeView == 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 = TypedNativeView.Text ?? string.Empty;
if (mauiText != nativeText)
VirtualView.Text = nativeText;
}
}
}
6 changes: 5 additions & 1 deletion src/Core/src/Platform/Android/EntryExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ public static class EntryExtensions

public static void UpdateText(this EditText editText, IEntry entry)
{
editText.Text = entry.Text;
var newText = entry.Text ?? string.Empty;
var oldText = editText.Text ?? string.Empty;

if (oldText != newText)
editText.Text = newText;
}

public static void UpdateTextColor(this EditText editText, IEntry entry, ColorStateList? defaultColor)
Expand Down
30 changes: 30 additions & 0 deletions src/Core/src/Platform/iOS/MauiTextField.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System;
using CoreGraphics;
using UIKit;

namespace Microsoft.Maui.Platform.iOS
{
public class MauiTextField : UITextField
{
public MauiTextField(CGRect frame)
: base(frame)
{
}

public MauiTextField()
{
}

public override string? Text
{
get => base.Text;
set
{
base.Text = value;
TextPropertySet?.Invoke(this,EventArgs.Empty);
}
}

public event EventHandler? TextPropertySet;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ EditText GetNativeEntry(EntryHandler entryHandler) =>
string GetNativeText(EntryHandler entryHandler) =>
GetNativeEntry(entryHandler).Text;

void SetNativeText(EntryHandler entryHandler, string text) =>
GetNativeEntry(entryHandler).Text = text;

Color GetNativeTextColor(EntryHandler entryHandler)
{
int currentTextColorInt = GetNativeEntry(entryHandler).CurrentTextColor;
Expand Down
44 changes: 44 additions & 0 deletions src/Core/tests/DeviceTests/Handlers/Entry/EntryHandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,5 +135,49 @@ await ValidatePropertyUpdatesValue(
setValue,
unsetValue);
}

[Theory(DisplayName = "Text Changed Events Fire Correctly")]
// null/empty
[InlineData(null, null, false)]
[InlineData(null, "", false)]
[InlineData("", null, false)]
[InlineData("", "", false)]
// whitespace
[InlineData(null, " ", true)]
[InlineData("", " ", true)]
[InlineData(" ", null, true)]
[InlineData(" ", "", true)]
[InlineData(" ", " ", false)]
// text
[InlineData(null, "Hello", true)]
[InlineData("", "Hello", true)]
[InlineData(" ", "Hello", true)]
[InlineData("Hello", null, true)]
[InlineData("Hello", "", true)]
[InlineData("Hello", " ", true)]
[InlineData("Hello", "Goodbye", true)]
public async Task TextChangeEventsFireCorrectly(string initialText, string newText, bool eventExpected)
{
var entry = new EntryStub
{
Text = initialText,
};

var eventFiredCount = 0;
entry.TextChanged += (sender, e) =>
{
eventFiredCount++;

Assert.Equal(initialText, e.OldValue);
Assert.Equal(newText ?? string.Empty, e.NewValue);
};

await SetValueAsync(entry, newText, SetNativeText);

if (eventExpected)
Assert.Equal(1, eventFiredCount);
else
Assert.Equal(0, eventFiredCount);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ UITextField GetNativeEntry(EntryHandler entryHandler) =>
string GetNativeText(EntryHandler entryHandler) =>
GetNativeEntry(entryHandler).Text;

void SetNativeText(EntryHandler entryHandler, string text) =>
GetNativeEntry(entryHandler).Text = text;

Color GetNativeTextColor(EntryHandler entryHandler) =>
GetNativeEntry(entryHandler).TextColor.ToColor();

Expand Down
9 changes: 9 additions & 0 deletions src/Core/tests/DeviceTests/Handlers/HandlerTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@ protected Task<TValue> GetValueAsync<TValue>(IView view, Func<THandler, TValue>
});
}

protected Task SetValueAsync<TValue>(IView view, TValue value, Action<THandler, TValue> func)
{
return InvokeOnMainThreadAsync(() =>
{
var handler = CreateHandler(view);
func(handler, value);
});
}

async protected Task ValidatePropertyInitValue<TValue>(
IView view,
Func<TValue> GetValue,
Expand Down
15 changes: 13 additions & 2 deletions src/Core/tests/DeviceTests/Stubs/EntryStub.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
namespace Microsoft.Maui.DeviceTests.Stubs
using System;

namespace Microsoft.Maui.DeviceTests.Stubs
{
public partial class EntryStub : StubBase, IEntry
{
private string _text;

public string Text { get => _text; set => this.SetProperty(ref _text, value); }
public string Text
{
get => _text;
set => SetProperty(ref _text, value, onChanged: OnTextChanged);
}

public Color TextColor { get; set; }

public bool IsPassword { get; set; }

public bool IsTextPredictionEnabled { get; set; }

public event EventHandler<StubPropertyChangedEventArgs<string>> TextChanged;

void OnTextChanged(string oldValue, string newValue) =>
TextChanged?.Invoke(this, new StubPropertyChangedEventArgs<string>(oldValue, newValue));
}
}
5 changes: 3 additions & 2 deletions src/Core/tests/DeviceTests/Stubs/StubBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,15 @@ public void Arrange(Rectangle bounds)

protected bool SetProperty<T>(ref T backingStore, T value,
[CallerMemberName] string propertyName = "",
Action onChanged = null)
Action<T, T> onChanged = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;

var oldValue = backingStore;
backingStore = value;
onChanged?.Invoke();
Handler?.UpdateValue(propertyName);
onChanged?.Invoke(oldValue, value);
return true;
}

Expand Down
Loading