Skip to content

Commit

Permalink
Implement Keyboard property in EditorHandlers (#686)
Browse files Browse the repository at this point in the history
* Implement Keyboard property in EditorHandlers

* Cleanup EditorHandler.Windows.cs

* Add missing bracket

* Fixed failing Editor handler tests

Co-authored-by: Rachel Kang <[email protected]>
  • Loading branch information
jsuarezruiz and rachelkang authored Jun 1, 2021
1 parent f3b3745 commit a6d35a1
Show file tree
Hide file tree
Showing 10 changed files with 265 additions and 7 deletions.
5 changes: 5 additions & 0 deletions src/Core/src/Handlers/Editor/EditorHandler.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ public static void MapFont(EditorHandler handler, IEditor editor)
handler.NativeView?.UpdateFont(editor, fontManager);
}

public static void MapKeyboard(EditorHandler handler, IEditor editor)
{
handler.NativeView?.UpdateKeyboard(editor);
}

void OnFocusedChange(bool hasFocus)
{
if (!hasFocus)
Expand Down
1 change: 1 addition & 0 deletions src/Core/src/Handlers/Editor/EditorHandler.Standard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ public static void MapIsTextPredictionEnabled(EditorHandler handler, IEditor edi
public static void MapFont(IViewHandler handler, IEditor editor) { }
public static void MapIsReadOnly(IViewHandler handler, IEditor editor) { }
public static void MapTextColor(EditorHandler handler, IEditor editor) { }
public static void MapKeyboard(EditorHandler handler, IEditor editor) { }
}
}
3 changes: 3 additions & 0 deletions src/Core/src/Handlers/Editor/EditorHandler.Windows.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ public static void MapIsReadOnly(EditorHandler handler, IEditor editor)

public static void MapTextColor(EditorHandler handler, IEditor editor) =>
handler.NativeView?.UpdateTextColor(editor);

[MissingMapper]
public static void MapKeyboard(EditorHandler handler, IEditor editor) { }

void OnLostFocus(object? sender, RoutedEventArgs e)
{
Expand Down
1 change: 1 addition & 0 deletions src/Core/src/Handlers/Editor/EditorHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public partial class EditorHandler
[nameof(IEditor.PlaceholderColor)] = MapPlaceholderColor,
[nameof(IEditor.Text)] = MapText,
[nameof(IEditor.TextColor)] = MapTextColor,
[nameof(IEditor.Keyboard)] = MapKeyboard
};

public EditorHandler() : base(EditorMapper)
Expand Down
9 changes: 7 additions & 2 deletions src/Core/src/Handlers/Editor/EditorHandler.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public static void MapIsReadOnly(EditorHandler handler, IEditor editor)

public static void MapIsTextPredictionEnabled(EditorHandler handler, IEditor editor)
{
handler.NativeView?.UpdatePredictiveText(editor);
handler.NativeView?.UpdateIsTextPredictionEnabled(editor);
}

public static void MapFormatting(EditorHandler handler, IEditor editor)
Expand All @@ -97,7 +97,7 @@ public static void MapFont(EditorHandler handler, IEditor editor)
handler.NativeView?.UpdateFont(editor, fontManager);
}

void OnChanged(object? sender, System.EventArgs e) => OnTextChanged();
void OnChanged(object? sender, EventArgs e) => OnTextChanged();

void OnTextChanged()
{
Expand Down Expand Up @@ -137,5 +137,10 @@ void OnEnded(object? sender, EventArgs eventArgs)
// TODO: Update IsFocused property
VirtualView.Completed();
}

public static void MapKeyboard(EditorHandler handler, IEditor editor)
{
handler.NativeView?.UpdateKeyboard(editor);
}
}
}
36 changes: 36 additions & 0 deletions src/Core/src/Platform/Android/EditTextExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,11 @@ public static void UpdateKeyboard(this AppCompatEditText editText, IEntry entry)
editText.SetInputType(entry);
}

public static void UpdateKeyboard(this AppCompatEditText editText, IEditor editor)
{
editText.SetInputType(editor);
}

