From 2dc808f3e86b00289911acc81ab306964ca5cf2a Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Thu, 5 Jun 2025 15:26:33 +0000 Subject: [PATCH] Fix insets on android 15 (#18844) * fix insets on android 15 * add api diff * fix nit --- api/Avalonia.nupkg.xml | 12 ++++ samples/ControlCatalog/MainView.xaml.cs | 6 +- .../SafeAreaDemo/ViewModels/MainViewModel.cs | 4 +- .../Platform/AndroidInsetsManager.cs | 68 +++++++++++++------ .../Platform/IInsetsManager.cs | 17 ++++- .../Avalonia.Browser/BrowserInsetsManager.cs | 2 +- src/iOS/Avalonia.iOS/InsetsManager.cs | 2 +- 7 files changed, 81 insertions(+), 30 deletions(-) diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml index 0be3d69b7b..79381a04d7 100644 --- a/api/Avalonia.nupkg.xml +++ b/api/Avalonia.nupkg.xml @@ -109,6 +109,18 @@ baseline/netstandard2.0/Avalonia.Controls.dll target/netstandard2.0/Avalonia.Controls.dll + + CP0006 + P:Avalonia.Controls.Platform.IInsetsManager.DisplayEdgeToEdgePreference + baseline/netstandard2.0/Avalonia.Controls.dll + target/netstandard2.0/Avalonia.Controls.dll + + + CP0006 + P:Avalonia.Controls.Platform.IInsetsManager.DisplaysEdgeToEdge + baseline/netstandard2.0/Avalonia.Controls.dll + target/netstandard2.0/Avalonia.Controls.dll + CP0009 T:Avalonia.Diagnostics.StyleDiagnostics diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs index 89bccb4475..1fefe766e2 100644 --- a/samples/ControlCatalog/MainView.xaml.cs +++ b/samples/ControlCatalog/MainView.xaml.cs @@ -108,14 +108,14 @@ namespace ControlCatalog ViewModel.SafeAreaPadding = insets.SafeAreaPadding; }; - ViewModel.DisplayEdgeToEdge = insets.DisplayEdgeToEdge; + ViewModel.DisplayEdgeToEdge = insets.DisplayEdgeToEdgePreference; ViewModel.IsSystemBarVisible = insets.IsSystemBarVisible ?? true; ViewModel.PropertyChanged += async (sender, args) => { if (args.PropertyName == nameof(ViewModel.DisplayEdgeToEdge)) { - insets.DisplayEdgeToEdge = ViewModel.DisplayEdgeToEdge; + insets.DisplayEdgeToEdgePreference = ViewModel.DisplayEdgeToEdge; } else if (args.PropertyName == nameof(ViewModel.IsSystemBarVisible)) { @@ -124,7 +124,7 @@ namespace ControlCatalog // Give the OS some time to apply new values and refresh the view model. await Task.Delay(100); - ViewModel.DisplayEdgeToEdge = insets.DisplayEdgeToEdge; + ViewModel.DisplayEdgeToEdge = insets.DisplayEdgeToEdgePreference; ViewModel.IsSystemBarVisible = insets.IsSystemBarVisible ?? true; }; } diff --git a/samples/SafeAreaDemo/ViewModels/MainViewModel.cs b/samples/SafeAreaDemo/ViewModels/MainViewModel.cs index c52536d157..4679948a6a 100644 --- a/samples/SafeAreaDemo/ViewModels/MainViewModel.cs +++ b/samples/SafeAreaDemo/ViewModels/MainViewModel.cs @@ -72,7 +72,7 @@ namespace SafeAreaDemo.ViewModels if (_insetsManager != null) { - _insetsManager.DisplayEdgeToEdge = value; + _insetsManager.DisplayEdgeToEdgePreference = value; } this.RaisePropertyChanged(); @@ -129,7 +129,7 @@ namespace SafeAreaDemo.ViewModels { _insetsManager.SafeAreaChanged += InsetsManager_SafeAreaChanged; - _displayEdgeToEdge = _insetsManager.DisplayEdgeToEdge; + _displayEdgeToEdge = _insetsManager.DisplayEdgeToEdgePreference; _hideSystemBars = !(_insetsManager.IsSystemBarVisible ?? false); } diff --git a/src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs b/src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs index 992d37ed18..2716e2057e 100644 --- a/src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs +++ b/src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs @@ -17,9 +17,12 @@ namespace Avalonia.Android.Platform { internal sealed class AndroidInsetsManager : WindowInsetsAnimationCompat.Callback, IInsetsManager, IOnApplyWindowInsetsListener, ViewTreeObserver.IOnGlobalLayoutListener, IInputPane { + // For now, we check if running under net 9. TODO: remove runtime check when we target net 10 + private static bool IsDisplayEdgeToEdgeForced = System.Environment.Version.Major >=9 && Build.VERSION.SdkInt >= (BuildVersionCodes)35; + private readonly Activity _activity; private readonly TopLevelImpl _topLevel; - private bool _displayEdgeToEdge; + private bool _displaysEdgeToEdge; private bool? _systemUiVisibility; private SystemBarTheme? _statusBarTheme; private bool? _isDefaultSystemBarLightTheme; @@ -27,6 +30,7 @@ namespace Avalonia.Android.Platform private InputPaneState _state; private Rect _previousRect; private Insets? _previousImeInset; + private bool _displayEdgeToEdgePreference; private readonly bool _usesLegacyLayouts; private AndroidWindow Window => _activity.Window ?? throw new InvalidOperationException("Activity.Window must be set."); @@ -50,29 +54,42 @@ namespace Avalonia.Android.Platform } } - public bool DisplayEdgeToEdge + public bool DisplayEdgeToEdgePreference { - get => _displayEdgeToEdge; + get => _displayEdgeToEdgePreference; set { - _displayEdgeToEdge = value; + _displayEdgeToEdgePreference = value; - if (OperatingSystem.IsAndroidVersionAtLeast(28) && Window.Attributes is { } attributes) - { - attributes.LayoutInDisplayCutoutMode = value ? LayoutInDisplayCutoutMode.ShortEdges : LayoutInDisplayCutoutMode.Default; - } + UpdateDisplayEdgeToEgdeState(); + } + } - WindowCompat.SetDecorFitsSystemWindows(Window, !value); + private void UpdateDisplayEdgeToEgdeState() + { + if (IsDisplayEdgeToEdgeForced) + { + _displaysEdgeToEdge = true; + return; + } - if (value) - { - Window.AddFlags(WindowManagerFlags.TranslucentStatus); - Window.AddFlags(WindowManagerFlags.TranslucentNavigation); - } - else - { - SystemBarColor = _systemBarColor; - } + _displaysEdgeToEdge = _displayEdgeToEdgePreference; + + if (OperatingSystem.IsAndroidVersionAtLeast(28) && Window.Attributes is { } attributes) + { + attributes.LayoutInDisplayCutoutMode = _displayEdgeToEdgePreference ? LayoutInDisplayCutoutMode.ShortEdges : LayoutInDisplayCutoutMode.Default; + } + + WindowCompat.SetDecorFitsSystemWindows(Window, !_displayEdgeToEdgePreference); + + if (_displayEdgeToEdgePreference) + { + Window.AddFlags(WindowManagerFlags.TranslucentStatus); + Window.AddFlags(WindowManagerFlags.TranslucentNavigation); + } + else + { + SystemBarColor = _systemBarColor; } } @@ -89,7 +106,7 @@ namespace Avalonia.Android.Platform _activity.Window?.DecorView.ViewTreeObserver?.AddOnGlobalLayoutListener(this); } - DisplayEdgeToEdge = false; + DisplayEdgeToEdgePreference = false; ViewCompat.SetWindowInsetsAnimationCallback(Window.DecorView, this); } @@ -105,11 +122,11 @@ namespace Avalonia.Android.Platform var renderScaling = _topLevel.RenderScaling; var inset = insets.GetInsets( - _displayEdgeToEdge ? + DisplaysEdgeToEdge ? WindowInsetsCompat.Type.StatusBars() | WindowInsetsCompat.Type.NavigationBars() | WindowInsetsCompat.Type.DisplayCutout() : 0); - return new Thickness(inset.Left / renderScaling, + return new Thickness(inset.Left / renderScaling, inset.Top / renderScaling, inset.Right / renderScaling, inset.Bottom / renderScaling); @@ -264,7 +281,10 @@ namespace Avalonia.Android.Platform { _systemBarColor = value; - if (_systemBarColor is { } color && !_displayEdgeToEdge && _activity.Window != null) + if (IsDisplayEdgeToEdgeForced) + return; + + if (_systemBarColor is { } color && !_displaysEdgeToEdge && _activity.Window != null) { _activity.Window.ClearFlags(WindowManagerFlags.TranslucentStatus); _activity.Window.ClearFlags(WindowManagerFlags.TranslucentNavigation); @@ -282,6 +302,10 @@ namespace Avalonia.Android.Platform } } + public bool DisplayEdgeToEdge { get => DisplaysEdgeToEdge; set => DisplayEdgeToEdgePreference = value; } + + public bool DisplaysEdgeToEdge => _displaysEdgeToEdge; + internal void ApplyStatusBarState() { IsSystemBarVisible = _systemUiVisibility; diff --git a/src/Avalonia.Controls/Platform/IInsetsManager.cs b/src/Avalonia.Controls/Platform/IInsetsManager.cs index 94a166a9ae..fb68e980f4 100644 --- a/src/Avalonia.Controls/Platform/IInsetsManager.cs +++ b/src/Avalonia.Controls/Platform/IInsetsManager.cs @@ -17,8 +17,20 @@ namespace Avalonia.Controls.Platform /// /// Gets or sets whether the window draws edge to edge. behind any visible system bars. /// + bool DisplayEdgeToEdgePreference { get; set; } + + + /// + /// Gets or sets whether the window draws edge to edge. behind any visible system bars. + /// + [Obsolete("Use DisplayEdgeToEdgePreference")] bool DisplayEdgeToEdge { get; set; } + /// + /// Gets whether the window is currently displaying edge to edge. + /// + bool DisplaysEdgeToEdge { get; } + /// /// Gets the current safe area padding. /// @@ -39,9 +51,12 @@ namespace Avalonia.Controls.Platform public abstract class InsetsManagerBase : IInsetsManager { public virtual bool? IsSystemBarVisible { get; set; } - public virtual bool DisplayEdgeToEdge { get; set; } + public virtual bool DisplayEdgeToEdgePreference { get; set; } + public virtual bool DisplayEdgeToEdge { get => DisplaysEdgeToEdge; set => DisplayEdgeToEdgePreference = value; } public virtual Thickness SafeAreaPadding { get; protected set; } public virtual Color? SystemBarColor { get; set; } + public virtual bool DisplaysEdgeToEdge => DisplayEdgeToEdgePreference; + public event EventHandler? SafeAreaChanged; protected void OnSafeAreaChanged(SafeAreaChangedArgs eventArgs) diff --git a/src/Browser/Avalonia.Browser/BrowserInsetsManager.cs b/src/Browser/Avalonia.Browser/BrowserInsetsManager.cs index dd34f744c1..4c042c5361 100644 --- a/src/Browser/Avalonia.Browser/BrowserInsetsManager.cs +++ b/src/Browser/Avalonia.Browser/BrowserInsetsManager.cs @@ -18,7 +18,7 @@ namespace Avalonia.Browser } } - public override bool DisplayEdgeToEdge { get; set; } + public override bool DisplayEdgeToEdgePreference { get; set; } public override Thickness SafeAreaPadding { diff --git a/src/iOS/Avalonia.iOS/InsetsManager.cs b/src/iOS/Avalonia.iOS/InsetsManager.cs index 105e208ee9..170a669841 100644 --- a/src/iOS/Avalonia.iOS/InsetsManager.cs +++ b/src/iOS/Avalonia.iOS/InsetsManager.cs @@ -35,7 +35,7 @@ internal class InsetsManager : InsetsManagerBase } public event EventHandler? DisplayEdgeToEdgeChanged; - public override bool DisplayEdgeToEdge + public override bool DisplayEdgeToEdgePreference { get => _displayEdgeToEdge; set