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

Don't set editor action to handled #11386

Merged
merged 3 commits into from
Nov 17, 2022
Merged
Show file tree
Hide file tree
Changes from 2 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
2 changes: 1 addition & 1 deletion src/Core/src/Handlers/Entry/EntryHandler.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ void OnEditorAction(object? sender, EditorActionEventArgs e)
}
}

e.Handled = true;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only SDK change

e.Handled = false;
}

private void OnSelectionChanged(object? sender, EventArgs e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,38 @@
using Android.App;
using Android.Content;
using Android.OS;
using Android.Views;
using Android.Views.InputMethods;
using Android.Widget;
using AndroidX.Core.View;
using AView = Android.Views.View;

namespace Microsoft.Maui.Controls.Platform
namespace Microsoft.Maui.Platform
{
internal static class KeyboardManager
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this to Core so that we can use it from AssertionExtensions

{
internal static void HideKeyboard(this AView inputView, bool overrideValidation = false)
{
if (inputView == null)
if (inputView?.Context == null)
throw new ArgumentNullException(nameof(inputView) + " must be set before the keyboard can be hidden.");

using (var inputMethodManager = (InputMethodManager)inputView.Context.GetSystemService(Context.InputMethodService))
using (var inputMethodManager = (InputMethodManager)inputView.Context.GetSystemService(Context.InputMethodService)!)
{
if (!overrideValidation && !(inputView is EditText || inputView is TextView || inputView is SearchView))
throw new ArgumentException("inputView should be of type EditText, SearchView, or TextView");

IBinder windowToken = inputView.WindowToken;
var windowToken = inputView.WindowToken;
if (windowToken != null && inputMethodManager != null)
inputMethodManager.HideSoftInputFromWindow(windowToken, HideSoftInputFlags.None);
}
}

internal static void ShowKeyboard(this TextView inputView)
{
if (inputView == null)
if (inputView?.Context == null)
throw new ArgumentNullException(nameof(inputView) + " must be set before the keyboard can be shown.");

using (var inputMethodManager = (InputMethodManager)inputView.Context.GetSystemService(Context.InputMethodService))
using (var inputMethodManager = (InputMethodManager)inputView.Context.GetSystemService(Context.InputMethodService)!)
{
// The zero value for the second parameter comes from
// https://developer.android.com/reference/android/view/inputmethod/InputMethodManager#showSoftInput(android.view.View,%20int)
Expand All @@ -42,7 +44,7 @@ internal static void ShowKeyboard(this TextView inputView)

internal static void ShowKeyboard(this SearchView searchView)
{
if (searchView == null)
if (searchView?.Context == null || searchView?.Resources == null)
{
throw new ArgumentNullException(nameof(searchView));
}
Expand All @@ -64,7 +66,7 @@ internal static void ShowKeyboard(this SearchView searchView)
return;
}

using (var inputMethodManager = (InputMethodManager)searchView.Context.GetSystemService(Context.InputMethodService))
using (var inputMethodManager = (InputMethodManager)searchView.Context.GetSystemService(Context.InputMethodService)!)
{
// The zero value for the second parameter comes from
// https://developer.android.com/reference/android/view/inputmethod/InputMethodManager#showSoftInput(android.view.View,%20int)
Expand Down Expand Up @@ -102,5 +104,15 @@ void ShowKeyboard()

view.Post(ShowKeyboard);
}

public static bool IsSoftKeyboardVisible(this AView view)
{
var insets = ViewCompat.GetRootWindowInsets(view);
if (insets == null)
return false;

var result = insets.IsVisible(WindowInsetsCompat.Type.Ime());
return result;
}
}
}
1 change: 1 addition & 0 deletions src/Core/src/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,5 @@
[assembly: InternalsVisibleTo("CommunityToolkit.Maui.Markup.UnitTests")]
[assembly: InternalsVisibleTo("Reloadify-emit")]
[assembly: InternalsVisibleTo("Microsoft.Maui.TestUtils.DeviceTests.Runners")]
[assembly: InternalsVisibleTo("Microsoft.Maui.TestUtils.DeviceTests")]
[assembly: InternalsVisibleTo("Microsoft.Maui.DeviceTests.Shared")]
90 changes: 90 additions & 0 deletions src/Core/tests/DeviceTests/Handlers/Entry/EntryHandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Microsoft.Maui.DeviceTests.Stubs;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Hosting;
using Xunit;

namespace Microsoft.Maui.DeviceTests
Expand Down Expand Up @@ -494,6 +495,95 @@ await ValidateUnrelatedPropertyUnaffected(
() => entry.CharacterSpacing = newSize);
}

#if ANDROID
[Fact]
public async Task NextMovesToNextEntrySuccessfully()
{
EnsureHandlerCreated(builder =>
{
builder.ConfigureMauiHandlers(handler =>
{
handler.AddHandler<VerticalStackLayoutStub, LayoutHandler>();
handler.AddHandler<EntryStub, EntryHandler>();
});
});

var layout = new VerticalStackLayoutStub();

var entry1 = new EntryStub
{
Text = "Entry 1",
ReturnType = ReturnType.Next
};

var entry2 = new EntryStub
{
Text = "Entry 2",
ReturnType = ReturnType.Next
};

layout.Add(entry1);
layout.Add(entry2);

layout.Width = 100;
layout.Height = 150;

await InvokeOnMainThreadAsync(async () =>
{
var contentViewHandler = CreateHandler<LayoutHandler>(layout);
await contentViewHandler.PlatformView.AttachAndRun(async () =>
{
await entry1.SendKeyboardReturnType(ReturnType.Next);
await entry2.WaitForFocused();
Assert.True(entry2.IsFocused);
});
});
}

[Fact]
public async Task DoneClosesKeyboard()
{
EnsureHandlerCreated(builder =>
{
builder.ConfigureMauiHandlers(handler =>
{
handler.AddHandler<VerticalStackLayoutStub, LayoutHandler>();
handler.AddHandler<EntryStub, EntryHandler>();
});
});

var layout = new VerticalStackLayoutStub();

var entry1 = new EntryStub
{
Text = "Entry 1",
ReturnType = ReturnType.Done
};

var entry2 = new EntryStub
{
Text = "Entry 2",
ReturnType = ReturnType.Done
};

layout.Add(entry1);
layout.Add(entry2);

layout.Width = 100;
layout.Height = 150;

await InvokeOnMainThreadAsync(async () =>
{
var handler = CreateHandler<LayoutHandler>(layout);
await handler.PlatformView.AttachAndRun(async () =>
{
await entry1.SendKeyboardReturnType(ReturnType.Done);
await entry1.WaitForKeyboardToHide();
});
});
}
#endif

[Category(TestCategory.Entry)]
public class EntryTextStyleTests : TextStyleHandlerTests<EntryHandler, EntryStub>
{
Expand Down
4 changes: 3 additions & 1 deletion src/Core/tests/DeviceTests/Stubs/LayoutStub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ public Size CrossPlatformArrange(Rect bounds)
public int Count => _children.Count;
public bool IsReadOnly => _children.IsReadOnly;

ILayoutManager LayoutManager => _layoutManager ??= new LayoutManagerStub();
ILayoutManager LayoutManager => _layoutManager ??= CreateLayoutManager();

protected virtual ILayoutManager CreateLayoutManager() => new LayoutManagerStub();

public bool IgnoreSafeArea => false;

Expand Down
14 changes: 14 additions & 0 deletions src/Core/tests/DeviceTests/Stubs/StubBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ public Size Arrange(Rect bounds)
{
Frame = bounds;
DesiredSize = bounds.Size;

// If this view is attached to the visual tree then let's arrange it
if (IsLoaded)
Handler?.PlatformArrange(Frame);

return DesiredSize;
}

Expand Down Expand Up @@ -139,5 +144,14 @@ public Size Measure(double widthConstraint, double heightConstraint)
IReadOnlyList<Maui.IVisualTreeElement> IVisualTreeElement.GetVisualChildren() => this.Children.Cast<IVisualTreeElement>().ToList().AsReadOnly();

IVisualTreeElement IVisualTreeElement.GetVisualParent() => this.Parent as IVisualTreeElement;


public bool IsLoaded
{
get
{
return (Handler as IPlatformViewHandler)?.PlatformView?.IsLoaded() == true;
}
}
}
}
19 changes: 19 additions & 0 deletions src/Core/tests/DeviceTests/Stubs/VerticalStackLayoutStub.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Maui.Layouts;

namespace Microsoft.Maui.DeviceTests.Stubs
{
public class VerticalStackLayoutStub : LayoutStub, IStackLayout
{
public double Spacing => 0;

protected override ILayoutManager CreateLayoutManager()
{
return new VerticalStackLayoutManager(this);
}
}
}
81 changes: 80 additions & 1 deletion src/TestUtils/src/DeviceTests/AssertionExtensions.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Android.Graphics.Drawables;
using Android.Text;
using Android.Views;
using Android.Views.InputMethods;
using Android.Widget;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Platform;
Expand All @@ -17,12 +18,89 @@ namespace Microsoft.Maui.DeviceTests
{
public static partial class AssertionExtensions
{
public static async Task SendValueToKeyboard(this AView view, char value, int timeout = 1000)
{
await view.ShowKeyboardForView(timeout);

// I tried various permutations of KeyEventActions to set the keyboard in upper case
// But I wasn't successful
if (Enum.TryParse($"{value}".ToUpper(), out Keycode result))
{
view.OnCreateInputConnection(new EditorInfo())?
.SendKeyEvent(new KeyEvent(10, 10, KeyEventActions.Down, result, 0));

view.OnCreateInputConnection(new EditorInfo())?
.SendKeyEvent(new KeyEvent(10, 10, KeyEventActions.Up, result, 0));
}
}

public static async Task SendKeyboardReturnType(this AView view, ReturnType returnType, int timeout = 1000)
{
await view.ShowKeyboardForView(timeout);

view
.OnCreateInputConnection(new EditorInfo())?
.PerformEditorAction(returnType.ToPlatform());

// Let the action propagate
await Task.Delay(10);
}

public static async Task WaitForFocused(this AView view, int timeout = 1000)
{
if (!view.IsFocused)
{
TaskCompletionSource focusSource = new TaskCompletionSource();
view.FocusChange += OnFocused;
await focusSource.Task.WaitAsync(TimeSpan.FromMilliseconds(timeout));

// Even thuogh the event fires focus hasn't fully been achieved
await Task.Delay(10);

void OnFocused(object? sender, AView.FocusChangeEventArgs e)
{
view.FocusChange -= OnFocused;
focusSource.SetResult();
}
}
}

public static Task FocusView(this AView view, int timeout = 1000)
{
if (!view.IsFocused)
{
view.Focus(new FocusRequest(view.IsFocused));
return view.WaitForFocused(timeout);
}

return Task.CompletedTask;
}

public static async Task ShowKeyboardForView(this AView view, int timeout = 1000)
{
await view.FocusView(timeout);
KeyboardManager.ShowKeyboard(view);
await view.WaitForKeyboardToShow(timeout);
}

public static async Task WaitForKeyboardToShow(this AView view, int timeout = 1000)
{
var result = await Wait(() => KeyboardManager.IsSoftKeyboardVisible(view), timeout);
Assert.True(result);

}

public static async Task WaitForKeyboardToHide(this AView view, int timeout = 1000)
{
var result = await Wait(() => !KeyboardManager.IsSoftKeyboardVisible(view), timeout);
Assert.True(result);
}

public static Task<bool> WaitForLayout(AView view, int timeout = 1000)
{
var tcs = new TaskCompletionSource<bool>();

view.LayoutChange += OnLayout;

var cts = new CancellationTokenSource();
cts.Token.Register(() => OnLayout(view), true);
cts.CancelAfter(timeout);
Expand All @@ -36,6 +114,7 @@ void OnLayout(object? sender = null, AView.LayoutChangeEventArgs? e = null)
if (view.Handle != IntPtr.Zero)
view.LayoutChange -= OnLayout;

// let the layout resolve after changing
tcs.TrySetResult(e != null);
}
}
Expand Down
Loading