From a8222d6b3c14477b55a51619ea32d191f82344ff Mon Sep 17 00:00:00 2001 From: Shane Neuville Date: Mon, 14 Nov 2022 16:25:14 -0600 Subject: [PATCH] Set window soft input via window mapper --- .../HandlerImpl/Application/Application.cs | 2 + .../Core/HandlerImpl/Window/Window.Android.cs | 16 ++ .../src/Core/HandlerImpl/Window/Window.cs | 9 +- .../Extensions/ApplicationExtensions.cs | 39 ++--- .../Application/ApplicationTests.Android.cs | 147 ++++++++++++++++++ .../Elements/Application/ApplicationTests.cs | 13 ++ src/Controls/tests/DeviceTests/MauiProgram.cs | 2 +- .../tests/DeviceTests/TestCategory.cs | 1 + .../src/Platform/Android/WindowExtensions.cs | 12 ++ 9 files changed, 216 insertions(+), 25 deletions(-) create mode 100644 src/Controls/tests/DeviceTests/Elements/Application/ApplicationTests.Android.cs create mode 100644 src/Controls/tests/DeviceTests/Elements/Application/ApplicationTests.cs diff --git a/src/Controls/src/Core/HandlerImpl/Application/Application.cs b/src/Controls/src/Core/HandlerImpl/Application/Application.cs index 09ef53df4b66..5b3036a34576 100644 --- a/src/Controls/src/Core/HandlerImpl/Application/Application.cs +++ b/src/Controls/src/Core/HandlerImpl/Application/Application.cs @@ -6,6 +6,8 @@ public partial class Application new PropertyMapper(ApplicationHandler.Mapper) { #if ANDROID + // There is also a mapper on Window for this property since this property is relevant at the window level for + // Android not the application level [PlatformConfiguration.AndroidSpecific.Application.WindowSoftInputModeAdjustProperty.PropertyName] = MapWindowSoftInputModeAdjust, #endif }; diff --git a/src/Controls/src/Core/HandlerImpl/Window/Window.Android.cs b/src/Controls/src/Core/HandlerImpl/Window/Window.Android.cs index a1fc5fb8ac03..9ebf072d2715 100644 --- a/src/Controls/src/Core/HandlerImpl/Window/Window.Android.cs +++ b/src/Controls/src/Core/HandlerImpl/Window/Window.Android.cs @@ -3,6 +3,7 @@ using Android.App; using Android.Views; using AndroidX.AppCompat.App; +using Microsoft.Maui.Controls.Platform; using Microsoft.Maui.Handlers; namespace Microsoft.Maui.Controls @@ -20,5 +21,20 @@ public static void MapContent(WindowHandler handler, IWindow view) public static void MapContent(IWindowHandler handler, IWindow view) { } + + internal static void MapWindowSoftInputModeAdjust(IWindowHandler handler, IWindow view) + { + if (view.Parent is Application app) + { + var setting = PlatformConfiguration.AndroidSpecific.Application.GetWindowSoftInputModeAdjust(app); + view.UpdateWindowSoftInputModeAdjust(setting.ToPlatform()); + } + } + + private protected override void OnParentChangedCore() + { + base.OnParentChangedCore(); + Handler?.UpdateValue(PlatformConfiguration.AndroidSpecific.Application.WindowSoftInputModeAdjustProperty.PropertyName); + } } } \ No newline at end of file diff --git a/src/Controls/src/Core/HandlerImpl/Window/Window.cs b/src/Controls/src/Core/HandlerImpl/Window/Window.cs index dd61c7b4f7bc..996e44bb6412 100644 --- a/src/Controls/src/Core/HandlerImpl/Window/Window.cs +++ b/src/Controls/src/Core/HandlerImpl/Window/Window.cs @@ -5,7 +5,14 @@ namespace Microsoft.Maui.Controls { public partial class Window { - public static IPropertyMapper ControlsWindowMapper = new PropertyMapper(WindowHandler.Mapper); + public static IPropertyMapper ControlsWindowMapper = + new PropertyMapper(WindowHandler.Mapper) + { +#if ANDROID + // This property is also on the Application Mapper since that's where the attached property exists + [PlatformConfiguration.AndroidSpecific.Application.WindowSoftInputModeAdjustProperty.PropertyName] = MapWindowSoftInputModeAdjust, +#endif + }; internal static void RemapForControls() { diff --git a/src/Controls/src/Core/Platform/Android/Extensions/ApplicationExtensions.cs b/src/Controls/src/Core/Platform/Android/Extensions/ApplicationExtensions.cs index c1940fdbe5ec..d3accf33ac2b 100644 --- a/src/Controls/src/Core/Platform/Android/Extensions/ApplicationExtensions.cs +++ b/src/Controls/src/Core/Platform/Android/Extensions/ApplicationExtensions.cs @@ -1,5 +1,6 @@ using Android.App; using Android.Content; +using Android.Media; using Android.Views; using Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpecific; using AApplication = Android.App.Application; @@ -10,32 +11,24 @@ public static class ApplicationExtensions { public static void UpdateWindowSoftInputModeAdjust(this AApplication platformView, Application application) { - var adjust = SoftInput.AdjustPan; - - if (Application.Current != null) + if (application is IApplication app) { - WindowSoftInputModeAdjust elementValue = Application.Current.OnThisPlatform().GetWindowSoftInputModeAdjust(); - - switch (elementValue) - { - case WindowSoftInputModeAdjust.Resize: - adjust = SoftInput.AdjustResize; - break; - case WindowSoftInputModeAdjust.Unspecified: - adjust = SoftInput.AdjustUnspecified; - break; - default: - adjust = SoftInput.AdjustPan; - break; - } + foreach (var window in app.Windows) + window?.Handler?.UpdateValue(PlatformConfiguration.AndroidSpecific.Application.WindowSoftInputModeAdjustProperty.PropertyName); } + } - IMauiContext mauiContext = application.FindMauiContext(true); - Context context = mauiContext?.Context; - Activity activity = context.GetActivity(); - - activity?.Window?.SetSoftInputMode(adjust); - + internal static SoftInput ToPlatform(this WindowSoftInputModeAdjust windowSoftInputModeAdjust) + { + switch (windowSoftInputModeAdjust) + { + case WindowSoftInputModeAdjust.Resize: + return SoftInput.AdjustResize; + case WindowSoftInputModeAdjust.Unspecified: + return SoftInput.AdjustUnspecified; + default: + return SoftInput.AdjustPan; + } } } } diff --git a/src/Controls/tests/DeviceTests/Elements/Application/ApplicationTests.Android.cs b/src/Controls/tests/DeviceTests/Elements/Application/ApplicationTests.Android.cs new file mode 100644 index 000000000000..bc0c8bc28966 --- /dev/null +++ b/src/Controls/tests/DeviceTests/Elements/Application/ApplicationTests.Android.cs @@ -0,0 +1,147 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Maui.Controls; +using Microsoft.Maui.Controls.Platform; +using Microsoft.Maui.Handlers; +using Microsoft.Maui.Hosting; +using Microsoft.Maui.Platform; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; +using ASoftInput = Android.Views.SoftInput; +using AApplication = Android.App.Application; +using AActivity = Android.App.Activity; + +namespace Microsoft.Maui.DeviceTests +{ + [Category(TestCategory.Application)] + public partial class ApplicationTests : ControlsHandlerTestBase + { + [Category(TestCategory.Application)] + public class SoftInputModeTests : ControlsHandlerTestBase + { + [Fact] + public async Task SoftInputModeDefaultsToAdjustPan() + { + await InvokeOnMainThreadAsync(() => + { + Assert.Equal(ASoftInput.AdjustPan, GetSoftInput()); + }); + } + + [Fact] + public async Task SoftInputModeSetOnApplicationPropagatesToWindowHandlers() + { + EnsureHandlerCreated((builder) => + { + builder.Services.AddSingleton((_) => new SoftInputModeApplication()); + builder.ConfigureMauiHandlers(handler => + { + handler.AddHandler(); + handler.AddHandler(); + }); + }); + + await InvokeOnMainThreadAsync(() => + { + // Setup application stub + var app = MauiContext.Services.GetService() as SoftInputModeApplication; + app.Handler = app.ToHandler(MauiContext); + + // Setup window + var windowHandler = (SoftInputWindowHandlerStub)app.Window.ToHandler(MauiContext); + app.Window.Handler = windowHandler; + + // Validate that the Soft Input initializes to AdjustPan + Assert.Equal(ASoftInput.AdjustPan, windowHandler.LastASoftInputSet); + + // Set to Resize + Controls.PlatformConfiguration.AndroidSpecific.Application.SetWindowSoftInputModeAdjust( + app, + Controls.PlatformConfiguration.AndroidSpecific.WindowSoftInputModeAdjust.Resize); + + // Validate the mapper on the window handler is called with correct value + Assert.Equal(ASoftInput.AdjustResize, windowHandler.LastASoftInputSet); + + // Set to Pan + Controls.PlatformConfiguration.AndroidSpecific.Application.SetWindowSoftInputModeAdjust( + app, + Controls.PlatformConfiguration.AndroidSpecific.WindowSoftInputModeAdjust.Pan); + + // Validate the mapper on the window handler is called with correct value + Assert.Equal(ASoftInput.AdjustPan, windowHandler.LastASoftInputSet); + }); + } + + ASoftInput GetSoftInput() => + GetSoftInput(MauiContext.Context.GetActivity()); + + ASoftInput GetSoftInput(AActivity aActivity) => + aActivity.Window.Attributes.SoftInputMode; + + class SoftInputApplicationHandlerStub : ApplicationHandler + { + public SoftInputApplicationHandlerStub() : base(Application.ControlsApplicationMapper) + { + } + + protected override AApplication CreatePlatformElement() + { + return new AApplication(); + } + } + + class SoftInputModeApplication : Application, IApplication + { + public SoftInputModeWindow Window { get; } = new SoftInputModeWindow(); + + public SoftInputModeApplication() : base(false) + { + Window.Parent = this; + } + + IReadOnlyList IApplication.Windows + { + get + { + return new List() { Window }; + } + } + } + + class SoftInputModeWindow : Window + { + + } + + class SoftInputWindowHandlerStub : ElementHandler, IWindowHandler + { + public ASoftInput LastASoftInputSet { get; private set; } = ASoftInput.AdjustUnspecified; + + public static IPropertyMapper StubMapper = + new PropertyMapper() + { + [Controls.PlatformConfiguration.AndroidSpecific.Application.WindowSoftInputModeAdjustProperty.PropertyName] = MapWindowSoftInputModeAdjust + }; + + public static void MapWindowSoftInputModeAdjust(SoftInputWindowHandlerStub arg1, IWindow arg2) + { + if (arg2.Parent is Application app) + { + var setting = Controls.PlatformConfiguration.AndroidSpecific.Application.GetWindowSoftInputModeAdjust(app); + arg1.LastASoftInputSet = setting.ToPlatform(); + } + } + + public SoftInputWindowHandlerStub() : base(StubMapper, null) + { + + } + + protected override AActivity CreatePlatformElement() + { + return new AActivity(); + } + } + } + } +} diff --git a/src/Controls/tests/DeviceTests/Elements/Application/ApplicationTests.cs b/src/Controls/tests/DeviceTests/Elements/Application/ApplicationTests.cs new file mode 100644 index 000000000000..3859f5de499a --- /dev/null +++ b/src/Controls/tests/DeviceTests/Elements/Application/ApplicationTests.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.Maui.DeviceTests +{ + public partial class ApplicationTests : ControlsHandlerTestBase + { + } +} diff --git a/src/Controls/tests/DeviceTests/MauiProgram.cs b/src/Controls/tests/DeviceTests/MauiProgram.cs index 2a8d8289bd3c..d22625138e6b 100644 --- a/src/Controls/tests/DeviceTests/MauiProgram.cs +++ b/src/Controls/tests/DeviceTests/MauiProgram.cs @@ -14,7 +14,7 @@ public static class MauiProgram public static Microsoft.UI.Xaml.Window CurrentWindow => MauiProgramDefaults.DefaultWindow; #endif - public static IApplication DefaultTestApp { get; private set; } + public static IApplication DefaultTestApp => MauiProgramDefaults.DefaultTestApp; public static MauiApp CreateMauiApp() => MauiProgramDefaults.CreateMauiApp(new List() diff --git a/src/Controls/tests/DeviceTests/TestCategory.cs b/src/Controls/tests/DeviceTests/TestCategory.cs index dfedc804224e..bf5600d80992 100644 --- a/src/Controls/tests/DeviceTests/TestCategory.cs +++ b/src/Controls/tests/DeviceTests/TestCategory.cs @@ -2,6 +2,7 @@ { public static class TestCategory { + public const string Application = "Application"; public const string Behavior = "Behavior"; public const string Button = "Button"; public const string CheckBox = "CheckBox"; diff --git a/src/Core/src/Platform/Android/WindowExtensions.cs b/src/Core/src/Platform/Android/WindowExtensions.cs index 6b392030e5ab..f7aa040a8edd 100644 --- a/src/Core/src/Platform/Android/WindowExtensions.cs +++ b/src/Core/src/Platform/Android/WindowExtensions.cs @@ -1,5 +1,7 @@ using Android.App; +using Android.Content; using Android.Content.Res; +using Android.Views; using Microsoft.Maui.Devices; using Microsoft.Maui.Platform; @@ -20,5 +22,15 @@ internal static DisplayOrientation GetOrientation(this IWindow? window) _ => DisplayOrientation.Unknown }; } + + internal static void UpdateWindowSoftInputModeAdjust(this IWindow platformView, SoftInput inputMode) + { + var activity = platformView?.Handler?.PlatformView as Activity ?? + platformView?.Handler?.MauiContext?.GetPlatformWindow(); + + activity? + .Window? + .SetSoftInputMode(inputMode); + } } }