diff --git a/src/Controls/src/Core/Entry/Entry.Android.cs b/src/Controls/src/Core/Entry/Entry.Android.cs index 7c670e609020..267969957b3b 100644 --- a/src/Controls/src/Core/Entry/Entry.Android.cs +++ b/src/Controls/src/Core/Entry/Entry.Android.cs @@ -35,7 +35,6 @@ public static void MapText(IEntryHandler handler, Entry entry) static void MapFocus(IViewHandler handler, IView view, object args) { handler.ShowKeyboardIfFocused(view); - EntryHandler.CommandMapper.Chained?.Invoke(handler, view, nameof(IView.Focus), args); } } } diff --git a/src/Controls/src/Core/Entry/Entry.Mapper.cs b/src/Controls/src/Core/Entry/Entry.Mapper.cs index 5f5328ff8fdb..6d6d2224257c 100644 --- a/src/Controls/src/Core/Entry/Entry.Mapper.cs +++ b/src/Controls/src/Core/Entry/Entry.Mapper.cs @@ -18,18 +18,14 @@ public partial class Entry [nameof(TextTransform)] = MapText, }; - static CommandMapper ControlsCommandMapper = new(EntryHandler.CommandMapper) - { -#if ANDROID - [nameof(IEntry.Focus)] = MapFocus -#endif - }; - internal static new void RemapForControls() { // Adjust the mappings to preserve Controls.Entry legacy behaviors EntryHandler.Mapper = ControlsEntryMapper; - EntryHandler.CommandMapper = ControlsCommandMapper; + +#if ANDROID + EntryHandler.CommandMapper.PrependToMapping(nameof(IEntry.Focus), MapFocus); +#endif } } } diff --git a/src/Controls/src/Core/HandlerImpl/Editor/Editor.Android.cs b/src/Controls/src/Core/HandlerImpl/Editor/Editor.Android.cs index aee3ae678a95..d67d92fd03d0 100644 --- a/src/Controls/src/Core/HandlerImpl/Editor/Editor.Android.cs +++ b/src/Controls/src/Core/HandlerImpl/Editor/Editor.Android.cs @@ -24,7 +24,6 @@ public static void MapText(IEditorHandler handler, Editor editor) static void MapFocus(IViewHandler handler, IView view, object args) { handler.ShowKeyboardIfFocused(view); - EditorHandler.CommandMapper.Chained?.Invoke(handler, view, nameof(IView.Focus), args); } } } diff --git a/src/Controls/src/Core/HandlerImpl/Editor/Editor.cs b/src/Controls/src/Core/HandlerImpl/Editor/Editor.cs index 221cc31be702..2eb66320065c 100644 --- a/src/Controls/src/Core/HandlerImpl/Editor/Editor.cs +++ b/src/Controls/src/Core/HandlerImpl/Editor/Editor.cs @@ -13,18 +13,14 @@ public partial class Editor [nameof(TextTransform)] = MapText, }; - static CommandMapper ControlsCommandMapper = new(EditorHandler.CommandMapper) - { -#if ANDROID - [nameof(IEditor.Focus)] = MapFocus -#endif - }; - internal static new void RemapForControls() { // Adjust the mappings to preserve Controls.Editor legacy behaviors EditorHandler.Mapper = ControlsEditorMapper; - EditorHandler.CommandMapper = ControlsCommandMapper; + +#if ANDROID + EditorHandler.CommandMapper.PrependToMapping(nameof(IEditor.Focus), MapFocus); +#endif } } } \ No newline at end of file diff --git a/src/Controls/src/Core/HandlerImpl/SearchBar/SearchBar.Android.cs b/src/Controls/src/Core/HandlerImpl/SearchBar/SearchBar.Android.cs index e9b47703f741..f83afc734b9b 100644 --- a/src/Controls/src/Core/HandlerImpl/SearchBar/SearchBar.Android.cs +++ b/src/Controls/src/Core/HandlerImpl/SearchBar/SearchBar.Android.cs @@ -16,7 +16,6 @@ public static void MapText(ISearchBarHandler handler, SearchBar searchBar) static void MapFocus(IViewHandler handler, IView view, object args) { handler.ShowKeyboardIfFocused(view); - SearchBarHandler.CommandMapper.Chained?.Invoke(handler, view, nameof(IView.Focus), args); } } } diff --git a/src/Controls/src/Core/HandlerImpl/SearchBar/SearchBar.cs b/src/Controls/src/Core/HandlerImpl/SearchBar/SearchBar.cs index 56d6ba28a0eb..d68c8e451a52 100644 --- a/src/Controls/src/Core/HandlerImpl/SearchBar/SearchBar.cs +++ b/src/Controls/src/Core/HandlerImpl/SearchBar/SearchBar.cs @@ -15,18 +15,14 @@ public partial class SearchBar [nameof(TextTransform)] = MapText, }; - static CommandMapper ControlsCommandMapper = new(SearchBarHandler.CommandMapper) - { -#if ANDROID - [nameof(ISearchBar.Focus)] = MapFocus -#endif - }; - internal static new void RemapForControls() { // Adjust the mappings to preserve Controls.SearchBar legacy behaviors SearchBarHandler.Mapper = ControlsSearchBarMapper; - SearchBarHandler.CommandMapper = ControlsCommandMapper; + +#if ANDROID + SearchBarHandler.CommandMapper.PrependToMapping(nameof(ISearchBar.Focus), MapFocus); +#endif } } } diff --git a/src/Controls/src/Core/HandlerImpl/VisualElement/VisualElement.cs b/src/Controls/src/Core/HandlerImpl/VisualElement/VisualElement.cs index b8533ca4c86c..fa98561e72d0 100644 --- a/src/Controls/src/Core/HandlerImpl/VisualElement/VisualElement.cs +++ b/src/Controls/src/Core/HandlerImpl/VisualElement/VisualElement.cs @@ -24,15 +24,11 @@ public partial class VisualElement [nameof(IViewHandler.ContainerView)] = MapContainerView, }; - static CommandMapper ControlsViewCommandMapper = new(ViewHandler.ViewCommandMapper) - { - [nameof(IView.Focus)] = MapFocus, - }; - internal static void RemapForControls() { ViewHandler.ViewMapper = ControlsVisualElementMapper; - ViewHandler.ViewCommandMapper = ControlsViewCommandMapper; + + ViewHandler.ViewCommandMapper.ModifyMapping(nameof(IView.Focus), MapFocus); } public static void MapBackgroundColor(IViewHandler handler, IView view) @@ -70,12 +66,12 @@ static void MapContainerView(IViewHandler arg1, IView arg2) } } - static void MapFocus(IViewHandler handler, IView view, object args) + static void MapFocus(IViewHandler handler, VisualElement view, object args, Action baseMethod) { if (args is not FocusRequest fr) return; - view.MapFocus(handler, fr); + view.MapFocus(fr, baseMethod is null ? null : () => baseMethod?.Invoke(handler, view, args)); } } } diff --git a/src/Controls/src/Core/ViewExtensions.cs b/src/Controls/src/Core/ViewExtensions.cs index c5c0cc9e5439..18b75f78bc48 100644 --- a/src/Controls/src/Core/ViewExtensions.cs +++ b/src/Controls/src/Core/ViewExtensions.cs @@ -452,35 +452,32 @@ static internal bool RequestFocus(this VisualElement view) // if there is no handler, we need to still run some code var focusRequest = new FocusRequest(); - MapFocus(view, null, focusRequest); + view.MapFocus(focusRequest); return focusRequest.Result; } - static internal void MapFocus(this IView view, IViewHandler? handler, FocusRequest focusRequest) + static internal void MapFocus(this VisualElement view, FocusRequest focusRequest, Action? baseMethod = null) { - if (view is VisualElement ve) + // the virtual view is already focused + if (view.IsFocused) { - // the virtual view is already focused - if (ve.IsFocused) - { - focusRequest.TrySetResult(true); - return; - } + focusRequest.TrySetResult(true); + return; + } - // if there are legacy events, then use that - if (ve.HasFocusChangeRequestedEvent) - { - var arg = new VisualElement.FocusRequestArgs { Focus = true }; - ve.InvokeFocusChangeRequested(arg); - focusRequest.TrySetResult(arg.Result); - return; - } + // if there are legacy events, then use that + if (view.HasFocusChangeRequestedEvent) + { + var arg = new VisualElement.FocusRequestArgs { Focus = true }; + view.InvokeFocusChangeRequested(arg); + focusRequest.TrySetResult(arg.Result); + return; } // otherwise, fall back to "base" - if (handler is not null) + if (baseMethod is not null) { - ViewHandler.MapFocus(handler, view, focusRequest); + baseMethod.Invoke(); return; } diff --git a/src/Core/src/Handlers/SearchBar/SearchBarHandler.Android.cs b/src/Core/src/Handlers/SearchBar/SearchBarHandler.Android.cs index b6637ebbe66e..48f982b050f4 100644 --- a/src/Core/src/Handlers/SearchBar/SearchBarHandler.Android.cs +++ b/src/Core/src/Handlers/SearchBar/SearchBarHandler.Android.cs @@ -119,7 +119,7 @@ public static void MapKeyboard(ISearchBarHandler handler, ISearchBar searchBar) handler.PlatformView?.UpdateKeyboard(searchBar); } - static void MapFocus(ISearchBarHandler handler, ISearchBar searchBar, object? args) + public static void MapFocus(ISearchBarHandler handler, ISearchBar searchBar, object? args) { if (args is FocusRequest request) handler.QueryEditor?.Focus(request); diff --git a/src/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txt b/src/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txt index db9619a849ba..4528ab650bd9 100644 --- a/src/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txt +++ b/src/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txt @@ -56,6 +56,7 @@ static Microsoft.Maui.FontSize.operator ==(Microsoft.Maui.FontSize left, Microso static Microsoft.Maui.Graphics.PaintExtensions.ToDrawable(this Microsoft.Maui.Graphics.Paint? paint, Android.Content.Context? context) -> Android.Graphics.Drawables.Drawable? static Microsoft.Maui.GridLength.operator !=(Microsoft.Maui.GridLength left, Microsoft.Maui.GridLength right) -> bool static Microsoft.Maui.GridLength.operator ==(Microsoft.Maui.GridLength left, Microsoft.Maui.GridLength right) -> bool +static Microsoft.Maui.Handlers.SearchBarHandler.MapFocus(Microsoft.Maui.Handlers.ISearchBarHandler! handler, Microsoft.Maui.ISearchBar! searchBar, object? args) -> void static Microsoft.Maui.Handlers.StepperHandler.MapIsEnabled(Microsoft.Maui.Handlers.IStepperHandler! handler, Microsoft.Maui.IStepper! stepper) -> void static Microsoft.Maui.Layouts.FlexBasis.operator !=(Microsoft.Maui.Layouts.FlexBasis left, Microsoft.Maui.Layouts.FlexBasis right) -> bool static Microsoft.Maui.Layouts.FlexBasis.operator ==(Microsoft.Maui.Layouts.FlexBasis left, Microsoft.Maui.Layouts.FlexBasis right) -> bool diff --git a/src/Core/tests/DeviceTests/Handlers/Button/ButtonHandlerTests.cs b/src/Core/tests/DeviceTests/Handlers/Button/ButtonHandlerTests.cs index 86b10a5c3011..5255ad411038 100644 --- a/src/Core/tests/DeviceTests/Handlers/Button/ButtonHandlerTests.cs +++ b/src/Core/tests/DeviceTests/Handlers/Button/ButtonHandlerTests.cs @@ -180,8 +180,9 @@ public class ButtonTextStyleTests : TextStyleHandlerTests { diff --git a/src/TestUtils/src/DeviceTests/AssertionExtensions.Android.cs b/src/TestUtils/src/DeviceTests/AssertionExtensions.Android.cs index 1a20a26eb930..515d878f95c7 100644 --- a/src/TestUtils/src/DeviceTests/AssertionExtensions.Android.cs +++ b/src/TestUtils/src/DeviceTests/AssertionExtensions.Android.cs @@ -284,6 +284,9 @@ public static string CreateColorError(this Bitmap bitmap, string message) => public static string CreateEqualError(this Bitmap bitmap, Bitmap other, string message) => $"{message} This is what it looked like: {bitmap.ToBase64String()} and {other.ToBase64String()}"; + public static string CreateScreenshotError(this Bitmap bitmap, string message) => + $"{message} This is what it looked like:{bitmap.ToBase64String()}"; + public static AColor ColorAtPoint(this Bitmap bitmap, int x, int y, bool includeAlpha = false) { int pixel = bitmap.GetPixel(x, y); @@ -594,6 +597,15 @@ public static Task AssertNotEqualAsync(this Bitmap bitmap, Bitmap other) return Task.CompletedTask; } + public static async Task ThrowScreenshot(this AView view, IMauiContext mauiContext, string? message = null, Exception? ex = null) + { + var bitmap = await view.ToBitmap(mauiContext); + if (ex is null) + throw new XunitException(CreateScreenshotError(bitmap, message ?? "There was an error.")); + else + throw new XunitException(CreateScreenshotError(bitmap, message ?? "There was an error: " + ex.Message), ex); + } + public static TextUtils.TruncateAt? ToPlatform(this LineBreakMode mode) => mode switch { diff --git a/src/TestUtils/src/DeviceTests/AssertionExtensions.Windows.cs b/src/TestUtils/src/DeviceTests/AssertionExtensions.Windows.cs index 5a93d55328f4..ebcb6b9fc2fc 100644 --- a/src/TestUtils/src/DeviceTests/AssertionExtensions.Windows.cs +++ b/src/TestUtils/src/DeviceTests/AssertionExtensions.Windows.cs @@ -44,16 +44,6 @@ public static Task SendKeyboardReturnType(this FrameworkElement view, ReturnType public static async Task WaitForFocused(this FrameworkElement view, int timeout = 1000) { - if (view is AutoSuggestBox searchView) - { - var queryEditor = searchView.GetFirstDescendant(); - - if (queryEditor is null) - throw new Exception("Unable to locate TextBox on AutoSuggestBox"); - - view = queryEditor; - } - TaskCompletionSource focusSource = new TaskCompletionSource(); view.GotFocus += OnFocused; @@ -87,8 +77,6 @@ public static async Task WaitForUnFocused(this FrameworkElement view, int timeou view.LostFocus -= OnUnFocused; } - await focusSource.Task.WaitAsync(TimeSpan.FromMilliseconds(timeout)); - void OnUnFocused(object? sender, RoutedEventArgs e) { view.LostFocus -= OnUnFocused; @@ -120,6 +108,9 @@ public static async Task CreateColorError(this CanvasBitmap bitmap, stri public static async Task CreateEqualError(this CanvasBitmap bitmap, CanvasBitmap other, string message) => $"{message} This is what it looked like: {await bitmap.ToBase64StringAsync()} and {await other.ToBase64StringAsync()}"; + public static async Task CreateScreenshotError(this CanvasBitmap bitmap, string message) => + $"{message} This is what it looked like:{await bitmap.ToBase64StringAsync()}"; + public static async Task ToBase64StringAsync(this CanvasBitmap bitmap) { using var ms = new InMemoryRandomAccessStream(); @@ -455,6 +446,15 @@ bool IsMatching() } } + public static async Task ThrowScreenshot(this FrameworkElement view, IMauiContext mauiContext, string? message = null, Exception? ex = null) + { + var bitmap = await view.ToBitmap(mauiContext); + if (ex is null) + throw new XunitException(await CreateScreenshotError(bitmap, message ?? "There was an error.")); + else + throw new XunitException(await CreateScreenshotError(bitmap, message ?? "There was an error: " + ex.Message), ex); + } + public static TextTrimming ToPlatform(this LineBreakMode mode) => mode switch { diff --git a/src/TestUtils/src/DeviceTests/AssertionExtensions.iOS.cs b/src/TestUtils/src/DeviceTests/AssertionExtensions.iOS.cs index 9acc9c957444..dc64fe9a6aae 100644 --- a/src/TestUtils/src/DeviceTests/AssertionExtensions.iOS.cs +++ b/src/TestUtils/src/DeviceTests/AssertionExtensions.iOS.cs @@ -82,6 +82,9 @@ public static string CreateColorError(this UIImage bitmap, string message) => public static string CreateEqualError(this UIImage bitmap, UIImage other, string message) => $"{message} This is what it looked like: {bitmap.ToBase64String()} and {other.ToBase64String()}"; + public static string CreateScreenshotError(this UIImage bitmap, string message) => + $"{message} This is what it looked like:{bitmap.ToBase64String()}"; + public static string ToBase64String(this UIImage bitmap) { var data = bitmap.AsPNG(); @@ -415,6 +418,15 @@ bool IsMatching() } } + public static async Task ThrowScreenshot(this UIView view, IMauiContext mauiContext, string? message = null, Exception? ex = null) + { + var bitmap = await view.ToBitmap(mauiContext); + if (ex is null) + throw new XunitException(CreateScreenshotError(bitmap, message ?? "There was an error.")); + else + throw new XunitException(CreateScreenshotError(bitmap, message ?? "There was an error: " + ex.Message), ex); + } + public static UILineBreakMode ToPlatform(this LineBreakMode mode) => mode switch {