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 26 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0630c5c
create services for DialogFragment
pictos Dec 26, 2024
0a7d0c8
register the service on android during the builder phase
pictos Dec 26, 2024
410fe31
move interface to Interfaces folder
pictos Dec 26, 2024
2364d10
move services to CommunityToolkit.Maui
pictos Dec 26, 2024
29aac74
add option to not use the default MCT impl
pictos Dec 26, 2024
1e2142f
move the registration bits into to CommunityToolkit.Maui project
pictos Dec 26, 2024
0e38c75
removed files
pictos Dec 26, 2024
b2fa42f
code cleanup
pictos Dec 26, 2024
03eb3cf
add trace
pictos Dec 26, 2024
d7a53c7
remove debug assert
pictos Dec 26, 2024
048a024
Update Naming
TheCodeTraveler Dec 27, 2024
6704cd7
Move to `CommunityToolkit.Maui.Core`
TheCodeTraveler Dec 27, 2024
5753c2d
`dotnet format`
TheCodeTraveler Dec 27, 2024
6e45f20
`dotnet format`
TheCodeTraveler Dec 27, 2024
4cdb611
Merge branch 'main' into pj/fix-status-bar-on-modal
TheCodeTraveler Jan 9, 2025
bcd13da
Merge branch 'main' into pj/fix-status-bar-on-modal
TheCodeTraveler Jan 10, 2025
c78e1ba
Promote to `public`
TheCodeTraveler Jan 10, 2025
d9145dd
Update CONTRIBUTING.md
TheCodeTraveler Jan 10, 2025
1775a81
Add XML Comments
TheCodeTraveler Jan 10, 2025
0462d85
Add `[EditorBrowsable(EditorBrowsableState.Never)]`
TheCodeTraveler Jan 10, 2025
b490d35
Merge branch 'main' into pj/fix-status-bar-on-modal
TheCodeTraveler Jan 10, 2025
1752606
Merge branch 'pj/fix-status-bar-on-modal' of https://github.com/Commu…
TheCodeTraveler Jan 10, 2025
6f4220c
Merge branch 'main' into pj/fix-status-bar-on-modal
TheCodeTraveler Jan 10, 2025
fd4c9c8
Merge branch 'main' into pj/fix-status-bar-on-modal
TheCodeTraveler Jan 13, 2025
6c85020
Merge branch 'main' into pj/fix-status-bar-on-modal
TheCodeTraveler Jan 16, 2025
1205dd6
Merge branch 'main' into pj/fix-status-bar-on-modal
jfversluis Jan 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 43 additions & 2 deletions src/CommunityToolkit.Maui.Core/AppBuilderExtensions.shared.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
namespace CommunityToolkit.Maui.Core;
using System.Diagnostics;
using CommunityToolkit.Maui.Core.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Maui.LifecycleEvents;
using Microsoft.Maui.Platform;

namespace CommunityToolkit.Maui.Core;

/// <summary>
/// <see cref="MauiAppBuilder"/> Extensions
Expand All @@ -11,9 +17,44 @@ public static class AppBuilderExtensions
/// <param name="builder"><see cref="MauiAppBuilder"/> generated by <see cref="MauiApp"/> </param>
/// <param name="options"><see cref="Options"/></param>
/// <returns><see cref="MauiAppBuilder"/> initialized for <see cref="CommunityToolkit.Maui.Core"/></returns>
public static MauiAppBuilder UseMauiCommunityToolkitCore(this MauiAppBuilder builder, Action<Options>? options = default)
public static MauiAppBuilder UseMauiCommunityToolkitCore(this MauiAppBuilder builder, Action<Options>? options = null)
{
options?.Invoke(new Options());

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

builder.ConfigureLifecycleEvents(static lifecycleBuilder =>
{
lifecycleBuilder.AddAndroid(static androidBuilder =>
{
androidBuilder.OnCreate(static (activity, _) =>
{
if (activity is not AndroidX.AppCompat.App.AppCompatActivity componentActivity)
{
Trace.WriteLine($"Unable to Modify Android StatusBar On ModalPage: Activity {activity.LocalClassName} must be an {nameof(AndroidX.AppCompat.App.AppCompatActivity)}");
return;
}

if (componentActivity.GetFragmentManager() is not AndroidX.Fragment.App.FragmentManager fragmentManager)
{
Trace.WriteLine($"Unable to Modify Android StatusBar On ModalPage: Unable to retrieve fragment manager from {nameof(AndroidX.AppCompat.App.AppCompatActivity)}");
return;
}

var dialogFragmentService = IPlatformApplication.Current?.Services.GetRequiredService<IDialogFragmentService>()
?? throw new InvalidOperationException($"Unable to retrieve {nameof(IDialogFragmentService)}");


fragmentManager.RegisterFragmentLifecycleCallbacks(new FragmentLifecycleManager(dialogFragmentService), false);
});
});
});
}
#endif

