Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix statusBar color changes on modal pages #2413

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Android.Content;
using Android.Views;

namespace CommunityToolkit.Maui.Core;

public interface IDialogFragmentService
{
void OnFragmentAttached(AndroidX.Fragment.App.FragmentManager fm, AndroidX.Fragment.App.Fragment f, Context context);
void OnFragmentCreated(AndroidX.Fragment.App.FragmentManager fm, AndroidX.Fragment.App.Fragment f, Bundle? savedInstanceState);
void OnFragmentDestroyed(AndroidX.Fragment.App.FragmentManager fm, AndroidX.Fragment.App.Fragment f);
void OnFragmentDetached(AndroidX.Fragment.App.FragmentManager fm, AndroidX.Fragment.App.Fragment f);
void OnFragmentPaused(AndroidX.Fragment.App.FragmentManager fm, AndroidX.Fragment.App.Fragment f);
void OnFragmentPreAttached(AndroidX.Fragment.App.FragmentManager fm, AndroidX.Fragment.App.Fragment f, Context context);
void OnFragmentPreCreated(AndroidX.Fragment.App.FragmentManager fm, AndroidX.Fragment.App.Fragment f, Bundle? savedInstanceState);
void OnFragmentResumed(AndroidX.Fragment.App.FragmentManager fm, AndroidX.Fragment.App.Fragment f);
void OnFragmentSaveInstanceState(AndroidX.Fragment.App.FragmentManager fm, AndroidX.Fragment.App.Fragment f, Bundle outState);
void OnFragmentStarted(AndroidX.Fragment.App.FragmentManager fm, AndroidX.Fragment.App.Fragment f);
void OnFragmentStopped(AndroidX.Fragment.App.FragmentManager fm, AndroidX.Fragment.App.Fragment f);
void OnFragmentViewCreated(AndroidX.Fragment.App.FragmentManager fm, AndroidX.Fragment.App.Fragment f, View v, Bundle? savedInstanceState);
void OnFragmentViewDestroyed(AndroidX.Fragment.App.FragmentManager fm, AndroidX.Fragment.App.Fragment f);
}
30 changes: 29 additions & 1 deletion src/CommunityToolkit.Maui/AppBuilderExtensions.shared.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
using CommunityToolkit.Maui.Core;
#if ANDROID
using CommunityToolkit.Maui.Services;
#endif
using CommunityToolkit.Maui.Core;
using CommunityToolkit.Maui.Core.Handlers;
using CommunityToolkit.Maui.PlatformConfiguration.AndroidSpecific;
using CommunityToolkit.Maui.Views;
using Microsoft.Maui.LifecycleEvents;
using Microsoft.Maui.Platform;

namespace CommunityToolkit.Maui;

