diff --git a/MauiAppEdgeToEdge/MauiAppEdgeToEdge/MauiAppEdgeToEdge.csproj b/MauiAppEdgeToEdge/MauiAppEdgeToEdge/MauiAppEdgeToEdge.csproj index 79fecfa..4351db3 100644 --- a/MauiAppEdgeToEdge/MauiAppEdgeToEdge/MauiAppEdgeToEdge.csproj +++ b/MauiAppEdgeToEdge/MauiAppEdgeToEdge/MauiAppEdgeToEdge.csproj @@ -1,7 +1,7 @@  - net8.0-android + net9.0-android Exe MauiAppEdgeToEdge @@ -24,8 +24,8 @@ - - + + diff --git a/MauiAppEdgeToEdge/MauiAppEdgeToEdge/MauiProgram.cs b/MauiAppEdgeToEdge/MauiAppEdgeToEdge/MauiProgram.cs index 3f58def..9e09a53 100644 --- a/MauiAppEdgeToEdge/MauiAppEdgeToEdge/MauiProgram.cs +++ b/MauiAppEdgeToEdge/MauiAppEdgeToEdge/MauiProgram.cs @@ -1,4 +1,13 @@ -namespace MauiAppEdgeToEdge +#if ANDROID +using Android.OS; +using AndroidX.Activity; +using AndroidX.Fragment.App; + +#endif +using Microsoft.Maui.LifecycleEvents; +using Microsoft.Maui.Platform; + +namespace MauiAppEdgeToEdge { public static class MauiProgram { @@ -6,9 +15,52 @@ public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); builder - .UseMauiApp(); + .UseMauiApp() + .ConfigureLifecycleEvents(lifecycleBuilder => + { +#if ANDROID + lifecycleBuilder.AddAndroid(androidLifecycleBuilder => + { + androidLifecycleBuilder.OnCreate((activity, savedInstance) => + { + if (activity is ComponentActivity componentActivity) + { + // Enable Edge to Edge for the activity + EdgeToEdge.Enable(componentActivity); + + // Also wire up a fragment lifecycle callback so we can enable edge to edge on fragments + componentActivity.GetFragmentManager()?.RegisterFragmentLifecycleCallbacks(new MyFragmentLifecycleCallbacks((fragmentManager, fragment) => + { + // Modals in MAUI in NET9 use DialogFragment + if (fragment is AndroidX.Fragment.App.DialogFragment dialogFragment && dialogFragment.Activity is not null) + { + dialogFragment.Dialog?.Window?.EnableEdgeToEdge(dialogFragment.Dialog.Window.DecorView!.Resources!); + + // These don't seem to work, perhaps it's not getting the correct activity still + // if (dialogFragment.Dialog.OwnerActivity is ComponentActivity dfComponentActivity) + // EdgeToEdge.Enable(dfComponentActivity); + //EdgeToEdge.Enable(dialogFragment.Activity); + } + + }), false); + } + }); + }); +#endif + }); return builder.Build(); } } } + +#if ANDROID +public class MyFragmentLifecycleCallbacks(Action onFragmentStarted) : AndroidX.Fragment.App.FragmentManager.FragmentLifecycleCallbacks +{ + public override void OnFragmentStarted(AndroidX.Fragment.App.FragmentManager fm, AndroidX.Fragment.App.Fragment f) + { + onFragmentStarted?.Invoke(fm, f); + base.OnFragmentStarted(fm, f); + } +} +#endif \ No newline at end of file diff --git a/MauiAppEdgeToEdge/MauiAppEdgeToEdge/Platforms/Android/EdgeToEdgeExtensions.cs b/MauiAppEdgeToEdge/MauiAppEdgeToEdge/Platforms/Android/EdgeToEdgeExtensions.cs new file mode 100644 index 0000000..5fb3cc2 --- /dev/null +++ b/MauiAppEdgeToEdge/MauiAppEdgeToEdge/Platforms/Android/EdgeToEdgeExtensions.cs @@ -0,0 +1,236 @@ +using Android.Content.Res; +using Android.Views; +using AndroidX.Core.View; +using System; + +namespace MauiAppEdgeToEdge; + +internal static class ActivityExtensions +{ + static global::Android.Graphics.Color DefaultLightScrim = global::Android.Graphics.Color.Argb(0xe6, 0xFF, 0xFF, 0xFF); + static global::Android.Graphics.Color DefaultDarkScrim = global::Android.Graphics.Color.Argb(0x80, 0x1b, 0x1b, 0x1b); + + + public static void EnableEdgeToEdge(this global::Android.App.Activity activity) + => activity.EnableEdgeToEdge( + SystemBarStyle.Auto(global::Android.Graphics.Color.Transparent, global::Android.Graphics.Color.Transparent), + SystemBarStyle.Auto(DefaultLightScrim, DefaultDarkScrim)); + + public static void EnableEdgeToEdge(this global::Android.App.Activity activity, SystemBarStyle statusBarStyle, SystemBarStyle navigationBarStyle) + => activity.Window?.EnableEdgeToEdge(activity.Resources!, statusBarStyle, navigationBarStyle); + + public static void EnableEdgeToEdge(this global::Android.Views.Window window, global::Android.Content.Res.Resources resources) + => window.EnableEdgeToEdge(resources, + SystemBarStyle.Auto(global::Android.Graphics.Color.Transparent, global::Android.Graphics.Color.Transparent), + SystemBarStyle.Auto(DefaultLightScrim, DefaultDarkScrim)); + + public static void EnableEdgeToEdge(this global::Android.Views.Window window, global::Android.Content.Res.Resources resources, SystemBarStyle statusBarStyle, SystemBarStyle navigationBarStyle) + { + var res = resources; + var view = window!.DecorView; + var statusBarIsDark = statusBarStyle.DetectDarkMode(res!); + var navigationBarIsDark = navigationBarStyle.DetectDarkMode(res!); + + IEdgeToEdge impl; + + if (OperatingSystem.IsAndroidVersionAtLeast(30)) + impl = new EdgeToEdge30(); + else if (OperatingSystem.IsAndroidVersionAtLeast(29)) + impl = new EdgeToEdgeApi29(); + else if (OperatingSystem.IsAndroidVersionAtLeast(28)) + impl = new EdgeToEdgeApi28(); + else if (OperatingSystem.IsAndroidVersionAtLeast(26)) + impl = new EdgeToEdgeApi26(); + else if (OperatingSystem.IsAndroidVersionAtLeast(23)) + impl = new EdgeToEdgeApi23(); + else if (OperatingSystem.IsAndroidVersionAtLeast(21)) + impl = new EdgeToEdgeApi21(); + else + impl = new EdgeToEdgeBase(); + + impl.Setup(statusBarStyle, navigationBarStyle, window, view, statusBarIsDark, navigationBarIsDark); + + impl.AdjustLayoutInDisplayCutoutMode(window!); + } +} + + +internal class SystemBarStyle +{ + public SystemBarStyle(global::Android.Graphics.Color lightScrim, global::Android.Graphics.Color darkScrim, UiMode nightMode, Func detectDarkMode) + { + LightScrim = lightScrim; + DarkScrim = darkScrim; + NightMode = nightMode; + DetectDarkMode = detectDarkMode; + } + + public static SystemBarStyle Auto(global::Android.Graphics.Color lightScrim, global::Android.Graphics.Color darkScrim) + => new SystemBarStyle(lightScrim, darkScrim, UiMode.NightUndefined, res => + (res.Configuration!.UiMode & UiMode.NightMask) == UiMode.NightYes); + + public static SystemBarStyle Dark(global::Android.Graphics.Color scrim) + => new SystemBarStyle(scrim, scrim, UiMode.NightYes, res => true); + + public static SystemBarStyle Light(global::Android.Graphics.Color scrim, global::Android.Graphics.Color darkScrim) + => new SystemBarStyle(scrim, darkScrim, UiMode.NightNo, res => false); + + public global::Android.Graphics.Color LightScrim { get; private set; } + public global::Android.Graphics.Color DarkScrim { get; private set; } + + public UiMode NightMode { get; private set; } + + public Func DetectDarkMode { get; private set; } + + public global::Android.Graphics.Color GetScrim(bool isDark) + { + return isDark ? DarkScrim : LightScrim; + } + + public global::Android.Graphics.Color GetScrimWithEnforcedContrast(bool isDark) + { + if (NightMode == UiMode.NightUndefined) + return global::Android.Graphics.Color.Transparent; + + if (isDark) + return DarkScrim; + + return LightScrim; + } +} + +internal interface IEdgeToEdge +{ + void Setup( + SystemBarStyle statusBarStyle, + SystemBarStyle navigationBarStyle, + global::Android.Views.Window window, + global::Android.Views.View view, + bool statusBarIsDark, + bool navigationBarIsDark) + { + // No edge to edge, pre SDK 21 + } + + void AdjustLayoutInDisplayCutoutMode(global::Android.Views.Window window) + { + // No display cutout before SDK 28 + } +} + +internal class EdgeToEdgeBase : IEdgeToEdge +{ + public virtual void Setup( + SystemBarStyle statusBarStyle, + SystemBarStyle navigationBarStyle, + global::Android.Views.Window window, + global::Android.Views.View view, + bool statusBarIsDark, + bool navigationBarIsDark) + { + // No edge to edge, pre SDK 21 + } + + public virtual void AdjustLayoutInDisplayCutoutMode(global::Android.Views.Window window) + { + // No display cutout before SDK 28 + } +} + +internal class EdgeToEdgeApi21 : EdgeToEdgeBase +{ + public override void Setup( + SystemBarStyle statusBarStyle, + SystemBarStyle navigationBarStyle, + global::Android.Views.Window window, + global::Android.Views.View view, + bool statusBarIsDark, + bool navigationBarIsDark) + { + WindowCompat.SetDecorFitsSystemWindows(window, false); + window.AddFlags(WindowManagerFlags.TranslucentStatus); + window.AddFlags(WindowManagerFlags.TranslucentNavigation); + } +} + +internal class EdgeToEdgeApi23 : EdgeToEdgeBase +{ + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1422:Validate platform compatibility", Justification = "")] + public override void Setup( + SystemBarStyle statusBarStyle, + SystemBarStyle navigationBarStyle, + global::Android.Views.Window window, + global::Android.Views.View view, + bool statusBarIsDark, + bool navigationBarIsDark) + { + WindowCompat.SetDecorFitsSystemWindows(window, false); + window.SetStatusBarColor(statusBarStyle.GetScrim(statusBarIsDark)); + window.SetNavigationBarColor(navigationBarStyle.DarkScrim); + new WindowInsetsControllerCompat(window, view).AppearanceLightStatusBars = !statusBarIsDark; + } +} + +internal class EdgeToEdgeApi26 : EdgeToEdgeBase +{ + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1422:Validate platform compatibility", Justification = "")] + public override void Setup( + SystemBarStyle statusBarStyle, + SystemBarStyle navigationBarStyle, + global::Android.Views.Window window, + global::Android.Views.View view, + bool statusBarIsDark, + bool navigationBarIsDark) + { + WindowCompat.SetDecorFitsSystemWindows(window, false); + window.SetStatusBarColor(statusBarStyle.GetScrim(statusBarIsDark)); + window.SetNavigationBarColor(navigationBarStyle.GetScrim(navigationBarIsDark)); + var c = new WindowInsetsControllerCompat(window, view); + c.AppearanceLightStatusBars = !statusBarIsDark; + c.AppearanceLightNavigationBars = !navigationBarIsDark; + } +} + +internal class EdgeToEdgeApi28 : EdgeToEdgeApi26 +{ + public override void AdjustLayoutInDisplayCutoutMode(global::Android.Views.Window window) + { + if (OperatingSystem.IsAndroidVersionAtLeast(28)) + window.Attributes!.LayoutInDisplayCutoutMode = LayoutInDisplayCutoutMode.ShortEdges; + } +} + +internal class EdgeToEdgeApi29 : EdgeToEdgeApi28 +{ + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1422:Validate platform compatibility", Justification = "")] + public override void Setup( + SystemBarStyle statusBarStyle, + SystemBarStyle navigationBarStyle, + global::Android.Views.Window window, + global::Android.Views.View view, + bool statusBarIsDark, + bool navigationBarIsDark) + { + if (OperatingSystem.IsAndroidVersionAtLeast(29)) + { + WindowCompat.SetDecorFitsSystemWindows(window, false); + window.SetStatusBarColor(statusBarStyle.GetScrimWithEnforcedContrast(statusBarIsDark)); + window.SetNavigationBarColor(navigationBarStyle.GetScrimWithEnforcedContrast(navigationBarIsDark)); + window.StatusBarContrastEnforced = false; + window.NavigationBarContrastEnforced = navigationBarStyle.NightMode == UiMode.NightUndefined; + + var c = new WindowInsetsControllerCompat(window, view); + c.AppearanceLightStatusBars = !statusBarIsDark; + c.AppearanceLightNavigationBars = !navigationBarIsDark; + } + } +} + +internal class EdgeToEdge30 : EdgeToEdgeApi29 +{ + public override void AdjustLayoutInDisplayCutoutMode(global::Android.Views.Window window) + { + if (OperatingSystem.IsAndroidVersionAtLeast(30)) + window.Attributes!.LayoutInDisplayCutoutMode = LayoutInDisplayCutoutMode.Always; + } +} \ No newline at end of file diff --git a/MauiAppEdgeToEdge/MauiAppEdgeToEdge/Platforms/Android/MainActivity.cs b/MauiAppEdgeToEdge/MauiAppEdgeToEdge/Platforms/Android/MainActivity.cs index 0305c42..9434589 100644 --- a/MauiAppEdgeToEdge/MauiAppEdgeToEdge/Platforms/Android/MainActivity.cs +++ b/MauiAppEdgeToEdge/MauiAppEdgeToEdge/Platforms/Android/MainActivity.cs @@ -3,21 +3,25 @@ using Android.OS; using AndroidX.Activity; using AndroidX.Core.View; +using AndroidX.Fragment.App; +using Google.Android.Material.AppBar; namespace MauiAppEdgeToEdge { [Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)] public class MainActivity : MauiAppCompatActivity { - protected override void OnCreate(Bundle? savedInstanceState) - { - base.OnCreate(savedInstanceState); + //protected override void OnCreate(Bundle? savedInstanceState) + //{ + // base.OnCreate(savedInstanceState); - EdgeToEdge.Enable(this); + // var appBarLayout = FindViewById(Resource.Id.navigationlayout_appbar); - //WindowCompat.SetDecorFitsSystemWindows(Window, false); - //Window.SetStatusBarColor(global::Android.Graphics.Color.Transparent); - //Window.SetNavigationBarColor(global::Android.Graphics.Color.Transparent); - } - } + // appBarLayout?.SetFitsSystemWindows(true); + + // var contentLayout = FindViewById(Resource.Id.navigationlayout_content); + + // contentLayout?.SetFitsSystemWindows(true); + //} + } } diff --git a/MauiAppEdgeToEdge/global.json b/MauiAppEdgeToEdge/global.json new file mode 100644 index 0000000..ab51b21 --- /dev/null +++ b/MauiAppEdgeToEdge/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "8.0.400", + "allowPrerelease": true, + "rollForward": "latestMajor" + } +} \ No newline at end of file