From 91348e22424aebc21218fbe9483798d37724b219 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Wed, 17 Jul 2024 12:07:25 +0000 Subject: [PATCH] allow setting ThemeVariant to Default to reset theme to system on android --- samples/SafeAreaDemo/App.xaml | 2 +- samples/SafeAreaDemo/Views/MainView.xaml | 1 - src/Android/Avalonia.Android/AvaloniaView.cs | 51 ++++++++++++++----- .../Platform/AndroidInsetsManager.cs | 5 ++ .../Platform/AndroidPlatformSettings.cs | 8 +-- .../Platform/SkiaPlatform/TopLevelImpl.cs | 17 +++++-- src/Avalonia.Base/Styling/ThemeVariant.cs | 6 +-- src/Avalonia.Controls/Application.cs | 14 +++-- .../Offscreen/OffscreenTopLevelImpl.cs | 2 +- .../Platform/ITopLevelImpl.cs | 2 +- src/Avalonia.Controls/ThemeVariantScope.cs | 5 ++ src/Avalonia.Controls/TopLevel.cs | 17 ++++--- src/Avalonia.DesignerSupport/Remote/Stubs.cs | 2 +- src/Avalonia.Native/TopLevelImpl.cs | 2 +- src/Avalonia.Native/WindowImplBase.cs | 4 +- src/Avalonia.X11/X11Window.cs | 2 +- .../Avalonia.Browser/BrowserTopLevelImpl.cs | 2 +- .../Avalonia.Headless/HeadlessWindowImpl.cs | 2 +- .../FramebufferToplevelImpl.cs | 2 +- src/Windows/Avalonia.Win32/WindowImpl.cs | 4 +- src/iOS/Avalonia.iOS/AvaloniaView.cs | 2 +- .../CompositorTestServices.cs | 2 +- 22 files changed, 105 insertions(+), 49 deletions(-) diff --git a/samples/SafeAreaDemo/App.xaml b/samples/SafeAreaDemo/App.xaml index f406bfe710..70fd759dad 100644 --- a/samples/SafeAreaDemo/App.xaml +++ b/samples/SafeAreaDemo/App.xaml @@ -2,7 +2,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:SafeAreaDemo" x:Class="SafeAreaDemo.App" - RequestedThemeVariant="Light"> + RequestedThemeVariant="Default"> diff --git a/samples/SafeAreaDemo/Views/MainView.xaml b/samples/SafeAreaDemo/Views/MainView.xaml index 85163e7dad..a2b336abe0 100644 --- a/samples/SafeAreaDemo/Views/MainView.xaml +++ b/samples/SafeAreaDemo/Views/MainView.xaml @@ -8,7 +8,6 @@ d:DesignHeight="450" x:Class="SafeAreaDemo.Views.MainView" x:DataType="vm:MainViewModel" - Background="#ccc" TopLevel.AutoSafeAreaPadding="{Binding AutoSafeAreaPadding, Mode=TwoWay}"> _view; @@ -39,15 +36,42 @@ namespace Avalonia.Android public object? Content { - get { return _root.Content; } - set { _root.Content = value; } + get { return _root?.Content; } + set + { + _content = null; + if (_root != null) + _root.Content = value; + else + { + _content = value; + } + } } protected override void Dispose(bool disposing) { base.Dispose(disposing); + } + + protected override void OnDetachedFromWindow() + { + base.OnDetachedFromWindow(); _root?.Dispose(); - _root = null!; + _root = null; + } + + protected override void OnAttachedToWindow() + { + _root = new EmbeddableControlRoot(_view); + _root.Prepare(); + if(_content != null) + { + _root.Content = _content; + } + SendConfigurationChanged(Context?.Resources?.Configuration); + + base.OnAttachedToWindow(); } public override bool DispatchKeyEvent(KeyEvent? e) @@ -70,6 +94,8 @@ namespace Avalonia.Android internal void OnVisibilityChanged(bool isVisible) { + if (_root == null) + return; if (isVisible && _timerSubscription == null) { if (AvaloniaLocator.Current.GetService() is ChoreographerTimer timer) @@ -95,16 +121,17 @@ namespace Avalonia.Android protected override void OnConfigurationChanged(Configuration? newConfig) { base.OnConfigurationChanged(newConfig); - OnConfigurationChanged(); + SendConfigurationChanged(newConfig ?? Context?.Resources?.Configuration); } - private void OnConfigurationChanged() + private void SendConfigurationChanged(Configuration? newConfig) { - if (Context is { } context) + _view?.InsetsManager?.SetDefaultSystemLightMode(!(newConfig?.UiMode.HasFlag(UiMode.NightYes) ?? false)); + if (Context is { } context && newConfig is { } config) { var settings = AvaloniaLocator.Current.GetRequiredService() as AndroidPlatformSettings; - settings?.OnViewConfigurationChanged(context); + settings?.OnViewConfigurationChanged(context, config); } } diff --git a/src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs b/src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs index 992d37ed18..ebabc50e5a 100644 --- a/src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs +++ b/src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs @@ -166,6 +166,11 @@ namespace Avalonia.Android.Platform return insets; } + internal void SetDefaultSystemLightMode(bool islightMode) + { + _isDefaultSystemBarLightTheme = islightMode; + } + private void NotifySafeAreaChanged(Thickness safeAreaPadding) { Dispatcher.UIThread.Send(_ => SafeAreaChanged?.Invoke(this, new SafeAreaChangedArgs(safeAreaPadding))); diff --git a/src/Android/Avalonia.Android/Platform/AndroidPlatformSettings.cs b/src/Android/Avalonia.Android/Platform/AndroidPlatformSettings.cs index 3963a88203..8ca921c03c 100644 --- a/src/Android/Avalonia.Android/Platform/AndroidPlatformSettings.cs +++ b/src/Android/Avalonia.Android/Platform/AndroidPlatformSettings.cs @@ -22,14 +22,14 @@ internal class AndroidPlatformSettings : DefaultPlatformSettings return _latestValues; } - internal void OnViewConfigurationChanged(Context context) + internal void OnViewConfigurationChanged(Context context, Configuration configuration) { - if (context.Resources?.Configuration is null) + if (context.Resources is null) { return; } - var systemTheme = (context.Resources.Configuration.UiMode & UiMode.NightMask) switch + PlatformThemeVariant systemTheme = (configuration.UiMode & UiMode.NightMask) switch { UiMode.NightYes => PlatformThemeVariant.Dark, UiMode.NightNo => PlatformThemeVariant.Light, @@ -71,7 +71,7 @@ internal class AndroidPlatformSettings : DefaultPlatformSettings } finally { - array.Recycle(); + array.Recycle(); } } } diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 79052bf9bd..8a666ecf63 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -282,9 +282,9 @@ namespace Avalonia.Android.Platform.SkiaPlatform } } - public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) + public void SetFrameThemeVariant(PlatformThemeVariant? themeVariant) { - if(_insetsManager != null) + if (_insetsManager != null) { _insetsManager.SystemBarTheme = themeVariant switch { @@ -294,7 +294,16 @@ namespace Avalonia.Android.Platform.SkiaPlatform }; } - AppCompatDelegate.DefaultNightMode = themeVariant == PlatformThemeVariant.Light ? AppCompatDelegate.ModeNightNo : AppCompatDelegate.ModeNightYes; + AppCompatDelegate.DefaultNightMode = themeVariant == null ? AppCompatDelegate.ModeNightFollowSystem : + themeVariant == PlatformThemeVariant.Light ? AppCompatDelegate.ModeNightNo : AppCompatDelegate.ModeNightYes; + + if (AppCompatDelegate.DefaultNightMode == AppCompatDelegate.ModeNightFollowSystem && _view.Context is { } context + && context.Resources?.Configuration is { } config) + { + var settings = + AvaloniaLocator.Current.GetRequiredService() as AndroidPlatformSettings; + settings?.OnViewConfigurationChanged(context, config); + } } public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new AcrylicPlatformCompensationLevels(1, 1, 1); @@ -305,6 +314,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform public double Scaling => RenderScaling; + internal AndroidInsetsManager? InsetsManager => _insetsManager; + public void SetTransparencyLevelHint(IReadOnlyList transparencyLevels) { if (_view.Context is not AvaloniaMainActivity activity) diff --git a/src/Avalonia.Base/Styling/ThemeVariant.cs b/src/Avalonia.Base/Styling/ThemeVariant.cs index 23bc15dfa7..9a1fb5f931 100644 --- a/src/Avalonia.Base/Styling/ThemeVariant.cs +++ b/src/Avalonia.Base/Styling/ThemeVariant.cs @@ -100,11 +100,11 @@ public sealed record ThemeVariant { PlatformThemeVariant.Light => Light, PlatformThemeVariant.Dark => Dark, - _ => throw new ArgumentOutOfRangeException(nameof(themeVariant), themeVariant, null) + _ => Default, }; } - public static explicit operator PlatformThemeVariant?(ThemeVariant themeVariant) + public static explicit operator PlatformThemeVariant?(ThemeVariant? themeVariant) { if (themeVariant == Light) { @@ -114,7 +114,7 @@ public sealed record ThemeVariant { return PlatformThemeVariant.Dark; } - else if (themeVariant.InheritVariant is { } inheritVariant) + else if (themeVariant?.InheritVariant is { } inheritVariant) { return (PlatformThemeVariant?)inheritVariant; } diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index e48711efdb..210378b933 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -68,6 +68,11 @@ namespace Avalonia /// public event EventHandler? ActualThemeVariantChanged; + static Application() + { + RequestedThemeVariantProperty.OverrideDefaultValue(ThemeVariant.Default); + } + /// /// Creates an instance of the class. /// @@ -364,20 +369,19 @@ namespace Avalonia if (change.Property == RequestedThemeVariantProperty) { - if (change.GetNewValue() is {} themeVariant && themeVariant != ThemeVariant.Default) + if (change.GetNewValue() is { } themeVariant) SetValue(ActualThemeVariantProperty, themeVariant); - else - ClearValue(ActualThemeVariantProperty); } else if (change.Property == ActualThemeVariantProperty) { ActualThemeVariantChanged?.Invoke(this, EventArgs.Empty); } } - + private void OnColorValuesChanged(object? sender, PlatformColorValues e) { - SetValue(ActualThemeVariantProperty, (ThemeVariant)e.ThemeVariant, BindingPriority.Template); + if ((RequestedThemeVariant ?? ThemeVariant.Default) == ThemeVariant.Default) + SetCurrentValue(ActualThemeVariantProperty, (ThemeVariant)e.ThemeVariant); } } } diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs index 14bb65f0df..5668c017b4 100644 --- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs +++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs @@ -61,7 +61,7 @@ namespace Avalonia.Controls.Embedding.Offscreen public Action? TransparencyLevelChanged { get; set; } - public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { } + public void SetFrameThemeVariant(PlatformThemeVariant? themeVariant) { } public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1); diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs index 9b847d71d3..37bcc7a2b2 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs @@ -136,6 +136,6 @@ namespace Avalonia.Platform /// Sets the on the frame if it should be dark or light. /// Also applies for the mobile status bar. /// - void SetFrameThemeVariant(PlatformThemeVariant themeVariant); + void SetFrameThemeVariant(PlatformThemeVariant? themeVariant); } } diff --git a/src/Avalonia.Controls/ThemeVariantScope.cs b/src/Avalonia.Controls/ThemeVariantScope.cs index f5ad4b2f94..c88dfa7dd2 100644 --- a/src/Avalonia.Controls/ThemeVariantScope.cs +++ b/src/Avalonia.Controls/ThemeVariantScope.cs @@ -15,6 +15,11 @@ namespace Avalonia.Controls public static readonly StyledProperty RequestedThemeVariantProperty = ThemeVariant.RequestedThemeVariantProperty.AddOwner(); + static ThemeVariantScope() + { + RequestedThemeVariantProperty.OverrideDefaultValue(ThemeVariant.Default); + } + /// /// Gets or sets the UI theme variant that is used by the control (and its child elements) for resource determination. /// The UI theme you specify with ThemeVariant can override the app-level ThemeVariant. diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index b4b76085d9..2bc9270dfa 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -233,11 +233,6 @@ namespace Avalonia.Controls impl.TransparencyLevelChanged = HandleTransparencyLevelChanged; CreatePlatformImplBinding(TransparencyLevelHintProperty, hint => PlatformImpl.SetTransparencyLevelHint(hint ?? Array.Empty())); - CreatePlatformImplBinding(ActualThemeVariantProperty, variant => - { - variant ??= ThemeVariant.Default; - PlatformImpl?.SetFrameThemeVariant((PlatformThemeVariant?)variant ?? PlatformThemeVariant.Light); - }); _keyboardNavigationHandler?.SetOwner(this); _accessKeyHandler?.SetOwner(this); @@ -252,6 +247,16 @@ namespace Avalonia.Controls SetValue(ActualThemeVariantProperty, _applicationThemeHost.ActualThemeVariant, BindingPriority.Template); _applicationThemeHost.ActualThemeVariantChanged += GlobalActualThemeVariantChanged; } + CreatePlatformImplBinding(ActualThemeVariantProperty, variant => + { + if(_applicationThemeHost is AvaloniaObject element) + { + if (element.GetValue(ThemeVariantScope.RequestedThemeVariantProperty) is { } requestedThemeVariant) + variant = requestedThemeVariant == ThemeVariant.Default ? ThemeVariant.Default : variant; + } + variant ??= ThemeVariant.Default; + PlatformImpl?.SetFrameThemeVariant((PlatformThemeVariant?)variant); + }); ClientSize = impl.ClientSize; @@ -878,7 +883,7 @@ namespace Avalonia.Controls private void GlobalActualThemeVariantChanged(object? sender, EventArgs e) { - SetValue(ActualThemeVariantProperty, ((IThemeVariantHost)sender!).ActualThemeVariant, BindingPriority.Template); + SetCurrentValue(ActualThemeVariantProperty, ((IThemeVariantHost)sender!).ActualThemeVariant); } private void SceneInvalidated(object? sender, SceneInvalidatedEventArgs e) diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index e21f4dc843..65045553e0 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -194,7 +194,7 @@ namespace Avalonia.DesignerSupport.Remote public bool NeedsManagedDecorations => false; - public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { } + public void SetFrameThemeVariant(PlatformThemeVariant? themeVariant) { } public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1); public object TryGetFeature(Type featureType) => null; diff --git a/src/Avalonia.Native/TopLevelImpl.cs b/src/Avalonia.Native/TopLevelImpl.cs index c7d3305efd..c8fa6254fa 100644 --- a/src/Avalonia.Native/TopLevelImpl.cs +++ b/src/Avalonia.Native/TopLevelImpl.cs @@ -148,7 +148,7 @@ internal class TopLevelImpl : ITopLevelImpl, IFramebufferPlatformSurface } public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0, 0); - public virtual void SetFrameThemeVariant(PlatformThemeVariant themeVariant) + public virtual void SetFrameThemeVariant(PlatformThemeVariant? themeVariant) { //noop } diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 29f6547c9f..ba84d1ad0a 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -70,9 +70,9 @@ namespace Avalonia.Native Native?.Resize(clientSize.Width, clientSize.Height, (AvnPlatformResizeReason)reason); } - public override void SetFrameThemeVariant(PlatformThemeVariant themeVariant) + public override void SetFrameThemeVariant(PlatformThemeVariant? themeVariant) { - Native?.SetFrameThemeVariant((AvnPlatformThemeVariant)themeVariant); + Native?.SetFrameThemeVariant((AvnPlatformThemeVariant)(themeVariant ?? PlatformThemeVariant.Light)); } public override void Dispose() diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index eb2c8dea65..963d05e161 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -1440,7 +1440,7 @@ namespace Avalonia.X11 public WindowTransparencyLevel TransparencyLevel => _transparencyHelper?.CurrentLevel ?? WindowTransparencyLevel.None; - public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { } + public void SetFrameThemeVariant(PlatformThemeVariant? themeVariant) { } public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0.8, 0.8); diff --git a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs index 9c15d8d1a5..7785b447e1 100644 --- a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs +++ b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs @@ -141,7 +141,7 @@ namespace Avalonia.Browser public Action? LostFocus { get; set; } public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None; - public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) + public void SetFrameThemeVariant(PlatformThemeVariant? themeVariant) { // not in the standard, but we potentially can use "apple-mobile-web-app-status-bar-style" for iOS and "theme-color" for android. } diff --git a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs index 11d62d11c6..fc2feb7f74 100644 --- a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs @@ -429,7 +429,7 @@ namespace Avalonia.Headless } - public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) + public void SetFrameThemeVariant(PlatformThemeVariant? themeVariant) { } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs index 2dc7b36b11..c027960cde 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs @@ -78,7 +78,7 @@ using Avalonia.Rendering.Composition; public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None; - public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { } + public void SetFrameThemeVariant(PlatformThemeVariant? themeVariant) { } public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1); public object TryGetFeature(Type featureType) => null; diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 5e85b2f3e7..94a4faa908 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -894,9 +894,9 @@ namespace Avalonia.Win32 } } - public unsafe void SetFrameThemeVariant(PlatformThemeVariant themeVariant) + public unsafe void SetFrameThemeVariant(PlatformThemeVariant? themeVariant) { - _currentThemeVariant = themeVariant; + _currentThemeVariant = themeVariant ?? PlatformThemeVariant.Light; if (Win32Platform.WindowsVersion.Build >= 22000) { var pvUseBackdropBrush = themeVariant == PlatformThemeVariant.Dark ? 1 : 0; diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs index ff7098251b..cc91d67836 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs @@ -238,7 +238,7 @@ namespace Avalonia.iOS public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None; - public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) + public void SetFrameThemeVariant(PlatformThemeVariant? themeVariant) { #if !TVOS // TODO adjust status bar depending on full screen mode. diff --git a/tests/Avalonia.UnitTests/CompositorTestServices.cs b/tests/Avalonia.UnitTests/CompositorTestServices.cs index 4898604182..709b585dad 100644 --- a/tests/Avalonia.UnitTests/CompositorTestServices.cs +++ b/tests/Avalonia.UnitTests/CompositorTestServices.cs @@ -211,7 +211,7 @@ public class CompositorTestServices : IDisposable public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None; - public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) + public void SetFrameThemeVariant(PlatformThemeVariant? themeVariant) { }