public static void UpdateIsReadOnly(this AppCompatEditText editText, IEditor editor)
{
bool isReadOnly = !editor.IsReadOnly;
Expand Down Expand Up @@ -200,6 +205,37 @@ public static void UpdateReturnType(this AppCompatEditText editText, IEntry entr
editText.ImeOptions = entry.ReturnType.ToNative();
}

internal static void SetInputType(this AppCompatEditText editText, IEditor editor)
{
if (editor.IsReadOnly)
{
editText.InputType = InputTypes.Null;
}
else
{
var keyboard = editor.Keyboard;
var nativeInputTypeToUpdate = keyboard.ToInputType();

if (keyboard is not CustomKeyboard)
{
// TODO: IsSpellCheckEnabled handling must be here.

if ((nativeInputTypeToUpdate & InputTypes.TextFlagNoSuggestions) != InputTypes.TextFlagNoSuggestions)
{
if (!editor.IsTextPredictionEnabled)
nativeInputTypeToUpdate |= InputTypes.TextFlagNoSuggestions;
}
}

if (keyboard == Keyboard.Numeric)
{
editText.KeyListener = LocalizedDigitsKeyListener.Create(editText.InputType);
}

editText.InputType = nativeInputTypeToUpdate;
}
}

internal static void SetInputType(this AppCompatEditText editText, IEntry entry)
{
if (entry.IsReadOnly)
Expand Down
20 changes: 17 additions & 3 deletions src/Core/src/Platform/iOS/TextViewExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,12 @@ public static void UpdateMaxLength(this UITextView textView, IEditor editor)
textView.AttributedText = newText;
}

public static void UpdatePredictiveText(this UITextView textView, IEditor editor)
public static void UpdateIsTextPredictionEnabled(this UITextView textView, IEditor editor)
{
textView.AutocorrectionType = editor.IsTextPredictionEnabled
? UITextAutocorrectionType.Yes : UITextAutocorrectionType.No;
if (editor.IsTextPredictionEnabled)
textView.AutocorrectionType = UITextAutocorrectionType.Yes;
else
textView.AutocorrectionType = UITextAutocorrectionType.No;
}

public static void UpdateFont(this UITextView textView, ITextStyle textStyle, IFontManager fontManager)
Expand All @@ -56,5 +58,17 @@ public static void UpdateIsReadOnly(this UITextView textView, IEditor editor)
{
textView.UserInteractionEnabled = !editor.IsReadOnly;
}

public static void UpdateKeyboard(this UITextView textView, IEditor editor)
{
var keyboard = editor.Keyboard;

textView.ApplyKeyboard(keyboard);

if (keyboard is not CustomKeyboard)
textView.UpdateIsTextPredictionEnabled(editor);

textView.ReloadInputViews();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Threading.Tasks;
using Android.Text;
using Android.Text.Method;
using AndroidX.AppCompat.Widget;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Maui.DeviceTests.Stubs;
Expand Down Expand Up @@ -108,5 +109,54 @@ Color GetNativeTextColor(EditorHandler editorHandler)
AColor currentTextColor = new AColor(currentTextColorInt);
return currentTextColor.ToColor();
}

bool GetNativeIsNumericKeyboard(EditorHandler editorHandler)
{
var textView = GetNativeEditor(editorHandler);
var inputTypes = textView.InputType;

return textView.KeyListener is NumberKeyListener
&& (inputTypes.HasFlag(InputTypes.NumberFlagDecimal) && inputTypes.HasFlag(InputTypes.ClassNumber) && inputTypes.HasFlag(InputTypes.NumberFlagSigned));
}

bool GetNativeIsChatKeyboard(EditorHandler editorHandler)
{
var textView = GetNativeEditor(editorHandler);
var inputTypes = textView.InputType;

return inputTypes.HasFlag(InputTypes.ClassText) && inputTypes.HasFlag(InputTypes.TextFlagCapSentences) && inputTypes.HasFlag(InputTypes.TextFlagNoSuggestions);
}

bool GetNativeIsEmailKeyboard(EditorHandler editorHandler)
{
var textView = GetNativeEditor(editorHandler);
var inputTypes = textView.InputType;

return (inputTypes.HasFlag(InputTypes.ClassText) && inputTypes.HasFlag(InputTypes.TextVariationEmailAddress));
}

bool GetNativeIsTelephoneKeyboard(EditorHandler editorHandler)
{
var textView = GetNativeEditor(editorHandler);
var inputTypes = textView.InputType;

return inputTypes.HasFlag(InputTypes.ClassPhone);
}

bool GetNativeIsUrlKeyboard(EditorHandler editorHandler)
{
var textView = GetNativeEditor(editorHandler);
var inputTypes = textView.InputType;

return inputTypes.HasFlag(InputTypes.ClassText) && inputTypes.HasFlag(InputTypes.TextVariationUri);
}

bool GetNativeIsTextKeyboard(EditorHandler editorHandler)
{
var textView = GetNativeEditor(editorHandler);
var inputTypes = textView.InputType;

return inputTypes.HasFlag(InputTypes.ClassText) && inputTypes.HasFlag(InputTypes.TextFlagCapSentences) && !inputTypes.HasFlag(InputTypes.TextFlagNoSuggestions);
}
}
}
115 changes: 114 additions & 1 deletion src/Core/tests/DeviceTests/Handlers/Editor/EditorHandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,12 @@ public async Task IsTextPredictionEnabledCorrectly(bool isEnabled)
IsTextPredictionEnabled = isEnabled
};

