From a4bffbf660595ea70e3073401fd3df43746fd73f Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 6 Jan 2023 02:15:18 -0500 Subject: [PATCH] iOS and Android implementations --- .../Avalonia.Android/AndroidPlatform.cs | 2 +- src/Android/Avalonia.Android/AvaloniaView.cs | 16 ++++ .../Platform/AndroidPlatformSettings.cs | 73 +++++++++++++++++++ .../Platform/SkiaPlatform/TopLevelImpl.cs | 6 ++ src/iOS/Avalonia.iOS/AvaloniaView.cs | 27 ++++++- .../Avalonia.iOS/AvaloniaViewController.cs | 6 ++ src/iOS/Avalonia.iOS/Platform.cs | 2 +- src/iOS/Avalonia.iOS/PlatformSettings.cs | 34 +++++++++ 8 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 src/Android/Avalonia.Android/Platform/AndroidPlatformSettings.cs create mode 100644 src/iOS/Avalonia.iOS/AvaloniaViewController.cs create mode 100644 src/iOS/Avalonia.iOS/PlatformSettings.cs diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index 01b48ffd89..c73fd92423 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -43,7 +43,7 @@ namespace Avalonia.Android .Bind().ToTransient() .Bind().ToConstant(new WindowingPlatformStub()) .Bind().ToSingleton() - .Bind().ToSingleton() + .Bind().ToSingleton() .Bind().ToConstant(new AndroidThreadingInterface()) .Bind().ToSingleton() .Bind().ToConstant(new ChoreographerTimer()) diff --git a/src/Android/Avalonia.Android/AvaloniaView.cs b/src/Android/Avalonia.Android/AvaloniaView.cs index f7e32f99db..2a345a857c 100644 --- a/src/Android/Avalonia.Android/AvaloniaView.cs +++ b/src/Android/Avalonia.Android/AvaloniaView.cs @@ -1,11 +1,14 @@ using System; using Android.Content; +using Android.Content.Res; using Android.Runtime; using Android.Views; using Android.Widget; +using Avalonia.Android.Platform; using Avalonia.Android.Platform.SkiaPlatform; using Avalonia.Controls; using Avalonia.Controls.Embedding; +using Avalonia.Platform; using Avalonia.Rendering; namespace Avalonia.Android @@ -26,6 +29,7 @@ namespace Avalonia.Android _root.Prepare(); this.SetBackgroundColor(global::Android.Graphics.Color.Transparent); + OnConfigurationChanged(); } internal TopLevelImpl TopLevelImpl => _view; @@ -70,6 +74,18 @@ namespace Avalonia.Android _timerSubscription?.Dispose(); } } + + protected override void OnConfigurationChanged(Configuration newConfig) + { + base.OnConfigurationChanged(newConfig); + OnConfigurationChanged(); + } + + private void OnConfigurationChanged() + { + var settings = AvaloniaLocator.Current.GetRequiredService() as AndroidPlatformSettings; + settings?.OnViewConfigurationChanged(Context); + } class ViewImpl : TopLevelImpl { diff --git a/src/Android/Avalonia.Android/Platform/AndroidPlatformSettings.cs b/src/Android/Avalonia.Android/Platform/AndroidPlatformSettings.cs new file mode 100644 index 0000000000..f560dea3ce --- /dev/null +++ b/src/Android/Avalonia.Android/Platform/AndroidPlatformSettings.cs @@ -0,0 +1,73 @@ +using System; +using Android; +using Android.Content; +using Android.Content.Res; +using Android.Graphics; +using AndroidX.Core.Content.Resources; +using Avalonia.Media; +using Avalonia.Platform; +using Color = Avalonia.Media.Color; + +namespace Avalonia.Android.Platform; + +// TODO: ideally should be created per view/activity. +internal class AndroidPlatformSettings : DefaultPlatformSettings +{ + private PlatformColorValues _latestValues; + + public AndroidPlatformSettings() + { + _latestValues = base.GetColorValues(); + } + + public override PlatformColorValues GetColorValues() + { + return _latestValues; + } + + internal void OnViewConfigurationChanged(Context context) + { + if (context.Resources?.Configuration is null) + { + return; + } + + var systemTheme = (context.Resources.Configuration.UiMode & UiMode.NightMask) switch + { + UiMode.NightYes => PlatformThemeVariant.Dark, + UiMode.NightNo => PlatformThemeVariant.Light, + _ => throw new ArgumentOutOfRangeException() + }; + + if (OperatingSystem.IsAndroidVersionAtLeast(31)) + { + // See https://developer.android.com/reference/android/R.color + var accent1 = context.Resources.GetColor(17170494, context.Theme); // Resource.Color.SystemAccent1500 + var accent2 = context.Resources.GetColor(17170507, context.Theme); // Resource.Color.SystemAccent2500 + var accent3 = context.Resources.GetColor(17170520, context.Theme); // Resource.Color.SystemAccent3500 + + _latestValues = new PlatformColorValues( + systemTheme, + new Color(accent1.A, accent1.R, accent1.G, accent1.B), + new Color(accent2.A, accent2.R, accent2.G, accent2.B), + new Color(accent3.A, accent3.R, accent3.G, accent3.B)); + } + else if (OperatingSystem.IsAndroidVersionAtLeast(23)) + { + // See https://developer.android.com/reference/android/R.attr + var array = context.Theme.ObtainStyledAttributes(new[] { 16843829 }); // Resource.Attribute.ColorAccent + var accent = array.GetColor(0, 0); + + _latestValues = new PlatformColorValues( + systemTheme, + new Color(accent.A, accent.R, accent.G, accent.B)); + array.Recycle(); + } + else + { + _latestValues = _latestValues with { ThemeVariant = systemTheme }; + } + + OnColorValuesChanged(_latestValues); + } +} diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 1aac567dda..1f997af26d 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -26,6 +26,7 @@ using Avalonia.Rendering.Composition; using Java.Lang; using Math = System.Math; using AndroidRect = Android.Graphics.Rect; +using Window = Android.Views.Window; using Android.Graphics.Drawables; namespace Avalonia.Android.Platform.SkiaPlatform @@ -286,6 +287,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform public WindowTransparencyLevel TransparencyLevel { get; private set; } + public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) + { + // TODO adjust status bar depending on full screen mode. + } + public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new AcrylicPlatformCompensationLevels(1, 1, 1); IntPtr EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Handle => ((IPlatformHandle)_view).Handle; diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs index 48358c745f..6d36ca7814 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs @@ -52,6 +52,14 @@ namespace Avalonia.iOS public override bool CanResignFirstResponder => true; + public override void TraitCollectionDidChange(UITraitCollection previousTraitCollection) + { + base.TraitCollectionDidChange(previousTraitCollection); + + var settings = AvaloniaLocator.Current.GetRequiredService() as PlatformSettings; + settings?.TraitCollectionDidChange(); + } + internal class TopLevelImpl : ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider { @@ -120,7 +128,24 @@ namespace Avalonia.iOS // legacy no-op public IMouseDevice MouseDevice { get; } = new MouseDevice(); public WindowTransparencyLevel TransparencyLevel { get; } - + + public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) + { + // TODO adjust status bar depending on full screen mode. + if (OperatingSystem.IsIOSVersionAtLeast(13)) + { + var uiStatusBarStyle = themeVariant switch + { + PlatformThemeVariant.Light => UIStatusBarStyle.DarkContent, + PlatformThemeVariant.Dark => UIStatusBarStyle.LightContent, + _ => throw new ArgumentOutOfRangeException(nameof(themeVariant), themeVariant, null) + }; + + // Consider using UIViewController.PreferredStatusBarStyle in the future. + UIApplication.SharedApplication.SetStatusBarStyle(uiStatusBarStyle, true); + } + } + public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(); diff --git a/src/iOS/Avalonia.iOS/AvaloniaViewController.cs b/src/iOS/Avalonia.iOS/AvaloniaViewController.cs new file mode 100644 index 0000000000..c6856789b8 --- /dev/null +++ b/src/iOS/Avalonia.iOS/AvaloniaViewController.cs @@ -0,0 +1,6 @@ +namespace Avalonia.iOS; + +public class AvaloniaViewController +{ + +} diff --git a/src/iOS/Avalonia.iOS/Platform.cs b/src/iOS/Avalonia.iOS/Platform.cs index 63025f7f0a..5bcd1eae84 100644 --- a/src/iOS/Avalonia.iOS/Platform.cs +++ b/src/iOS/Avalonia.iOS/Platform.cs @@ -40,7 +40,7 @@ namespace Avalonia.iOS .Bind().ToConstant(new CursorFactoryStub()) .Bind().ToConstant(new WindowingPlatformStub()) .Bind().ToConstant(new ClipboardImpl()) - .Bind().ToSingleton() + .Bind().ToSingleton() .Bind().ToConstant(new PlatformIconLoaderStub()) .Bind().ToSingleton() .Bind().ToSingleton() diff --git a/src/iOS/Avalonia.iOS/PlatformSettings.cs b/src/iOS/Avalonia.iOS/PlatformSettings.cs new file mode 100644 index 0000000000..736e94ffc6 --- /dev/null +++ b/src/iOS/Avalonia.iOS/PlatformSettings.cs @@ -0,0 +1,34 @@ +using System; +using System.Linq; +using Avalonia.Media; +using Avalonia.Platform; +using Foundation; +using UIKit; + +namespace Avalonia.iOS; + +// TODO: ideally should be created per view/activity. +internal class PlatformSettings : DefaultPlatformSettings +{ + private PlatformColorValues _lastColorValues; + + public override PlatformColorValues GetColorValues() + { + var themeVariant = UITraitCollection.CurrentTraitCollection.UserInterfaceStyle == UIUserInterfaceStyle.Dark ? + PlatformThemeVariant.Dark : + PlatformThemeVariant.Light; + + return _lastColorValues = new PlatformColorValues(themeVariant); + } + + public void TraitCollectionDidChange() + { + var oldColorValues = _lastColorValues; + var colorValues = GetColorValues(); + + if (oldColorValues != colorValues) + { + OnColorValuesChanged(colorValues); + } + } +}