return builder;
}
}
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);
}
10 changes: 10 additions & 0 deletions src/CommunityToolkit.Maui.Core/Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,14 @@ namespace CommunityToolkit.Maui.Core;
/// </summary>
public class Options
{
internal static bool ShouldUseStatusBarBehaviorOnAndroidModalPage { get; private set; } = true;

/// <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 SetShouldUseStatusBarBehaviorOnAndroidModalPage(bool value) => ShouldUseStatusBarBehaviorOnAndroidModalPage = value;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
using System.Diagnostics.CodeAnalysis;
using Android.Content;
using Android.Views;
using AndroidX.AppCompat.App;
using DialogFragment = AndroidX.Fragment.App.DialogFragment;
using Fragment = AndroidX.Fragment.App.Fragment;
using FragmentManager = AndroidX.Fragment.App.FragmentManager;

namespace CommunityToolkit.Maui.Core.Services;

sealed partial 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 (!TryConvertToDialogFragment(f, out var dialogFragment) || Microsoft.Maui.ApplicationModel.Platform.CurrentActivity is not AppCompatActivity activity)
{
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);
if (dialogFragment.Dialog?.Window is not Window dialogWindow)
{
throw new InvalidOperationException("Dialog window cannot be null");
TheCodeTraveler marked this conversation as resolved.
Show resolved Hide resolved
}

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

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

if (windowInsetsController is null)
{
System.Diagnostics.Trace.WriteLine("WindowInsetsController is null, cannot set system bars appearance.");
return;
}

if (appearance.HasValue)
{
windowInsetsController.SetSystemBarsAppearance(appearance.Value, appearance.Value);
}
else
{
windowInsetsController.SetSystemBarsAppearance(
isColorTransparent ? 0 : (int)WindowInsetsControllerAppearance.LightStatusBars,
(int)WindowInsetsControllerAppearance.LightStatusBars);
}

dialogWindow.SetStatusBarColor(platformColor);

if (!OperatingSystem.IsAndroidVersionAtLeast(35))
{
dialogWindow.SetDecorFitsSystemWindows(!isColorTransparent);
}
else
{
AndroidX.Core.View.WindowCompat.SetDecorFitsSystemWindows(dialogWindow, !isColorTransparent);
}
}
else
{
dialogWindow.SetStatusBarColor(platformColor);

if (isColorTransparent)
{
dialogWindow.ClearFlags(WindowManagerFlags.DrawsSystemBarBackgrounds);
dialogWindow.SetFlags(WindowManagerFlags.LayoutNoLimits, WindowManagerFlags.LayoutNoLimits);
}
else
{
dialogWindow.ClearFlags(WindowManagerFlags.LayoutNoLimits);
dialogWindow.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 TryConvertToDialogFragment(Fragment fragment, [NotNullWhen(true)] out DialogFragment? dialogFragment)
{
dialogFragment = null;

if (fragment is not DialogFragment dialog)
{
return false;
}

dialogFragment = dialog;
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace CommunityToolkit.Maui.Core.Services;

sealed partial class DialogFragmentService
{

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using Android.Content;
using FragmentManager = AndroidX.Fragment.App.FragmentManager;

namespace CommunityToolkit.Maui.Core.Services;

sealed class FragmentLifecycleManager(IDialogFragmentService dialogFragmentService) : FragmentManager.FragmentLifecycleCallbacks
{
readonly IDialogFragmentService dialogFragmentService = dialogFragmentService;
TheCodeTraveler marked this conversation as resolved.
Show resolved Hide resolved

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);
}
}
Loading
Loading