await ValidatePropertyInitValue(editor, () => editor.IsTextPredictionEnabled, GetNativeIsTextPredictionEnabled, isEnabled);
var nativeIsTextPredictionEnabled = await GetValueAsync(editor, handler =>
{
return GetNativeIsTextPredictionEnabled(handler);
});

Assert.Equal(isEnabled, nativeIsTextPredictionEnabled);
}

[Theory(DisplayName = "IsTextPredictionEnabled Updates Correctly")]
Expand Down Expand Up @@ -231,5 +236,113 @@ public async Task FontSizeInitializesCorrectly(int fontSize)

await ValidatePropertyInitValue(editor, () => editor.Font.FontSize, GetNativeUnscaledFontSize, editor.Font.FontSize);
}

[Theory(DisplayName = "Validates Numeric Keyboard")]
[InlineData(nameof(Keyboard.Chat), false)]
[InlineData(nameof(Keyboard.Default), false)]
[InlineData(nameof(Keyboard.Email), false)]
[InlineData(nameof(Keyboard.Numeric), true)]
[InlineData(nameof(Keyboard.Plain), false)]
[InlineData(nameof(Keyboard.Telephone), false)]
[InlineData(nameof(Keyboard.Text), false)]
[InlineData(nameof(Keyboard.Url), false)]
public async Task ValidateNumericKeyboard(string keyboardName, bool expected)
{
var keyboard = (Keyboard)typeof(Keyboard).GetProperty(keyboardName).GetValue(null);

var editor = new EditorStub() { Keyboard = keyboard };

await ValidatePropertyInitValue(editor, () => expected, GetNativeIsNumericKeyboard, expected);
}

[Theory(DisplayName = "Validates Email Keyboard")]
[InlineData(nameof(Keyboard.Chat), false)]
[InlineData(nameof(Keyboard.Default), false)]
[InlineData(nameof(Keyboard.Email), true)]
[InlineData(nameof(Keyboard.Numeric), false)]
[InlineData(nameof(Keyboard.Plain), false)]
[InlineData(nameof(Keyboard.Telephone), false)]
[InlineData(nameof(Keyboard.Text), false)]
[InlineData(nameof(Keyboard.Url), false)]
public async Task ValidateEmailKeyboard(string keyboardName, bool expected)
{
var keyboard = (Keyboard)typeof(Keyboard).GetProperty(keyboardName).GetValue(null);

var editor = new EditorStub() { Keyboard = keyboard };

await ValidatePropertyInitValue(editor, () => expected, GetNativeIsEmailKeyboard, expected);
}

[Theory(DisplayName = "Validates Telephone Keyboard")]
[InlineData(nameof(Keyboard.Chat), false)]
[InlineData(nameof(Keyboard.Default), false)]
[InlineData(nameof(Keyboard.Email), false)]
[InlineData(nameof(Keyboard.Numeric), false)]
[InlineData(nameof(Keyboard.Plain), false)]
[InlineData(nameof(Keyboard.Telephone), true)]
[InlineData(nameof(Keyboard.Text), false)]
[InlineData(nameof(Keyboard.Url), false)]
public async Task ValidateTelephoneKeyboard(string keyboardName, bool expected)
{
var keyboard = (Keyboard)typeof(Keyboard).GetProperty(keyboardName).GetValue(null);

var editor = new EditorStub() { Keyboard = keyboard };

await ValidatePropertyInitValue(editor, () => expected, GetNativeIsTelephoneKeyboard, expected);
}