Expand All @@ -26,6 +31,29 @@ public static MauiAppBuilder UseMauiCommunityToolkit(this MauiAppBuilder builder
// Invokes options for both `CommunityToolkit.Maui` and `CommunityToolkit.Maui.Core`
options?.Invoke(new Options(builder));

#if ANDROID
if (Options.ShouldUseMCTDialogFragment)
{
builder.Services.AddSingleton<IDialogFragmentService, DialogFragmentService>();
}

builder.ConfigureLifecycleEvents(builder =>
{
builder.AddAndroid(androidBuilder =>
{
androidBuilder.OnCreate((activity, bundle) =>
{
if (activity is not AndroidX.AppCompat.App.AppCompatActivity componentActivity)
{
pictos marked this conversation as resolved.
Show resolved Hide resolved
return;
}

componentActivity.GetFragmentManager()?.RegisterFragmentLifecycleCallbacks(new MCTFragmentLifecycle(), false);
pictos marked this conversation as resolved.
Show resolved Hide resolved
});
});
});
#endif

builder.ConfigureMauiHandlers(h =>
{
h.AddHandler<DrawingView, DrawingViewHandler>();
Expand Down
10 changes: 10 additions & 0 deletions src/CommunityToolkit.Maui/Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ internal Options(in MauiAppBuilder builder) : this()
internal static bool ShouldSuppressExceptionsInConverters { get; private set; }
internal static bool ShouldSuppressExceptionsInBehaviors { get; private set; }
internal static bool ShouldEnableSnackbarOnWindows { get; private set; }
internal static bool ShouldUseMCTDialogFragment { get; private set; } = true;

/// <summary>
/// Will return the <see cref="ICommunityToolkitValueConverter.DefaultConvertReturnValue"/> default value instead of throwing an exception when using <see cref="BaseConverter{TFrom,TTo}"/>.
Expand All @@ -47,6 +48,15 @@ internal Options(in MauiAppBuilder builder) : this()
/// </remarks>
public void SetShouldSuppressExceptionsInBehaviors(bool value) => ShouldSuppressExceptionsInBehaviors = value;

/// <summary>
/// Enables the use of the DialogFragment Lifecycle service for Android.
/// </summary>
/// <param name="value">true if yes or false if you want to implement your own.</param>
/// <remarks>
/// Default value is true.
/// </remarks>
public void SetShouldUseMCTDialogFragment(bool value) => ShouldUseMCTDialogFragment = value;

/// <summary>
/// Enables <see cref="Alerts.Snackbar"/> for Windows
/// </summary>
Expand Down
146 changes: 146 additions & 0 deletions src/CommunityToolkit.Maui/Services/DialogFragmentService.android.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
using System.Diagnostics.CodeAnalysis;
using Android.Content;
using Android.OS;
using Android.Views;
using AndroidX.AppCompat.App;
using CommunityToolkit.Maui.Core;
using Debug = System.Diagnostics.Debug;
using DialogFragment = AndroidX.Fragment.App.DialogFragment;
using Fragment = AndroidX.Fragment.App.Fragment;
using FragmentManager = AndroidX.Fragment.App.FragmentManager;

namespace CommunityToolkit.Maui.Services;

sealed class DialogFragmentService : IDialogFragmentService
{
public void OnFragmentAttached(FragmentManager fm, Fragment f, Context context)
{
}

public void OnFragmentCreated(FragmentManager fm, Fragment f, Bundle? savedInstanceState)
{
}

public void OnFragmentDestroyed(FragmentManager fm, Fragment f)
{
}

public void OnFragmentDetached(FragmentManager fm, Fragment f)
{
}

public void OnFragmentPaused(FragmentManager fm, Fragment f)
{
}

public void OnFragmentPreAttached(FragmentManager fm, Fragment f, Context context)
{
}

public void OnFragmentPreCreated(FragmentManager fm, Fragment f, Bundle? savedInstanceState)
{
}

public void OnFragmentResumed(FragmentManager fm, Fragment f)
{
}

public void OnFragmentSaveInstanceState(FragmentManager fm, Fragment f, Bundle outState)
{
}

public void OnFragmentStarted(FragmentManager fm, Fragment f)
{
if (!IsDialogFragment(f, out var dialogFragment) || Platform.CurrentActivity is not AppCompatActivity activity)
{
pictos marked this conversation as resolved.
Show resolved Hide resolved
return;
}

HandleStatusBarColor(dialogFragment, activity);
}

static void HandleStatusBarColor(DialogFragment dialogFragment, AppCompatActivity activity)
{
if (activity.Window is null)
{
return;
}

var statusBarColor = activity.Window.StatusBarColor;
var platformColor = new Android.Graphics.Color(statusBarColor);
var dialog = dialogFragment.Dialog;

Debug.Assert(dialog is not null);
Debug.Assert(dialog.Window is not null);

var window = dialog.Window;

bool isColorTransparent = platformColor == Android.Graphics.Color.Transparent;

if (OperatingSystem.IsAndroidVersionAtLeast(30))
{
var windowInsetsController = window.InsetsController;
var appearance = activity.Window.InsetsController?.SystemBarsAppearance;

Debug.Assert(windowInsetsController is not null);
pictos marked this conversation as resolved.
Show resolved Hide resolved

if (appearance.HasValue)
{
windowInsetsController.SetSystemBarsAppearance(appearance.Value, appearance.Value);
}
else
{
windowInsetsController.SetSystemBarsAppearance(
isColorTransparent ? 0 : (int)WindowInsetsControllerAppearance.LightStatusBars,
(int)WindowInsetsControllerAppearance.LightStatusBars);
}
window.SetStatusBarColor(platformColor);
if (!OperatingSystem.IsAndroidVersionAtLeast(35))
{
window.SetDecorFitsSystemWindows(!isColorTransparent);
}
else
{
AndroidX.Core.View.WindowCompat.SetDecorFitsSystemWindows(window, !isColorTransparent);
}
}
else
{
dialog.Window.SetStatusBarColor(platformColor);

if (isColorTransparent)
{
window.ClearFlags(WindowManagerFlags.DrawsSystemBarBackgrounds);
window.SetFlags(WindowManagerFlags.LayoutNoLimits, WindowManagerFlags.LayoutNoLimits);
}
else
{
window.ClearFlags(WindowManagerFlags.LayoutNoLimits);
window.SetFlags(WindowManagerFlags.DrawsSystemBarBackgrounds, WindowManagerFlags.DrawsSystemBarBackgrounds);
}
}
}

public void OnFragmentStopped(FragmentManager fm, Fragment f)
{
}

public void OnFragmentViewCreated(FragmentManager fm, Fragment f, Android.Views.View v, Bundle? savedInstanceState)
{
}

public void OnFragmentViewDestroyed(FragmentManager fm, Fragment f)
{
}

static bool IsDialogFragment(Fragment fragment, [NotNullWhen(true)] out DialogFragment? dialogFragment)
{
dialogFragment = null;
if (fragment is DialogFragment dialog)
{
dialogFragment = dialog;
return true;
}
return false;
}
}
100 changes: 100 additions & 0 deletions src/CommunityToolkit.Maui/Services/MCTFragmentLifecycle.android.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
using Android.Content;
using Android.OS;
using CommunityToolkit.Maui.Core;
using FragmentManager = AndroidX.Fragment.App.FragmentManager;

namespace CommunityToolkit.Maui.Services;

sealed class MCTFragmentLifecycle : FragmentManager.FragmentLifecycleCallbacks
{
readonly IDialogFragmentService dialogFragmentService;

public MCTFragmentLifecycle()
{
if (IPlatformApplication.Current is null)
{
throw new InvalidOperationException("IPlatformApplication.Current is null.");
}

dialogFragmentService = (IDialogFragmentService?)IPlatformApplication.Current.Services.GetService(typeof(IDialogFragmentService))
?? throw new NullReferenceException($"{nameof(IDialogFragmentService)} is not registered, make sure to call UseMauiCommunityToolkit method on during the app builder or register the service manually.");
}

public override void OnFragmentAttached(FragmentManager fm, AndroidX.Fragment.App.Fragment f, Context context)
{
base.OnFragmentAttached(fm, f, context);
dialogFragmentService.OnFragmentAttached(fm, f, context);
}

public override void OnFragmentCreated(FragmentManager fm, AndroidX.Fragment.App.Fragment f, Bundle? savedInstanceState)
{
base.OnFragmentCreated(fm, f, savedInstanceState);
dialogFragmentService.OnFragmentCreated(fm, f, savedInstanceState);
}

public override void OnFragmentDestroyed(FragmentManager fm, AndroidX.Fragment.App.Fragment f)
{
base.OnFragmentDestroyed(fm, f);
dialogFragmentService.OnFragmentDestroyed(fm, f);
}

public override void OnFragmentDetached(FragmentManager fm, AndroidX.Fragment.App.Fragment f)
{
base.OnFragmentDetached(fm, f);
dialogFragmentService.OnFragmentDetached(fm, f);
}

public override void OnFragmentPaused(FragmentManager fm, AndroidX.Fragment.App.Fragment f)
{
base.OnFragmentPaused(fm, f);
dialogFragmentService.OnFragmentPaused(fm, f);
}

public override void OnFragmentPreAttached(FragmentManager fm, AndroidX.Fragment.App.Fragment f, Context context)
{
base.OnFragmentPreAttached(fm, f, context);
dialogFragmentService.OnFragmentPreAttached(fm, f, context);
}

public override void OnFragmentPreCreated(FragmentManager fm, AndroidX.Fragment.App.Fragment f, Bundle? savedInstanceState)
{
base.OnFragmentPreCreated(fm, f, savedInstanceState);
dialogFragmentService.OnFragmentPreCreated(fm, f, savedInstanceState);
}

public override void OnFragmentResumed(FragmentManager fm, AndroidX.Fragment.App.Fragment f)
{
base.OnFragmentResumed(fm, f);
dialogFragmentService.OnFragmentResumed(fm, f);
}

public override void OnFragmentSaveInstanceState(FragmentManager fm, AndroidX.Fragment.App.Fragment f, Bundle outState)
{
base.OnFragmentSaveInstanceState(fm, f, outState);
dialogFragmentService.OnFragmentSaveInstanceState(fm, f, outState);
}

public override void OnFragmentStarted(FragmentManager fm, AndroidX.Fragment.App.Fragment f)
{
base.OnFragmentStarted(fm, f);
dialogFragmentService.OnFragmentStarted(fm, f);
}

public override void OnFragmentStopped(FragmentManager fm, AndroidX.Fragment.App.Fragment f)
{
base.OnFragmentStopped(fm, f);
dialogFragmentService.OnFragmentStopped(fm, f);
}

public override void OnFragmentViewCreated(FragmentManager fm, AndroidX.Fragment.App.Fragment f, Android.Views.View v, Bundle? savedInstanceState)
{
base.OnFragmentViewCreated(fm, f, v, savedInstanceState);
dialogFragmentService.OnFragmentViewCreated(fm, f, v, savedInstanceState);
}

public override void OnFragmentViewDestroyed(FragmentManager fm, AndroidX.Fragment.App.Fragment f)
{
base.OnFragmentViewDestroyed(fm, f);
dialogFragmentService.OnFragmentViewDestroyed(fm, f);
}
}