@ -2,53 +2,68 @@
using System.Collections.Generic ;
using Android.OS ;
using Android.Views ;
using Android.Views.Animations ;
using AndroidX.Core.View ;
using Avalonia.Android.Platform.SkiaPlatform ;
using Avalonia.Animation.Easings ;
using Avalonia.Controls.Platform ;
using Avalonia.Media ;
using AndroidWindow = Android . Views . Window ;
namespace Avalonia.Android.Platform
{
internal class AndroidInsetsManager : Java . Lang . Object , IInsetsManager , IOnApplyWindowInsetsListener , ViewTreeObserver . IOnGlobalLayoutListener
internal sealed class AndroidInsetsManager : WindowInsetsAnimationCompat . Callback , IInsetsManager , IOnApplyWindowInsetsListener , ViewTreeObserver . IOnGlobalLayoutListener , IInputPane
{
private readonly AvaloniaMainActivity _ activity ;
private readonly TopLevelImpl _ topLevel ;
private readonly InsetsAnimationCallback _ callback ;
private bool _d isplayEdgeToEdge ;
private bool _ usesLegacyLayouts ;
private bool? _ systemUiVisibility ;
private SystemBarTheme ? _ statusBarTheme ;
private bool? _ isDefaultSystemBarLightTheme ;
private Color ? _ systemBarColor ;
private InputPaneState _ state ;
private Rect _ previousRect ;
private readonly bool _ usesLegacyLayouts ;
private AndroidWindow Window = > _ activity . Window ? ? throw new InvalidOperationException ( "Activity.Window must be set." ) ;
public event EventHandler < SafeAreaChangedArgs > SafeAreaChanged ;
public event EventHandler < InputPaneStateEventArgs > StateChanged ;
public InputPaneState State
{
get = > _ state ; set
{
var oldState = _ state ;
_ state = value ;
if ( oldState ! = value & & Build . VERSION . SdkInt < = BuildVersionCodes . Q )
{
var currentRect = OccludedRect ;
StateChanged ? . Invoke ( this , new InputPaneStateEventArgs ( value , _ previousRect , currentRect , TimeSpan . Zero , null ) ) ;
_ previousRect = currentRect ;
}
}
}
public bool DisplayEdgeToEdge
{
get = > _d isplayEdgeToEdge ;
get = > _d isplayEdgeToEdge ;
set
{
_d isplayEdgeToEdge = value ;
var window = _ activity . Window ;
if ( OperatingSystem . IsAndroidVersionAtLeast ( 2 8 ) & & window ? . Attributes is { } attributes )
if ( OperatingSystem . IsAndroidVersionAtLeast ( 2 8 ) & & Window . Attributes is { } attributes )
{
attributes . LayoutInDisplayCutoutMode = value ? LayoutInDisplayCutoutMode . ShortEdges : LayoutInDisplayCutoutMode . Default ;
}
if ( window is not null )
{
WindowCompat . SetDecorFitsSystemWindows ( _ activity . Window , ! value ) ;
}
WindowCompat . SetDecorFitsSystemWindows ( Window , ! value ) ;
if ( value )
if ( value )
{
if ( window is not null )
{
window . AddFlags ( WindowManagerFlags . TranslucentStatus ) ;
window . AddFlags ( WindowManagerFlags . TranslucentNavigation ) ;
}
Window . AddFlags ( WindowManagerFlags . TranslucentStatus ) ;
Window . AddFlags ( WindowManagerFlags . TranslucentNavigation ) ;
}
else
{
@ -57,20 +72,12 @@ namespace Avalonia.Android.Platform
}
}
public AndroidInsetsManager ( AvaloniaMainActivity activity , TopLevelImpl topLevel )
internal AndroidInsetsManager ( AvaloniaMainActivity activity , TopLevelImpl topLevel ) : base ( DispatchModeStop )
{
_ activity = activity ;
_ topLevel = topLevel ;
_ callback = new InsetsAnimationCallback ( WindowInsetsAnimationCompat . Callback . DispatchModeStop ) ;
_ callback . InsetsManager = this ;
if ( _ activity . Window is { } window )
{
ViewCompat . SetOnApplyWindowInsetsListener ( window . DecorView , this ) ;
ViewCompat . SetWindowInsetsAnimationCallback ( window . DecorView , _ callback ) ;
}
ViewCompat . SetOnApplyWindowInsetsListener ( Window . DecorView , this ) ;
if ( Build . VERSION . SdkInt < BuildVersionCodes . R )
{
@ -79,32 +86,48 @@ namespace Avalonia.Android.Platform
}
DisplayEdgeToEdge = false ;
ViewCompat . SetWindowInsetsAnimationCallback ( Window . DecorView , this ) ;
}
public Thickness SafeAreaPadding
{
get
{
var insets = _ activity . Window is { } window ? ViewCompat . GetRootWindowInsets ( w indow. DecorView ) : null ;
var insets = ViewCompat . GetRootWindowInsets ( W indow. DecorView ) ;
if ( insets ! = null )
{
var renderScaling = _ topLevel . RenderScaling ;
var inset = insets . GetInsets (
( _d isplayEdgeToEdge ?
_d isplayEdgeToEdge ?
WindowInsetsCompat . Type . StatusBars ( ) | WindowInsetsCompat . Type . NavigationBars ( ) |
WindowInsetsCompat . Type . DisplayCutout ( ) :
0 ) | WindowInsetsCompat . Type . Ime ( ) ) ;
var navBarInset = insets . GetInsets ( WindowInsetsCompat . Type . NavigationBars ( ) ) ;
var imeInset = insets . GetInsets ( WindowInsetsCompat . Type . Ime ( ) ) ;
WindowInsetsCompat . Type . DisplayCutout ( ) : 0 ) ;
return new Thickness ( inset . Left / renderScaling ,
inset . Top / renderScaling ,
inset . Right / renderScaling ,
( imeInset . Bottom > 0 & & ( ( _ usesLegacyLayouts & & ! _d isplayEdgeToEdge ) | | ! _ usesLegacyLayouts ) ?
imeInset . Bottom - ( _d isplayEdgeToEdge ? 0 : navBarInset . Bottom ) :
inset . Bottom ) / renderScaling ) ;
inset . Bottom / renderScaling ) ;
}
return default ;
}
}
public Rect OccludedRect
{
get
{
var insets = ViewCompat . GetRootWindowInsets ( Window . DecorView ) ;
if ( insets ! = null )
{
var navbarInset = insets . GetInsets ( WindowInsetsCompat . Type . NavigationBars ( ) ) . Bottom ;
var height = Math . Max ( ( float ) ( ( insets . GetInsets ( WindowInsetsCompat . Type . Ime ( ) ) . Bottom - navbarInset ) / _ topLevel . RenderScaling ) , 0 ) ;
return new Rect ( 0 , _ topLevel . ClientSize . Height - SafeAreaPadding . Bottom - height , _ topLevel . ClientSize . Width , height ) ;
}
return default ;
@ -113,8 +136,16 @@ namespace Avalonia.Android.Platform
public WindowInsetsCompat OnApplyWindowInsets ( View v , WindowInsetsCompat insets )
{
NotifySafeAreaChanged ( SafeAreaPadding ) ;
insets = ViewCompat . OnApplyWindowInsets ( v , insets ) ;
NotifySafeAreaChanged ( SafeAreaPadding ) ;
if ( _ previousRect = = default )
{
_ previousRect = OccludedRect ;
}
State = insets . IsVisible ( WindowInsetsCompat . Type . Ime ( ) ) ? InputPaneState . Open : InputPaneState . Closed ;
return insets ;
}
@ -126,6 +157,12 @@ namespace Avalonia.Android.Platform
public void OnGlobalLayout ( )
{
NotifySafeAreaChanged ( SafeAreaPadding ) ;
if ( _ usesLegacyLayouts )
{
var insets = ViewCompat . GetRootWindowInsets ( Window . DecorView ) ;
State = insets ? . IsVisible ( WindowInsetsCompat . Type . Ime ( ) ) = = true ? InputPaneState . Open : InputPaneState . Closed ;
}
}
public SystemBarTheme ? SystemBarTheme
@ -134,7 +171,7 @@ namespace Avalonia.Android.Platform
{
try
{
var compat = new WindowInsetsControllerCompat ( _ activity . Window , _ topLevel . View ) ;
var compat = new WindowInsetsControllerCompat ( Window , _ topLevel . View ) ;
return compat . AppearanceLightStatusBars ? Controls . Platform . SystemBarTheme . Light : Controls . Platform . SystemBarTheme . Dark ;
}
@ -152,7 +189,7 @@ namespace Avalonia.Android.Platform
return ;
}
var compat = new WindowInsetsControllerCompat ( _ activity . Window , _ topLevel . View ) ;
var compat = new WindowInsetsControllerCompat ( Window , _ topLevel . View ) ;
if ( _ isDefaultSystemBarLightTheme = = null )
{
@ -161,7 +198,7 @@ namespace Avalonia.Android.Platform
if ( value = = null )
{
value = ( bool ) _ isDefaultSystemBarLightTheme ? Controls . Platform . SystemBarTheme . Light : Controls . Platform . SystemBarTheme . Dark ;
value = _ isDefaultSystemBarLightTheme . Valu e ? Controls . Platform . SystemBarTheme . Light : Controls . Platform . SystemBarTheme . Dark ;
}
compat . AppearanceLightStatusBars = value = = Controls . Platform . SystemBarTheme . Light ;
@ -173,7 +210,7 @@ namespace Avalonia.Android.Platform
{
get
{
if ( _ activity . Window = = null )
if ( _ activity . Window = = null )
{
return true ;
}
@ -190,7 +227,7 @@ namespace Avalonia.Android.Platform
return ;
}
var compat = WindowCompat . GetInsetsController ( _ activity . Window , _ topLevel . View ) ;
var compat = WindowCompat . GetInsetsController ( Window , _ topLevel . View ) ;
if ( value = = null | | value . Value )
{
@ -210,7 +247,7 @@ namespace Avalonia.Android.Platform
public Color ? SystemBarColor
{
get = > _ systemBarColor ;
get = > _ systemBarColor ;
set
{
_ systemBarColor = value ;
@ -240,40 +277,48 @@ namespace Avalonia.Android.Platform
SystemBarColor = _ systemBarColor ;
}
private class InsetsAnimationCallback : WindowInsetsAnimationCompat . Callback
public override WindowInsetsAnimationCompat . BoundsCompat OnStart ( WindowInsetsAnimationCompat animation , WindowInsetsAnimationCompat . BoundsCompat bounds )
{
public InsetsAnimationCallback ( int dispatchMode ) : base ( dispatchMode )
if ( ( animation . TypeMask & WindowInsetsCompat . Type . Ime ( ) ) ! = 0 )
{
}
public AndroidInsetsManager InsetsManager { get ; set ; }
var insets = ViewCompat . GetRootWindowInsets ( Window . DecorView ) ;
public override WindowInsetsCompat OnProgress ( WindowInsetsCompat insets , IList < WindowInsetsAnimationCompat > runningAnimations )
{
foreach ( var anim in runningAnimations )
if ( insets ! = null )
{
if ( ( anim . TypeMask & WindowInsetsCompat . Type . Ime ( ) ) ! = 0 )
{
var renderScaling = InsetsManager . _ topLevel . RenderScaling ;
var inset = insets . GetInsets ( ( InsetsManager . DisplayEdgeToEdge ? WindowInsetsCompat . Type . StatusBars ( ) | WindowInsetsCompat . Type . NavigationBars ( ) | WindowInsetsCompat . Type . DisplayCutout ( ) : 0 ) | WindowInsetsCompat . Type . Ime ( ) ) ;
var navBarInset = insets . GetInsets ( WindowInsetsCompat . Type . NavigationBars ( ) ) ;
var imeInset = insets . GetInsets ( WindowInsetsCompat . Type . Ime ( ) ) ;
var navbarInset = insets . GetInsets ( WindowInsetsCompat . Type . NavigationBars ( ) ) . Bottom ;
var height = Math . Max ( 0 , ( float ) ( ( bounds . LowerBound . Bottom - navbarInset ) / _ topLevel . RenderScaling ) ) ;
var upperRect = new Rect ( 0 , _ topLevel . ClientSize . Height - SafeAreaPadding . Bottom - height , _ topLevel . ClientSize . Width , height ) ;
height = Math . Max ( 0 , ( float ) ( ( bounds . UpperBound . Bottom - navbarInset ) / _ topLevel . RenderScaling ) ) ;
var lowerRect = new Rect ( 0 , _ topLevel . ClientSize . Height - SafeAreaPadding . Bottom - height , _ topLevel . ClientSize . Width , height ) ;
var duration = TimeSpan . FromMilliseconds ( animation . DurationMillis ) ;
var bottomPadding = ( imeInset . Bottom > 0 & & ! InsetsManager . DisplayEdgeToEdge ? imeInset . Bottom - navBarInset . Bottom : inset . Bottom ) ;
bottomPadding = ( int ) ( bottomPadding * anim . InterpolatedFraction ) ;
var padding = new Thickness ( inset . Left / renderScaling ,
inset . Top / renderScaling ,
inset . Right / renderScaling ,
bottomPadding / renderScaling ) ;
InsetsManager ? . NotifySafeAreaChanged ( padding ) ;
break ;
}
bool isOpening = State = = InputPaneState . Open ;
StateChanged ? . Invoke ( this , new InputPaneStateEventArgs ( State , isOpening ? upperRect : lowerRect , isOpening ? lowerRect : upperRect , duration , new AnimationEasing ( animation . Interpolator ) ) ) ;
}
return insets ;
}
return base . OnStart ( animation , bounds ) ;
}
public override WindowInsetsCompat OnProgress ( WindowInsetsCompat insets , IList < WindowInsetsAnimationCompat > runningAnimations )
{
return insets ;
}
}
internal sealed class AnimationEasing : Easing
{
private readonly IInterpolator _ interpolator ;
public AnimationEasing ( IInterpolator interpolator )
{
_ interpolator = interpolator ;
}
public override double Ease ( double progress )
{
return _ interpolator . GetInterpolation ( ( float ) progress ) ;
}
}
}