[Theory(DisplayName = "Validates Url Keyboard")]
[InlineData(nameof(Keyboard.Chat), false)]
[InlineData(nameof(Keyboard.Default), false)]
[InlineData(nameof(Keyboard.Email), false)]
[InlineData(nameof(Keyboard.Numeric), false)]
[InlineData(nameof(Keyboard.Plain), false)]
[InlineData(nameof(Keyboard.Telephone), false)]
[InlineData(nameof(Keyboard.Text), false)]
[InlineData(nameof(Keyboard.Url), true)]
public async Task ValidateUrlKeyboard(string keyboardName, bool expected)
{
var keyboard = (Keyboard)typeof(Keyboard).GetProperty(keyboardName).GetValue(null);

var editor = new EditorStub() { Keyboard = keyboard };

await ValidatePropertyInitValue(editor, () => expected, GetNativeIsUrlKeyboard, expected);
}

[Theory(DisplayName = "Validates Text Keyboard")]
[InlineData(nameof(Keyboard.Chat), false)]
[InlineData(nameof(Keyboard.Default), false)]
[InlineData(nameof(Keyboard.Email), false)]
[InlineData(nameof(Keyboard.Numeric), false)]
[InlineData(nameof(Keyboard.Plain), false)]
[InlineData(nameof(Keyboard.Telephone), false)]
[InlineData(nameof(Keyboard.Text), true)]
[InlineData(nameof(Keyboard.Url), false)]
public async Task ValidateTextKeyboard(string keyboardName, bool expected)
{
var keyboard = (Keyboard)typeof(Keyboard).GetProperty(keyboardName).GetValue(null);

var editor = new EditorStub() { Keyboard = keyboard };

await ValidatePropertyInitValue(editor, () => expected, GetNativeIsTextKeyboard, expected);
}

[Theory(DisplayName = "Validates Chat Keyboard")]
[InlineData(nameof(Keyboard.Chat), true)]
[InlineData(nameof(Keyboard.Default), false)]
[InlineData(nameof(Keyboard.Email), false)]
[InlineData(nameof(Keyboard.Numeric), false)]
[InlineData(nameof(Keyboard.Plain), false)]
[InlineData(nameof(Keyboard.Telephone), false)]
[InlineData(nameof(Keyboard.Text), false)]
[InlineData(nameof(Keyboard.Url), false)]
public async Task ValidateChatKeyboard(string keyboardName, bool expected)
{
var keyboard = (Keyboard)typeof(Keyboard).GetProperty(keyboardName).GetValue(null);

var editor = new EditorStub() { Keyboard = keyboard };

await ValidatePropertyInitValue(editor, () => expected, GetNativeIsChatKeyboard, expected);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public async Task FontFamilyInitializesCorrectly(string family)
}

MauiTextView GetNativeEditor(EditorHandler editorHandler) =>
(MauiTextView)editorHandler.NativeView;
editorHandler.NativeView;

string GetNativeText(EditorHandler editorHandler) =>
GetNativeEditor(editorHandler).Text;
Expand Down Expand Up @@ -91,5 +91,35 @@ bool GetNativeIsTextPredictionEnabled(EditorHandler editorHandler) =>

Color GetNativeTextColor(EditorHandler editorHandler) =>
GetNativeEditor(editorHandler).TextColor.ToColor();

bool GetNativeIsNumericKeyboard(EditorHandler editorHandler) =>
GetNativeEditor(editorHandler).KeyboardType == UIKeyboardType.DecimalPad;

bool GetNativeIsEmailKeyboard(EditorHandler editorHandler) =>
GetNativeEditor(editorHandler).KeyboardType == UIKeyboardType.EmailAddress;

bool GetNativeIsTelephoneKeyboard(EditorHandler editorHandler) =>
GetNativeEditor(editorHandler).KeyboardType == UIKeyboardType.PhonePad;

bool GetNativeIsUrlKeyboard(EditorHandler editorHandler) =>
GetNativeEditor(editorHandler).KeyboardType == UIKeyboardType.Url;

bool GetNativeIsTextKeyboard(EditorHandler editorHandler)
{
var nativeEditor = GetNativeEditor(editorHandler);

return nativeEditor.AutocapitalizationType == UITextAutocapitalizationType.Sentences &&
nativeEditor.AutocorrectionType == UITextAutocorrectionType.Yes &&
nativeEditor.SpellCheckingType == UITextSpellCheckingType.Yes;
}

bool GetNativeIsChatKeyboard(EditorHandler editorHandler)
{
var nativeEditor = GetNativeEditor(editorHandler);

return nativeEditor.AutocapitalizationType == UITextAutocapitalizationType.Sentences &&
nativeEditor.AutocorrectionType == UITextAutocorrectionType.Yes &&
nativeEditor.SpellCheckingType == UITextSpellCheckingType.No;
}
}
}

0 comments on commit a6d35a1

Please sign in to comment.