From 0630c5c58bcc0cad0f9eac3396b5be061e29a030 Mon Sep 17 00:00:00 2001 From: Pedro Jesus Date: Wed, 25 Dec 2024 23:24:15 -0300 Subject: [PATCH 01/19] create services for DialogFragment --- .../Services/DialogFragmentService.android.cs | 192 ++++++++++++++++++ .../IDialogFragmentService.android.cs | 21 ++ .../Services/MCTFragmentLifecycle.android.cs | 99 +++++++++ 3 files changed, 312 insertions(+) create mode 100644 src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.android.cs create mode 100644 src/CommunityToolkit.Maui.Core/Services/IDialogFragmentService.android.cs create mode 100644 src/CommunityToolkit.Maui.Core/Services/MCTFragmentLifecycle.android.cs diff --git a/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.android.cs b/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.android.cs new file mode 100644 index 0000000000..a12893e729 --- /dev/null +++ b/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.android.cs @@ -0,0 +1,192 @@ +using System.Diagnostics; +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 class DialogFragmentService : IDialogFragmentService +{ + public void OnFragmentAttached(FragmentManager fm, Fragment f, Context context) + { + if (!IsDialogFragment(f, out var dialogFragment)) + { + return; + } + } + + public void OnFragmentCreated(FragmentManager fm, Fragment f, Bundle? savedInstanceState) + { + if (!IsDialogFragment(f, out var dialogFragment)) + { + return; + } + } + + public void OnFragmentDestroyed(FragmentManager fm, Fragment f) + { + if (!IsDialogFragment(f, out var dialogFragment)) + { + return; + } + } + + public void OnFragmentDetached(FragmentManager fm, Fragment f) + { + if (!IsDialogFragment(f, out var dialogFragment)) + { + return; + } + } + + public void OnFragmentPaused(FragmentManager fm, Fragment f) + { + if (!IsDialogFragment(f, out var dialogFragment)) + { + return; + } + } + + public void OnFragmentPreAttached(FragmentManager fm, Fragment f, Context context) + { + if (!IsDialogFragment(f, out var dialogFragment)) + { + return; + } + } + + public void OnFragmentPreCreated(FragmentManager fm, Fragment f, Bundle? savedInstanceState) + { + if (!IsDialogFragment(f, out var dialogFragment)) + { + return; + } + } + + public void OnFragmentResumed(FragmentManager fm, Fragment f) + { + if (!IsDialogFragment(f, out var dialogFragment)) + { + return; + } + } + + public void OnFragmentSaveInstanceState(FragmentManager fm, Fragment f, Bundle outState) + { + if (!IsDialogFragment(f, out var dialogFragment)) + { + return; + } + } + + public void OnFragmentStarted(FragmentManager fm, Fragment f) + { + if (!IsDialogFragment(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); + 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); + + 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) + { + if (!IsDialogFragment(f, out var dialogFragment)) + { + return; + } + } + + public void OnFragmentViewCreated(FragmentManager fm, Fragment f, View v, Bundle? savedInstanceState) + { + if (!IsDialogFragment(f, out var dialogFragment)) + { + return; + } + } + + public void OnFragmentViewDestroyed(FragmentManager fm, Fragment f) + { + if (!IsDialogFragment(f, out var dialogFragment)) + { + return; + } + } + + static bool IsDialogFragment(Fragment fragment, [NotNullWhen(true)] out DialogFragment? dialogFragment) + { + dialogFragment = null; + if (fragment is DialogFragment dialog) + { + dialogFragment = dialog; + return true; + } + return false; + } +} diff --git a/src/CommunityToolkit.Maui.Core/Services/IDialogFragmentService.android.cs b/src/CommunityToolkit.Maui.Core/Services/IDialogFragmentService.android.cs new file mode 100644 index 0000000000..ab7f7a9a80 --- /dev/null +++ b/src/CommunityToolkit.Maui.Core/Services/IDialogFragmentService.android.cs @@ -0,0 +1,21 @@ +using Android.Content; +using Android.Views; + +namespace CommunityToolkit.Maui.Core.Services; + +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); +} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Core/Services/MCTFragmentLifecycle.android.cs b/src/CommunityToolkit.Maui.Core/Services/MCTFragmentLifecycle.android.cs new file mode 100644 index 0000000000..dd7b323e8f --- /dev/null +++ b/src/CommunityToolkit.Maui.Core/Services/MCTFragmentLifecycle.android.cs @@ -0,0 +1,99 @@ +using Android.Content; +using Android.Views; +using FragmentManager = AndroidX.Fragment.App.FragmentManager; + +namespace CommunityToolkit.Maui.Core.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, 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); + } +} From 0a7d0c88ab7f4795fdfcc19ed10543562b181563 Mon Sep 17 00:00:00 2001 From: Pedro Jesus Date: Wed, 25 Dec 2024 23:24:32 -0300 Subject: [PATCH 02/19] register the service on android during the builder phase --- .../AppBuilderExtensions.shared.cs | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/CommunityToolkit.Maui.Core/AppBuilderExtensions.shared.cs b/src/CommunityToolkit.Maui.Core/AppBuilderExtensions.shared.cs index 92de26908f..1e7f785fb7 100644 --- a/src/CommunityToolkit.Maui.Core/AppBuilderExtensions.shared.cs +++ b/src/CommunityToolkit.Maui.Core/AppBuilderExtensions.shared.cs @@ -1,4 +1,11 @@ -namespace CommunityToolkit.Maui.Core; +#if ANDROID +using CommunityToolkit.Maui.Core.Services; +#endif +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Maui.LifecycleEvents; +using Microsoft.Maui.Platform; + +namespace CommunityToolkit.Maui.Core; /// /// Extensions @@ -14,6 +21,25 @@ public static class AppBuilderExtensions public static MauiAppBuilder UseMauiCommunityToolkitCore(this MauiAppBuilder builder, Action? options = default) { options?.Invoke(new Options()); +#if ANDROID + builder.Services.AddSingleton(); + + builder.ConfigureLifecycleEvents(builder => + { + builder.AddAndroid(androidBuilder => + { + androidBuilder.OnCreate((activity, bundle) => + { + if (activity is not AndroidX.AppCompat.App.AppCompatActivity componentActivity) + { + return; + } + + componentActivity.GetFragmentManager()?.RegisterFragmentLifecycleCallbacks(new MCTFragmentLifecycle(), false); + }); + }); + }); +#endif return builder; } } \ No newline at end of file From 410fe317842f02f66bbf3e6a1f11b8e8578d6b69 Mon Sep 17 00:00:00 2001 From: Pedro Jesus Date: Wed, 25 Dec 2024 23:47:15 -0300 Subject: [PATCH 03/19] move interface to Interfaces folder --- .../IDialogFragmentService.android.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename src/CommunityToolkit.Maui.Core/{Services => Interfaces}/IDialogFragmentService.android.cs (95%) diff --git a/src/CommunityToolkit.Maui.Core/Services/IDialogFragmentService.android.cs b/src/CommunityToolkit.Maui.Core/Interfaces/IDialogFragmentService.android.cs similarity index 95% rename from src/CommunityToolkit.Maui.Core/Services/IDialogFragmentService.android.cs rename to src/CommunityToolkit.Maui.Core/Interfaces/IDialogFragmentService.android.cs index ab7f7a9a80..ccd3d4d6f9 100644 --- a/src/CommunityToolkit.Maui.Core/Services/IDialogFragmentService.android.cs +++ b/src/CommunityToolkit.Maui.Core/Interfaces/IDialogFragmentService.android.cs @@ -1,9 +1,9 @@ using Android.Content; using Android.Views; -namespace CommunityToolkit.Maui.Core.Services; +namespace CommunityToolkit.Maui.Core; -interface IDialogFragmentService +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); From 2364d1027e8930f59c7c6c64beff490657718506 Mon Sep 17 00:00:00 2001 From: Pedro Jesus Date: Wed, 25 Dec 2024 23:47:30 -0300 Subject: [PATCH 04/19] move services to CommunityToolkit.Maui --- .../Services/DialogFragmentService.android.cs | 7 +++++-- .../Services/MCTFragmentLifecycle.android.cs | 7 ++++--- 2 files changed, 9 insertions(+), 5 deletions(-) rename src/{CommunityToolkit.Maui.Core => CommunityToolkit.Maui}/Services/DialogFragmentService.android.cs (96%) rename src/{CommunityToolkit.Maui.Core => CommunityToolkit.Maui}/Services/MCTFragmentLifecycle.android.cs (95%) diff --git a/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.android.cs b/src/CommunityToolkit.Maui/Services/DialogFragmentService.android.cs similarity index 96% rename from src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.android.cs rename to src/CommunityToolkit.Maui/Services/DialogFragmentService.android.cs index a12893e729..6076cb42c8 100644 --- a/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.android.cs +++ b/src/CommunityToolkit.Maui/Services/DialogFragmentService.android.cs @@ -1,13 +1,16 @@ using System.Diagnostics; 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.Core.Services; +namespace CommunityToolkit.Maui.Services; sealed class DialogFragmentService : IDialogFragmentService { @@ -163,7 +166,7 @@ public void OnFragmentStopped(FragmentManager fm, Fragment f) } } - public void OnFragmentViewCreated(FragmentManager fm, Fragment f, View v, Bundle? savedInstanceState) + public void OnFragmentViewCreated(FragmentManager fm, Fragment f, Android.Views.View v, Bundle? savedInstanceState) { if (!IsDialogFragment(f, out var dialogFragment)) { diff --git a/src/CommunityToolkit.Maui.Core/Services/MCTFragmentLifecycle.android.cs b/src/CommunityToolkit.Maui/Services/MCTFragmentLifecycle.android.cs similarity index 95% rename from src/CommunityToolkit.Maui.Core/Services/MCTFragmentLifecycle.android.cs rename to src/CommunityToolkit.Maui/Services/MCTFragmentLifecycle.android.cs index dd7b323e8f..a94909a32d 100644 --- a/src/CommunityToolkit.Maui.Core/Services/MCTFragmentLifecycle.android.cs +++ b/src/CommunityToolkit.Maui/Services/MCTFragmentLifecycle.android.cs @@ -1,8 +1,9 @@ using Android.Content; -using Android.Views; +using Android.OS; +using CommunityToolkit.Maui.Core; using FragmentManager = AndroidX.Fragment.App.FragmentManager; -namespace CommunityToolkit.Maui.Core.Services; +namespace CommunityToolkit.Maui.Services; sealed class MCTFragmentLifecycle : FragmentManager.FragmentLifecycleCallbacks { @@ -85,7 +86,7 @@ public override void OnFragmentStopped(FragmentManager fm, AndroidX.Fragment.App dialogFragmentService.OnFragmentStopped(fm, f); } - public override void OnFragmentViewCreated(FragmentManager fm, AndroidX.Fragment.App.Fragment f, View v, Bundle? savedInstanceState) + 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); From 29aac74b5fd81f1521737a36c7f1f85c5251e1da Mon Sep 17 00:00:00 2001 From: Pedro Jesus Date: Wed, 25 Dec 2024 23:47:58 -0300 Subject: [PATCH 05/19] add option to not use the default MCT impl --- src/CommunityToolkit.Maui/Options.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/CommunityToolkit.Maui/Options.cs b/src/CommunityToolkit.Maui/Options.cs index 64275c3975..fb1f31ef91 100644 --- a/src/CommunityToolkit.Maui/Options.cs +++ b/src/CommunityToolkit.Maui/Options.cs @@ -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; /// /// Will return the default value instead of throwing an exception when using . @@ -47,6 +48,15 @@ internal Options(in MauiAppBuilder builder) : this() /// public void SetShouldSuppressExceptionsInBehaviors(bool value) => ShouldSuppressExceptionsInBehaviors = value; + /// + /// Enables the use of the DialogFragment Lifecycle service for Android. + /// + /// true if yes or false if you want to implement your own. + /// + /// Default value is true. + /// + public void SetShouldUseMCTDialogFragment(bool value) => ShouldUseMCTDialogFragment = value; + /// /// Enables for Windows /// From 1e2142f807fb66a9c07d20655bc1373a7a6b21fa Mon Sep 17 00:00:00 2001 From: Pedro Jesus Date: Wed, 25 Dec 2024 23:48:22 -0300 Subject: [PATCH 06/19] move the registration bits into to CommunityToolkit.Maui project --- .../AppBuilderExtensions.shared.cs | 18 +---------- .../AppBuilderExtensions.shared.cs | 30 ++++++++++++++++++- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/CommunityToolkit.Maui.Core/AppBuilderExtensions.shared.cs b/src/CommunityToolkit.Maui.Core/AppBuilderExtensions.shared.cs index 1e7f785fb7..7beaf280bd 100644 --- a/src/CommunityToolkit.Maui.Core/AppBuilderExtensions.shared.cs +++ b/src/CommunityToolkit.Maui.Core/AppBuilderExtensions.shared.cs @@ -22,23 +22,7 @@ public static MauiAppBuilder UseMauiCommunityToolkitCore(this MauiAppBuilder bui { options?.Invoke(new Options()); #if ANDROID - builder.Services.AddSingleton(); - - builder.ConfigureLifecycleEvents(builder => - { - builder.AddAndroid(androidBuilder => - { - androidBuilder.OnCreate((activity, bundle) => - { - if (activity is not AndroidX.AppCompat.App.AppCompatActivity componentActivity) - { - return; - } - - componentActivity.GetFragmentManager()?.RegisterFragmentLifecycleCallbacks(new MCTFragmentLifecycle(), false); - }); - }); - }); + #endif return builder; } diff --git a/src/CommunityToolkit.Maui/AppBuilderExtensions.shared.cs b/src/CommunityToolkit.Maui/AppBuilderExtensions.shared.cs index 404f8719c5..31bb697b4b 100644 --- a/src/CommunityToolkit.Maui/AppBuilderExtensions.shared.cs +++ b/src/CommunityToolkit.Maui/AppBuilderExtensions.shared.cs @@ -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; @@ -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(); + } + + builder.ConfigureLifecycleEvents(builder => + { + builder.AddAndroid(androidBuilder => + { + androidBuilder.OnCreate((activity, bundle) => + { + if (activity is not AndroidX.AppCompat.App.AppCompatActivity componentActivity) + { + return; + } + + componentActivity.GetFragmentManager()?.RegisterFragmentLifecycleCallbacks(new MCTFragmentLifecycle(), false); + }); + }); + }); +#endif + builder.ConfigureMauiHandlers(h => { h.AddHandler(); From 0e38c751454c2ca8c4857fd1583e233e7818a39e Mon Sep 17 00:00:00 2001 From: Pedro Jesus Date: Wed, 25 Dec 2024 23:49:05 -0300 Subject: [PATCH 07/19] removed files --- .../AppBuilderExtensions.shared.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/CommunityToolkit.Maui.Core/AppBuilderExtensions.shared.cs b/src/CommunityToolkit.Maui.Core/AppBuilderExtensions.shared.cs index 7beaf280bd..92de26908f 100644 --- a/src/CommunityToolkit.Maui.Core/AppBuilderExtensions.shared.cs +++ b/src/CommunityToolkit.Maui.Core/AppBuilderExtensions.shared.cs @@ -1,11 +1,4 @@ -#if ANDROID -using CommunityToolkit.Maui.Core.Services; -#endif -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Maui.LifecycleEvents; -using Microsoft.Maui.Platform; - -namespace CommunityToolkit.Maui.Core; +namespace CommunityToolkit.Maui.Core; /// /// Extensions @@ -21,9 +14,6 @@ public static class AppBuilderExtensions public static MauiAppBuilder UseMauiCommunityToolkitCore(this MauiAppBuilder builder, Action? options = default) { options?.Invoke(new Options()); -#if ANDROID - -#endif return builder; } } \ No newline at end of file From b2fa42ff39863334f0b7b94f7757f9a5d127b7e4 Mon Sep 17 00:00:00 2001 From: Pedro Jesus Date: Wed, 25 Dec 2024 23:51:11 -0300 Subject: [PATCH 08/19] code cleanup --- .../Services/DialogFragmentService.android.cs | 53 +------------------ 1 file changed, 2 insertions(+), 51 deletions(-) diff --git a/src/CommunityToolkit.Maui/Services/DialogFragmentService.android.cs b/src/CommunityToolkit.Maui/Services/DialogFragmentService.android.cs index 6076cb42c8..fa17c3853f 100644 --- a/src/CommunityToolkit.Maui/Services/DialogFragmentService.android.cs +++ b/src/CommunityToolkit.Maui/Services/DialogFragmentService.android.cs @@ -1,5 +1,4 @@ -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; using Android.Content; using Android.OS; using Android.Views; @@ -16,79 +15,43 @@ sealed class DialogFragmentService : IDialogFragmentService { public void OnFragmentAttached(FragmentManager fm, Fragment f, Context context) { - if (!IsDialogFragment(f, out var dialogFragment)) - { - return; - } } public void OnFragmentCreated(FragmentManager fm, Fragment f, Bundle? savedInstanceState) { - if (!IsDialogFragment(f, out var dialogFragment)) - { - return; - } } public void OnFragmentDestroyed(FragmentManager fm, Fragment f) { - if (!IsDialogFragment(f, out var dialogFragment)) - { - return; - } } public void OnFragmentDetached(FragmentManager fm, Fragment f) { - if (!IsDialogFragment(f, out var dialogFragment)) - { - return; - } } public void OnFragmentPaused(FragmentManager fm, Fragment f) { - if (!IsDialogFragment(f, out var dialogFragment)) - { - return; - } } public void OnFragmentPreAttached(FragmentManager fm, Fragment f, Context context) { - if (!IsDialogFragment(f, out var dialogFragment)) - { - return; - } } public void OnFragmentPreCreated(FragmentManager fm, Fragment f, Bundle? savedInstanceState) { - if (!IsDialogFragment(f, out var dialogFragment)) - { - return; - } } public void OnFragmentResumed(FragmentManager fm, Fragment f) { - if (!IsDialogFragment(f, out var dialogFragment)) - { - return; - } } public void OnFragmentSaveInstanceState(FragmentManager fm, Fragment f, Bundle outState) { - if (!IsDialogFragment(f, out var dialogFragment)) - { - return; - } } public void OnFragmentStarted(FragmentManager fm, Fragment f) { - if (!IsDialogFragment(f, out var dialogFragment) || Microsoft.Maui.ApplicationModel.Platform.CurrentActivity is not AppCompatActivity activity) + if (!IsDialogFragment(f, out var dialogFragment) || Platform.CurrentActivity is not AppCompatActivity activity) { return; } @@ -160,26 +123,14 @@ static void HandleStatusBarColor(DialogFragment dialogFragment, AppCompatActivit public void OnFragmentStopped(FragmentManager fm, Fragment f) { - if (!IsDialogFragment(f, out var dialogFragment)) - { - return; - } } public void OnFragmentViewCreated(FragmentManager fm, Fragment f, Android.Views.View v, Bundle? savedInstanceState) { - if (!IsDialogFragment(f, out var dialogFragment)) - { - return; - } } public void OnFragmentViewDestroyed(FragmentManager fm, Fragment f) { - if (!IsDialogFragment(f, out var dialogFragment)) - { - return; - } } static bool IsDialogFragment(Fragment fragment, [NotNullWhen(true)] out DialogFragment? dialogFragment) From 03eb3cfb92bcd2f26ed8701f314bf4a4d51c669d Mon Sep 17 00:00:00 2001 From: Pedro Jesus Date: Thu, 26 Dec 2024 13:59:46 -0300 Subject: [PATCH 09/19] add trace --- src/CommunityToolkit.Maui/AppBuilderExtensions.shared.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/CommunityToolkit.Maui/AppBuilderExtensions.shared.cs b/src/CommunityToolkit.Maui/AppBuilderExtensions.shared.cs index 31bb697b4b..b6936c3267 100644 --- a/src/CommunityToolkit.Maui/AppBuilderExtensions.shared.cs +++ b/src/CommunityToolkit.Maui/AppBuilderExtensions.shared.cs @@ -7,6 +7,7 @@ using CommunityToolkit.Maui.Views; using Microsoft.Maui.LifecycleEvents; using Microsoft.Maui.Platform; +using System.Diagnostics; namespace CommunityToolkit.Maui; @@ -45,6 +46,7 @@ public static MauiAppBuilder UseMauiCommunityToolkit(this MauiAppBuilder builder { if (activity is not AndroidX.AppCompat.App.AppCompatActivity componentActivity) { + Trace.WriteLine("Activity is not an AppCompatActivity, cannot register fragment lifecycle callbacks."); return; } From d7a53c7a7c91020775e172a2b9312902f9fd9341 Mon Sep 17 00:00:00 2001 From: Pedro Jesus Date: Thu, 26 Dec 2024 14:01:25 -0300 Subject: [PATCH 10/19] remove debug assert --- .../Services/DialogFragmentService.android.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/CommunityToolkit.Maui/Services/DialogFragmentService.android.cs b/src/CommunityToolkit.Maui/Services/DialogFragmentService.android.cs index fa17c3853f..4cfab6964d 100644 --- a/src/CommunityToolkit.Maui/Services/DialogFragmentService.android.cs +++ b/src/CommunityToolkit.Maui/Services/DialogFragmentService.android.cs @@ -82,7 +82,11 @@ static void HandleStatusBarColor(DialogFragment dialogFragment, AppCompatActivit var windowInsetsController = window.InsetsController; var appearance = activity.Window.InsetsController?.SystemBarsAppearance; - Debug.Assert(windowInsetsController is not null); + if (windowInsetsController is null) + { + System.Diagnostics.Trace.WriteLine("WindowInsetsController is null, cannot set system bars appearance."); + return; + } if (appearance.HasValue) { From 048a02423755cc2899f13e962d28bcc3b5d75129 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Fri, 27 Dec 2024 12:35:06 -0800 Subject: [PATCH 11/19] Update Naming --- .../AppBuilderExtensions.shared.cs | 92 ++++++++++--------- src/CommunityToolkit.Maui/Options.cs | 4 +- .../Services/DialogFragmentService.android.cs | 2 +- .../Services/DialogFragmentService.shared.cs | 6 ++ .../Services/MCTFragmentLifecycle.android.cs | 15 +-- 5 files changed, 61 insertions(+), 58 deletions(-) create mode 100644 src/CommunityToolkit.Maui/Services/DialogFragmentService.shared.cs diff --git a/src/CommunityToolkit.Maui/AppBuilderExtensions.shared.cs b/src/CommunityToolkit.Maui/AppBuilderExtensions.shared.cs index b6936c3267..6c7da920bd 100644 --- a/src/CommunityToolkit.Maui/AppBuilderExtensions.shared.cs +++ b/src/CommunityToolkit.Maui/AppBuilderExtensions.shared.cs @@ -1,13 +1,11 @@ -#if ANDROID -using CommunityToolkit.Maui.Services; -#endif +using System.Diagnostics; using CommunityToolkit.Maui.Core; using CommunityToolkit.Maui.Core.Handlers; using CommunityToolkit.Maui.PlatformConfiguration.AndroidSpecific; +using CommunityToolkit.Maui.Services; using CommunityToolkit.Maui.Views; using Microsoft.Maui.LifecycleEvents; using Microsoft.Maui.Platform; -using System.Diagnostics; namespace CommunityToolkit.Maui; @@ -16,55 +14,65 @@ namespace CommunityToolkit.Maui; /// public static class AppBuilderExtensions { - /// - /// Initializes the .NET MAUI Community Toolkit Library - /// - /// generated by - /// - /// initialized for - public static MauiAppBuilder UseMauiCommunityToolkit(this MauiAppBuilder builder, Action? options = default) - { - // Pass `null` because `options?.Invoke()` will set options on both `CommunityToolkit.Maui` and `CommunityToolkit.Maui.Core` - builder.UseMauiCommunityToolkitCore(null); - - builder.Services.AddSingleton(); + /// + /// Initializes the .NET MAUI Community Toolkit Library + /// + /// generated by + /// + /// initialized for + public static MauiAppBuilder UseMauiCommunityToolkit(this MauiAppBuilder builder, Action? options = null) + { + // Pass `null` because `options?.Invoke()` will set options on both `CommunityToolkit.Maui` and `CommunityToolkit.Maui.Core` + builder.UseMauiCommunityToolkitCore(null); - // Invokes options for both `CommunityToolkit.Maui` and `CommunityToolkit.Maui.Core` - options?.Invoke(new Options(builder)); + // Invokes options for both `CommunityToolkit.Maui` and `CommunityToolkit.Maui.Core` + options?.Invoke(new Options(builder)); #if ANDROID - if (Options.ShouldUseMCTDialogFragment) - { - builder.Services.AddSingleton(); - } + if (Options.ShouldUseStatusBarBehaviorOnAndroidModalPage) + { + builder.Services.AddSingleton(); - builder.ConfigureLifecycleEvents(builder => - { - builder.AddAndroid(androidBuilder => + builder.ConfigureLifecycleEvents(static lifecycleBuilder => { - androidBuilder.OnCreate((activity, bundle) => + lifecycleBuilder.AddAndroid(static androidBuilder => { - if (activity is not AndroidX.AppCompat.App.AppCompatActivity componentActivity) + androidBuilder.OnCreate(static (activity, _) => { - Trace.WriteLine("Activity is not an AppCompatActivity, cannot register fragment lifecycle callbacks."); - return; - } + 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; + } - componentActivity.GetFragmentManager()?.RegisterFragmentLifecycleCallbacks(new MCTFragmentLifecycle(), false); + 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() + ?? throw new InvalidOperationException($"Unable to retrieve {nameof(IDialogFragmentService)}"); + + + fragmentManager.RegisterFragmentLifecycleCallbacks(new FragmentLifecycleManager(dialogFragmentService), false); + }); }); }); - }); + } #endif - builder.ConfigureMauiHandlers(h => - { - h.AddHandler(); - h.AddHandler(); - h.AddHandler(); - }); + builder.Services.AddSingleton(); + + builder.ConfigureMauiHandlers(static h => + { + h.AddHandler(); + h.AddHandler(); + h.AddHandler(); + }); - Popup.RemapForControls(); - NavigationBar.RemapForControls(); - return builder; - } + Popup.RemapForControls(); + NavigationBar.RemapForControls(); + return builder; + } } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui/Options.cs b/src/CommunityToolkit.Maui/Options.cs index fb1f31ef91..12cfb759b8 100644 --- a/src/CommunityToolkit.Maui/Options.cs +++ b/src/CommunityToolkit.Maui/Options.cs @@ -18,11 +18,11 @@ internal Options(in MauiAppBuilder builder) : this() this.builder = builder; } + internal static bool ShouldUseStatusBarBehaviorOnAndroidModalPage { get; private set; } = true; internal static bool ShouldSuppressExceptionsInAnimations { get; private set; } 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; /// /// Will return the default value instead of throwing an exception when using . @@ -55,7 +55,7 @@ internal Options(in MauiAppBuilder builder) : this() /// /// Default value is true. /// - public void SetShouldUseMCTDialogFragment(bool value) => ShouldUseMCTDialogFragment = value; + public void SetShouldUseStatusBarBehaviorOnAndroidModalPage(bool value) => ShouldUseStatusBarBehaviorOnAndroidModalPage = value; /// /// Enables for Windows diff --git a/src/CommunityToolkit.Maui/Services/DialogFragmentService.android.cs b/src/CommunityToolkit.Maui/Services/DialogFragmentService.android.cs index 4cfab6964d..42126c0b79 100644 --- a/src/CommunityToolkit.Maui/Services/DialogFragmentService.android.cs +++ b/src/CommunityToolkit.Maui/Services/DialogFragmentService.android.cs @@ -11,7 +11,7 @@ namespace CommunityToolkit.Maui.Services; -sealed class DialogFragmentService : IDialogFragmentService +sealed partial class DialogFragmentService : IDialogFragmentService { public void OnFragmentAttached(FragmentManager fm, Fragment f, Context context) { diff --git a/src/CommunityToolkit.Maui/Services/DialogFragmentService.shared.cs b/src/CommunityToolkit.Maui/Services/DialogFragmentService.shared.cs new file mode 100644 index 0000000000..5585512e34 --- /dev/null +++ b/src/CommunityToolkit.Maui/Services/DialogFragmentService.shared.cs @@ -0,0 +1,6 @@ +namespace CommunityToolkit.Maui.Services; + +sealed partial class DialogFragmentService +{ + +} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui/Services/MCTFragmentLifecycle.android.cs b/src/CommunityToolkit.Maui/Services/MCTFragmentLifecycle.android.cs index a94909a32d..7c72de93d0 100644 --- a/src/CommunityToolkit.Maui/Services/MCTFragmentLifecycle.android.cs +++ b/src/CommunityToolkit.Maui/Services/MCTFragmentLifecycle.android.cs @@ -5,20 +5,9 @@ namespace CommunityToolkit.Maui.Services; -sealed class MCTFragmentLifecycle : FragmentManager.FragmentLifecycleCallbacks +sealed class FragmentLifecycleManager(IDialogFragmentService dialogFragmentService) : 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."); - } + readonly IDialogFragmentService dialogFragmentService = dialogFragmentService; public override void OnFragmentAttached(FragmentManager fm, AndroidX.Fragment.App.Fragment f, Context context) { From 6704cd750eb4dbd1c869f2ba025fb3384afc0015 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Fri, 27 Dec 2024 12:44:41 -0800 Subject: [PATCH 12/19] Move to `CommunityToolkit.Maui.Core` --- .../AppBuilderExtensions.shared.cs | 45 +++++++++++++++- src/CommunityToolkit.Maui.Core/Options.cs | 10 ++++ .../Services/DialogFragmentService.android.cs | 51 +++++++++---------- .../Services/DialogFragmentService.shared.cs | 2 +- .../FragmentLifecycleManager.android.cs} | 4 +- .../AppBuilderExtensions.shared.cs | 35 ------------- src/CommunityToolkit.Maui/Options.cs | 12 +---- 7 files changed, 81 insertions(+), 78 deletions(-) rename src/{CommunityToolkit.Maui => CommunityToolkit.Maui.Core}/Services/DialogFragmentService.android.cs (66%) rename src/{CommunityToolkit.Maui => CommunityToolkit.Maui.Core}/Services/DialogFragmentService.shared.cs (52%) rename src/{CommunityToolkit.Maui/Services/MCTFragmentLifecycle.android.cs => CommunityToolkit.Maui.Core/Services/FragmentLifecycleManager.android.cs} (97%) diff --git a/src/CommunityToolkit.Maui.Core/AppBuilderExtensions.shared.cs b/src/CommunityToolkit.Maui.Core/AppBuilderExtensions.shared.cs index 92de26908f..e61f5c1d13 100644 --- a/src/CommunityToolkit.Maui.Core/AppBuilderExtensions.shared.cs +++ b/src/CommunityToolkit.Maui.Core/AppBuilderExtensions.shared.cs @@ -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; /// /// Extensions @@ -11,9 +17,44 @@ public static class AppBuilderExtensions /// generated by /// /// initialized for - public static MauiAppBuilder UseMauiCommunityToolkitCore(this MauiAppBuilder builder, Action? options = default) + public static MauiAppBuilder UseMauiCommunityToolkitCore(this MauiAppBuilder builder, Action? options = null) { options?.Invoke(new Options()); + +#if ANDROID + if (Options.ShouldUseStatusBarBehaviorOnAndroidModalPage) + { + builder.Services.AddSingleton(); + + 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() + ?? throw new InvalidOperationException($"Unable to retrieve {nameof(IDialogFragmentService)}"); + + + fragmentManager.RegisterFragmentLifecycleCallbacks(new FragmentLifecycleManager(dialogFragmentService), false); + }); + }); + }); + } +#endif + return builder; } } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Core/Options.cs b/src/CommunityToolkit.Maui.Core/Options.cs index 7acaf46809..887114830a 100644 --- a/src/CommunityToolkit.Maui.Core/Options.cs +++ b/src/CommunityToolkit.Maui.Core/Options.cs @@ -5,4 +5,14 @@ namespace CommunityToolkit.Maui.Core; /// public class Options { + internal static bool ShouldUseStatusBarBehaviorOnAndroidModalPage { get; private set; } = true; + + /// + /// Enables the use of the DialogFragment Lifecycle service for Android. + /// + /// true if yes or false if you want to implement your own. + /// + /// Default value is true. + /// + public void SetShouldUseStatusBarBehaviorOnAndroidModalPage(bool value) => ShouldUseStatusBarBehaviorOnAndroidModalPage = value; } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui/Services/DialogFragmentService.android.cs b/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.android.cs similarity index 66% rename from src/CommunityToolkit.Maui/Services/DialogFragmentService.android.cs rename to src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.android.cs index 42126c0b79..42eb8ef059 100644 --- a/src/CommunityToolkit.Maui/Services/DialogFragmentService.android.cs +++ b/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.android.cs @@ -1,15 +1,12 @@ 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; +namespace CommunityToolkit.Maui.Core.Services; sealed partial class DialogFragmentService : IDialogFragmentService { @@ -51,7 +48,7 @@ public void OnFragmentSaveInstanceState(FragmentManager fm, Fragment f, Bundle o public void OnFragmentStarted(FragmentManager fm, Fragment f) { - if (!IsDialogFragment(f, out var dialogFragment) || Platform.CurrentActivity is not AppCompatActivity activity) + if (!TryConvertToDialogFragment(f, out var dialogFragment) || Microsoft.Maui.ApplicationModel.Platform.CurrentActivity is not AppCompatActivity activity) { return; } @@ -68,18 +65,16 @@ static void HandleStatusBarColor(DialogFragment dialogFragment, AppCompatActivit 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; + if (dialogFragment.Dialog?.Window is not Window dialogWindow) + { + throw new InvalidOperationException("Dialog window cannot be null"); + } - bool isColorTransparent = platformColor == Android.Graphics.Color.Transparent; + var isColorTransparent = platformColor == Android.Graphics.Color.Transparent; if (OperatingSystem.IsAndroidVersionAtLeast(30)) { - var windowInsetsController = window.InsetsController; + var windowInsetsController = dialogWindow.InsetsController; var appearance = activity.Window.InsetsController?.SystemBarsAppearance; if (windowInsetsController is null) @@ -98,29 +93,31 @@ static void HandleStatusBarColor(DialogFragment dialogFragment, AppCompatActivit isColorTransparent ? 0 : (int)WindowInsetsControllerAppearance.LightStatusBars, (int)WindowInsetsControllerAppearance.LightStatusBars); } - window.SetStatusBarColor(platformColor); + + dialogWindow.SetStatusBarColor(platformColor); + if (!OperatingSystem.IsAndroidVersionAtLeast(35)) { - window.SetDecorFitsSystemWindows(!isColorTransparent); + dialogWindow.SetDecorFitsSystemWindows(!isColorTransparent); } else { - AndroidX.Core.View.WindowCompat.SetDecorFitsSystemWindows(window, !isColorTransparent); + AndroidX.Core.View.WindowCompat.SetDecorFitsSystemWindows(dialogWindow, !isColorTransparent); } } else { - dialog.Window.SetStatusBarColor(platformColor); + dialogWindow.SetStatusBarColor(platformColor); if (isColorTransparent) { - window.ClearFlags(WindowManagerFlags.DrawsSystemBarBackgrounds); - window.SetFlags(WindowManagerFlags.LayoutNoLimits, WindowManagerFlags.LayoutNoLimits); + dialogWindow.ClearFlags(WindowManagerFlags.DrawsSystemBarBackgrounds); + dialogWindow.SetFlags(WindowManagerFlags.LayoutNoLimits, WindowManagerFlags.LayoutNoLimits); } else { - window.ClearFlags(WindowManagerFlags.LayoutNoLimits); - window.SetFlags(WindowManagerFlags.DrawsSystemBarBackgrounds, WindowManagerFlags.DrawsSystemBarBackgrounds); + dialogWindow.ClearFlags(WindowManagerFlags.LayoutNoLimits); + dialogWindow.SetFlags(WindowManagerFlags.DrawsSystemBarBackgrounds, WindowManagerFlags.DrawsSystemBarBackgrounds); } } } @@ -137,14 +134,16 @@ public void OnFragmentViewDestroyed(FragmentManager fm, Fragment f) { } - static bool IsDialogFragment(Fragment fragment, [NotNullWhen(true)] out DialogFragment? dialogFragment) + static bool TryConvertToDialogFragment(Fragment fragment, [NotNullWhen(true)] out DialogFragment? dialogFragment) { dialogFragment = null; - if (fragment is DialogFragment dialog) + + if (fragment is not DialogFragment dialog) { - dialogFragment = dialog; - return true; + return false; } - return false; + + dialogFragment = dialog; + return true; } } diff --git a/src/CommunityToolkit.Maui/Services/DialogFragmentService.shared.cs b/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.shared.cs similarity index 52% rename from src/CommunityToolkit.Maui/Services/DialogFragmentService.shared.cs rename to src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.shared.cs index 5585512e34..45ded5a1da 100644 --- a/src/CommunityToolkit.Maui/Services/DialogFragmentService.shared.cs +++ b/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.shared.cs @@ -1,4 +1,4 @@ -namespace CommunityToolkit.Maui.Services; +namespace CommunityToolkit.Maui.Core.Services; sealed partial class DialogFragmentService { diff --git a/src/CommunityToolkit.Maui/Services/MCTFragmentLifecycle.android.cs b/src/CommunityToolkit.Maui.Core/Services/FragmentLifecycleManager.android.cs similarity index 97% rename from src/CommunityToolkit.Maui/Services/MCTFragmentLifecycle.android.cs rename to src/CommunityToolkit.Maui.Core/Services/FragmentLifecycleManager.android.cs index 7c72de93d0..6164bbfc8d 100644 --- a/src/CommunityToolkit.Maui/Services/MCTFragmentLifecycle.android.cs +++ b/src/CommunityToolkit.Maui.Core/Services/FragmentLifecycleManager.android.cs @@ -1,9 +1,7 @@ using Android.Content; -using Android.OS; -using CommunityToolkit.Maui.Core; using FragmentManager = AndroidX.Fragment.App.FragmentManager; -namespace CommunityToolkit.Maui.Services; +namespace CommunityToolkit.Maui.Core.Services; sealed class FragmentLifecycleManager(IDialogFragmentService dialogFragmentService) : FragmentManager.FragmentLifecycleCallbacks { diff --git a/src/CommunityToolkit.Maui/AppBuilderExtensions.shared.cs b/src/CommunityToolkit.Maui/AppBuilderExtensions.shared.cs index 6c7da920bd..ee69f9c494 100644 --- a/src/CommunityToolkit.Maui/AppBuilderExtensions.shared.cs +++ b/src/CommunityToolkit.Maui/AppBuilderExtensions.shared.cs @@ -2,7 +2,6 @@ using CommunityToolkit.Maui.Core; using CommunityToolkit.Maui.Core.Handlers; using CommunityToolkit.Maui.PlatformConfiguration.AndroidSpecific; -using CommunityToolkit.Maui.Services; using CommunityToolkit.Maui.Views; using Microsoft.Maui.LifecycleEvents; using Microsoft.Maui.Platform; @@ -28,40 +27,6 @@ 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.ShouldUseStatusBarBehaviorOnAndroidModalPage) - { - builder.Services.AddSingleton(); - - 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() - ?? throw new InvalidOperationException($"Unable to retrieve {nameof(IDialogFragmentService)}"); - - - fragmentManager.RegisterFragmentLifecycleCallbacks(new FragmentLifecycleManager(dialogFragmentService), false); - }); - }); - }); - } -#endif - builder.Services.AddSingleton(); builder.ConfigureMauiHandlers(static h => diff --git a/src/CommunityToolkit.Maui/Options.cs b/src/CommunityToolkit.Maui/Options.cs index 12cfb759b8..7f14ba5751 100644 --- a/src/CommunityToolkit.Maui/Options.cs +++ b/src/CommunityToolkit.Maui/Options.cs @@ -17,8 +17,7 @@ internal Options(in MauiAppBuilder builder) : this() { this.builder = builder; } - - internal static bool ShouldUseStatusBarBehaviorOnAndroidModalPage { get; private set; } = true; + internal static bool ShouldSuppressExceptionsInAnimations { get; private set; } internal static bool ShouldSuppressExceptionsInConverters { get; private set; } internal static bool ShouldSuppressExceptionsInBehaviors { get; private set; } @@ -48,15 +47,6 @@ internal Options(in MauiAppBuilder builder) : this() /// public void SetShouldSuppressExceptionsInBehaviors(bool value) => ShouldSuppressExceptionsInBehaviors = value; - /// - /// Enables the use of the DialogFragment Lifecycle service for Android. - /// - /// true if yes or false if you want to implement your own. - /// - /// Default value is true. - /// - public void SetShouldUseStatusBarBehaviorOnAndroidModalPage(bool value) => ShouldUseStatusBarBehaviorOnAndroidModalPage = value; - /// /// Enables for Windows /// From 5753c2d2f8ffe3c2a3d603c2c24b5e70e8d96f35 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Fri, 27 Dec 2024 12:53:37 -0800 Subject: [PATCH 13/19] `dotnet format` --- src/CommunityToolkit.Maui/AppBuilderExtensions.shared.cs | 5 +---- src/CommunityToolkit.Maui/Options.cs | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/CommunityToolkit.Maui/AppBuilderExtensions.shared.cs b/src/CommunityToolkit.Maui/AppBuilderExtensions.shared.cs index ee69f9c494..1e5ae387fe 100644 --- a/src/CommunityToolkit.Maui/AppBuilderExtensions.shared.cs +++ b/src/CommunityToolkit.Maui/AppBuilderExtensions.shared.cs @@ -1,10 +1,7 @@ -using System.Diagnostics; -using CommunityToolkit.Maui.Core; +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; diff --git a/src/CommunityToolkit.Maui/Options.cs b/src/CommunityToolkit.Maui/Options.cs index 7f14ba5751..64275c3975 100644 --- a/src/CommunityToolkit.Maui/Options.cs +++ b/src/CommunityToolkit.Maui/Options.cs @@ -17,7 +17,7 @@ internal Options(in MauiAppBuilder builder) : this() { this.builder = builder; } - + internal static bool ShouldSuppressExceptionsInAnimations { get; private set; } internal static bool ShouldSuppressExceptionsInConverters { get; private set; } internal static bool ShouldSuppressExceptionsInBehaviors { get; private set; } From 6e45f207f17973d3287e7fc09cf804fb231c01df Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Fri, 27 Dec 2024 12:54:42 -0800 Subject: [PATCH 14/19] `dotnet format` --- .../AppBuilderExtensions.shared.cs | 4 +- src/CommunityToolkit.Maui.Core/Options.cs | 20 ++++---- .../Services/DialogFragmentService.android.cs | 10 ++-- .../Services/DialogFragmentService.shared.cs | 2 +- .../FragmentLifecycleManager.android.cs | 2 +- .../AppBuilderExtensions.shared.cs | 46 +++++++++---------- 6 files changed, 42 insertions(+), 42 deletions(-) diff --git a/src/CommunityToolkit.Maui.Core/AppBuilderExtensions.shared.cs b/src/CommunityToolkit.Maui.Core/AppBuilderExtensions.shared.cs index e61f5c1d13..8b9c3464e3 100644 --- a/src/CommunityToolkit.Maui.Core/AppBuilderExtensions.shared.cs +++ b/src/CommunityToolkit.Maui.Core/AppBuilderExtensions.shared.cs @@ -20,7 +20,7 @@ public static class AppBuilderExtensions public static MauiAppBuilder UseMauiCommunityToolkitCore(this MauiAppBuilder builder, Action? options = null) { options?.Invoke(new Options()); - + #if ANDROID if (Options.ShouldUseStatusBarBehaviorOnAndroidModalPage) { @@ -54,7 +54,7 @@ public static MauiAppBuilder UseMauiCommunityToolkitCore(this MauiAppBuilder bui }); } #endif - + return builder; } } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Core/Options.cs b/src/CommunityToolkit.Maui.Core/Options.cs index 887114830a..4f6e400f4c 100644 --- a/src/CommunityToolkit.Maui.Core/Options.cs +++ b/src/CommunityToolkit.Maui.Core/Options.cs @@ -5,14 +5,14 @@ namespace CommunityToolkit.Maui.Core; /// public class Options { - internal static bool ShouldUseStatusBarBehaviorOnAndroidModalPage { get; private set; } = true; - - /// - /// Enables the use of the DialogFragment Lifecycle service for Android. - /// - /// true if yes or false if you want to implement your own. - /// - /// Default value is true. - /// - public void SetShouldUseStatusBarBehaviorOnAndroidModalPage(bool value) => ShouldUseStatusBarBehaviorOnAndroidModalPage = value; + internal static bool ShouldUseStatusBarBehaviorOnAndroidModalPage { get; private set; } = true; + + /// + /// Enables the use of the DialogFragment Lifecycle service for Android. + /// + /// true if yes or false if you want to implement your own. + /// + /// Default value is true. + /// + public void SetShouldUseStatusBarBehaviorOnAndroidModalPage(bool value) => ShouldUseStatusBarBehaviorOnAndroidModalPage = value; } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.android.cs b/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.android.cs index 42eb8ef059..af4d219247 100644 --- a/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.android.cs +++ b/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.android.cs @@ -93,9 +93,9 @@ static void HandleStatusBarColor(DialogFragment dialogFragment, AppCompatActivit isColorTransparent ? 0 : (int)WindowInsetsControllerAppearance.LightStatusBars, (int)WindowInsetsControllerAppearance.LightStatusBars); } - + dialogWindow.SetStatusBarColor(platformColor); - + if (!OperatingSystem.IsAndroidVersionAtLeast(35)) { dialogWindow.SetDecorFitsSystemWindows(!isColorTransparent); @@ -137,13 +137,13 @@ 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; } -} +} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.shared.cs b/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.shared.cs index 45ded5a1da..366ba38cc2 100644 --- a/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.shared.cs +++ b/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.shared.cs @@ -2,5 +2,5 @@ namespace CommunityToolkit.Maui.Core.Services; sealed partial class DialogFragmentService { - + } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Core/Services/FragmentLifecycleManager.android.cs b/src/CommunityToolkit.Maui.Core/Services/FragmentLifecycleManager.android.cs index 6164bbfc8d..20accbe20f 100644 --- a/src/CommunityToolkit.Maui.Core/Services/FragmentLifecycleManager.android.cs +++ b/src/CommunityToolkit.Maui.Core/Services/FragmentLifecycleManager.android.cs @@ -84,4 +84,4 @@ public override void OnFragmentViewDestroyed(FragmentManager fm, AndroidX.Fragme base.OnFragmentViewDestroyed(fm, f); dialogFragmentService.OnFragmentViewDestroyed(fm, f); } -} +} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui/AppBuilderExtensions.shared.cs b/src/CommunityToolkit.Maui/AppBuilderExtensions.shared.cs index 1e5ae387fe..5e45d440ee 100644 --- a/src/CommunityToolkit.Maui/AppBuilderExtensions.shared.cs +++ b/src/CommunityToolkit.Maui/AppBuilderExtensions.shared.cs @@ -10,31 +10,31 @@ namespace CommunityToolkit.Maui; /// public static class AppBuilderExtensions { - /// - /// Initializes the .NET MAUI Community Toolkit Library - /// - /// generated by - /// - /// initialized for - public static MauiAppBuilder UseMauiCommunityToolkit(this MauiAppBuilder builder, Action? options = null) - { - // Pass `null` because `options?.Invoke()` will set options on both `CommunityToolkit.Maui` and `CommunityToolkit.Maui.Core` - builder.UseMauiCommunityToolkitCore(null); + /// + /// Initializes the .NET MAUI Community Toolkit Library + /// + /// generated by + /// + /// initialized for + public static MauiAppBuilder UseMauiCommunityToolkit(this MauiAppBuilder builder, Action? options = null) + { + // Pass `null` because `options?.Invoke()` will set options on both `CommunityToolkit.Maui` and `CommunityToolkit.Maui.Core` + builder.UseMauiCommunityToolkitCore(null); - // Invokes options for both `CommunityToolkit.Maui` and `CommunityToolkit.Maui.Core` - options?.Invoke(new Options(builder)); + // Invokes options for both `CommunityToolkit.Maui` and `CommunityToolkit.Maui.Core` + options?.Invoke(new Options(builder)); - builder.Services.AddSingleton(); + builder.Services.AddSingleton(); - builder.ConfigureMauiHandlers(static h => - { - h.AddHandler(); - h.AddHandler(); - h.AddHandler(); - }); + builder.ConfigureMauiHandlers(static h => + { + h.AddHandler(); + h.AddHandler(); + h.AddHandler(); + }); - Popup.RemapForControls(); - NavigationBar.RemapForControls(); - return builder; - } + Popup.RemapForControls(); + NavigationBar.RemapForControls(); + return builder; + } } \ No newline at end of file From c78e1ba868bd9909cc2aca0934055a1ba3790f30 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Fri, 10 Jan 2025 12:18:16 -0800 Subject: [PATCH 15/19] Promote to `public` --- .../Services/DialogFragmentService.android.cs | 3 ++- .../Services/DialogFragmentService.shared.cs | 5 ++++- .../Services/FragmentLifecycleManager.android.cs | 6 ++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.android.cs b/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.android.cs index af4d219247..f76241146b 100644 --- a/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.android.cs +++ b/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.android.cs @@ -1,4 +1,5 @@ -using System.Diagnostics.CodeAnalysis; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using Android.Content; using Android.Views; using AndroidX.AppCompat.App; diff --git a/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.shared.cs b/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.shared.cs index 366ba38cc2..ce58d8cdc4 100644 --- a/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.shared.cs +++ b/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.shared.cs @@ -1,6 +1,9 @@ +using System.ComponentModel; + namespace CommunityToolkit.Maui.Core.Services; -sealed partial class DialogFragmentService +[EditorBrowsable(EditorBrowsableState.Never)] +public sealed partial class DialogFragmentService { } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Core/Services/FragmentLifecycleManager.android.cs b/src/CommunityToolkit.Maui.Core/Services/FragmentLifecycleManager.android.cs index 20accbe20f..6d68be885c 100644 --- a/src/CommunityToolkit.Maui.Core/Services/FragmentLifecycleManager.android.cs +++ b/src/CommunityToolkit.Maui.Core/Services/FragmentLifecycleManager.android.cs @@ -1,9 +1,11 @@ -using Android.Content; +using System.ComponentModel; +using Android.Content; using FragmentManager = AndroidX.Fragment.App.FragmentManager; namespace CommunityToolkit.Maui.Core.Services; -sealed class FragmentLifecycleManager(IDialogFragmentService dialogFragmentService) : FragmentManager.FragmentLifecycleCallbacks +[EditorBrowsable(EditorBrowsableState.Never)] +public sealed class FragmentLifecycleManager(IDialogFragmentService dialogFragmentService) : FragmentManager.FragmentLifecycleCallbacks { readonly IDialogFragmentService dialogFragmentService = dialogFragmentService; From d9145dd070a7122f540709f90b85c96debc43888 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Fri, 10 Jan 2025 12:18:30 -0800 Subject: [PATCH 16/19] Update CONTRIBUTING.md --- CONTRIBUTING.md | 61 ++++++++++++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 92ab2b83bd..df98d2f6eb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,6 +8,8 @@ Having that said, if you are a first-timer and you could use some help please re Furthermore, for anyone, we would like you to take into consideration the following guidelines. +## Kindness + ### Make an effort to be nice If you disagree, that's fine. We don't think about everything the same way, be respectful and at some point decide to agree to disagree. If a decision needs to be made, try to involve at least one other person without continuing an endless discussion. @@ -20,13 +22,13 @@ During a code review try to make a habit out of it to say at least one nice thin Remember English is not everyones native language. Written communication always lacks non-verbal communication. With written communication in a language that is not your native tongue it is even harder to express certain emotions. -Always assume that people mean to do right. Try to read a sentence a couple of times over and take things more literal. Try to place yourself in their shoes and see the message beyond the actual words. +Always assume that people mean to do right. Try to read a sentence a couple of times over and take things more literal. Try to place yourself in their shoes and see the message beyond the actual words. Things might come across different than they were intended, please keep that in mind and always check to see how someone meant it. If you're not sure, pull someone offline in a private channel on Twitter or email and chat about it for a bit. Maybe even jump on a call to collaborate. We're living in the 21st century, all the tools are there, why not use them to get to know each other and be friends?! Besides language, we understand that contributing to open-source mostly happens in your spare time. Remember that priorities might change and we can only spend our time once. This works as a two-way street: don't expect things to be solved instantly, but also please let us know if you do not have the capacity to finish work you have in progress. There is no shame in that. That way it's clear to other people that they can step in and take over. -### THANK YOU! +### THANK YOU Lastly, a big thank you for spending your precious time on our project. We appreciate any effort you make to help us with this project. @@ -45,7 +47,7 @@ We will take appropriate actions and measures if necessary. 1. Install latest stable [.NET SDK](https://dotnet.microsoft.com/en-us/download) 1. Install .NET MAUI workloads (we recommend using Visual Studio installer) -> You will need to complete a Contribution License Agreement before any pull request can be accepted. Complete the CLA at https://cla.dotnetfoundation.org/. This will also be triggered whenever you open a PR and the link should guide you through it. +> You will need to complete a Contribution License Agreement before any pull request can be accepted. Complete the CLA at . This will also be triggered whenever you open a PR and the link should guide you through it. ## Reporting a bug @@ -59,10 +61,10 @@ We always request a reproduction sample, and that's not to make your life hard o And most important: **Please, help us to help you ❤️** - ## Opening a PR process ### TL;DR + * Find an issue/feature, make sure that the issue/feature has been `Approved` and is welcomed (also see [New Features](https://github.com/CommunityToolkit/Maui#submitting-a-new-feature)) * Fork repository * Create branch @@ -73,13 +75,16 @@ And most important: **Please, help us to help you ❤️** ### Please consider -#### Tabs vs. Spaces?! +#### Tabs vs. Spaces? + [Tabs](https://www.reddit.com/r/javascript/comments/c8drjo/nobody_talks_about_the_real_reason_to_use_tabs/). #### Make your changes small, don't keep adding + We love your enthusiasm, but small changes and small PRs are easier to digest. We're all doing this in our spare time, it is easier to review a couple of small things and merge that and iterate from there than to have a PR with 100+ files changed that will sit there forever. #### Added features should have tests, a sample and documentation + We like quality as much as the next person, so please provide tests. In addition, we would want a new feature or change to be as clear as possible for other developers. Please add a sample to the sample app as part of your PR and also provide a PR to our [documentation repository](https://github.com/MicrosoftDocs/CommunityToolkit). @@ -88,67 +93,75 @@ In addition, we would want a new feature or change to be as clear as possible fo If you are unsure on where to locate the changes you need to make then please use the following section and flowchart. -![](https://user-images.githubusercontent.com/13558917/145694198-7addbd35-0e5f-4816-b351-759a01ec2672.png) +![New Code Workflow](https://user-images.githubusercontent.com/13558917/145694198-7addbd35-0e5f-4816-b351-759a01ec2672.png) ### CommunityToolkit.Maui.Core In general, this project will have all the basement to develop our Toolkit, including some primitive types, interfaces and base classes, base views, and common code. This will be referenced by other Frameworks/Toolkit based on .NET MAUI that wants to have the same features that us. -Here we will have some: +We ask that all classes in `CommunityToolkit.Maui.Core` are `public`. This allows developers to extend `CommunityToolkit.Maui.Core`. The `[EditorBrowsable(EditorBrowsableState.Never)]` attribute can be added to classes that we don't recommend developers discovering. -- BaseViews, could be Views that will be used by other Views, like PaddingButton (that's used by Snackbar) or the MauiPopup (used by Popup) that will be a native control implemented in a way that can work with our handler. This same approach is used here +Here is an example of classes that will -- Primitives, which will be base types that can be used by everyone, like our MathOperator. So other frameworks may not have the concept of Behavior or Converter but they can mimic them as helper classes/methods and use our primitives. +* BaseViews, could be Views that will be used by other Views, like PaddingButton (that's used by Snackbar) or the MauiPopup (used by Popup) that will be a native control implemented in a way that can work with our handler. This same approach is used here -- Common Code, this will be all generic code (platform-specific or not) that can be used by other Frameworks/Toolkits +* Primitives, which will be base types that can be used by everyone, like our MathOperator. So other frameworks may not have the concept of Behavior or Converter but they can mimic them as helper classes/methods and use our primitives. -- Layout Managers, were introduced on .NET MAUI and they live on Microsoft.Maui.Core so makes sense to have our managers on Core as well. +* Common Code, this will be all generic code (platform-specific or not) that can be used by other Frameworks/Toolkits -- Handlers, on Core will be the most general Handler with the majority of features. +* Layout Managers, were introduced on .NET MAUI and they live on Microsoft.Maui.Core so makes sense to have our managers on Core as well. -### CommunityToolkit.Maui: +* Handlers, on Core will be the most general Handler with the majority of features. + +### CommunityToolkit.Maui This project has a reference to the Core project. Here will live the implementation of our Controls, Views, Behaviors, Animations, etc. In other words, this project will work with the .NET MAUI and will be MVVM friendly. Also, other Toolkits/Frameworks can reference this package if needed. Here we will have some: -- View Implementation, with BindableProperties, support to attach effects, behaviors, triggers, and all that jazz. +* View Implementation, with BindableProperties, support to attach effects, behaviors, triggers, and all that jazz. -- Platform Configuration, that is Platform-specific features, that can relate to some control - like the ArrowDirection that is part of Popup and works just on iOS - or the application itself - like the StatusBarColorEffect from XCT. +* Platform Configuration, that is Platform-specific features, that can relate to some control - like the ArrowDirection that is part of Popup and works just on iOS - or the application itself - like the StatusBarColorEffect from XCT. -- Handlers Implementation, We will add to our PropertyMapper and/or CommandMapper any Platform Configuration that some Handler/View may have. We also can implement here some features that we think will not be great to have on Core. Here is a reference for this +* Handlers Implementation, We will add to our PropertyMapper and/or CommandMapper any Platform Configuration that some Handler/View may have. We also can implement here some features that we think will not be great to have on Core. Here is a reference for this -- Layout, will be the implementation of ours custom layouts and will use the Layout Managers on Core +* Layout, will be the implementation of ours custom layouts and will use the Layout Managers on Core ## Contributing Code - Best Practices ### Debug Logging + * Always use `Trace.WriteLine()` instead of `Debug.WriteLine` for debug logging because `Debug.WriteLine` is removed by the compiler in Release builds ### Methods Returning Task and ValueTask + * Always include a `CancellationToken` as a parameter to every method returning `Task` or `ValueTask` * If the method is public, provide a the default value for the `CancellationToken` (eg `CancellationToken token = default`) * If the method is not publc, do not provide a default value for the `CancellationToken` * Use `CancellationToken.ThrowIfCancellationRequested()` to verify the `CancellationToken` ### Enums + * Always use `Unknown` at index 0 for return types that may have a value that is not known * Always use `Default` at index 0 for option types that can use the system default option * Follow naming guidelines for tense... `SensorSpeed` not `SensorSpeeds` * Assign values (0,1,2,3) for all enums ### Property Names + * Include units only if one of the platforms includes it in their implementation. For instance HeadingMagneticNorth implies degrees on all platforms, but PressureInHectopascals is needed since platforms don't provide a consistent API for this. ### Units + * Use the standard units and most well accepted units when possible. For instance Hectopascals are used on UWP/Android and iOS uses Kilopascals so we have chosen Hectopascals. ### Pattern matching #### Null checking + * Prefer using `is` when checking for null instead of `==`. -e.g. +e.g. ```csharp // null @@ -180,9 +193,10 @@ if (something is Bucket bucket) ``` ### File Scoped Namespaces + * Use [file scoped namespaces](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/file-scoped-namespaces) to help reduce code verbosity. -e.g. +e.g. ```csharp namespace CommunityToolkit.Maui.Converters; @@ -215,13 +229,13 @@ In other words, `NotImplementedException` implies that a feature is still in dev ### Bug Fixes -If you're looking for something to fix, please browse [open issues](https://github.com/CommunityToolkit/Maui/issues). +If you're looking for something to fix, please browse [open issues](https://github.com/CommunityToolkit/Maui/issues). Follow the style used by the [.NET Foundation](https://github.com/dotnet/runtime/blob/master/docs/coding-guidelines/coding-style.md), with two primary exceptions: -- We do not use the `private` keyword as it is the default accessibility level in C#. -- We will **not** use `_` or `s_` as a prefix for internal or private field names -- We will use `camelCaseFieldName` for naming internal or private fields in both instance and static implementations +* We do not use the `private` keyword as it is the default accessibility level in C#. +* We will **not** use `_` or `s_` as a prefix for internal or private field names +* We will use `camelCaseFieldName` for naming internal or private fields in both instance and static implementations Read and follow our [Pull Request template](https://github.com/CommunityToolkit/Maui/blob/main/.github/PULL_REQUEST_TEMPLATE.md) @@ -230,6 +244,7 @@ Read and follow our [Pull Request template](https://github.com/CommunityToolkit/ To propose a change or new feature, review the guidance on [Submitting a New Feature](https://github.com/CommunityToolkit/Maui#submitting-a-new-feature). #### Non-Starter Topics + The following topics should generally not be proposed for discussion as they are non-starters: * Large renames of APIs From 1775a815095abffb22ffecaaa374a73cd87df443 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Fri, 10 Jan 2025 12:24:47 -0800 Subject: [PATCH 17/19] Add XML Comments --- .../Services/DialogFragmentService.android.cs | 3 +-- .../Services/DialogFragmentService.shared.cs | 5 +++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.android.cs b/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.android.cs index f76241146b..af4d219247 100644 --- a/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.android.cs +++ b/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.android.cs @@ -1,5 +1,4 @@ -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; using Android.Content; using Android.Views; using AndroidX.AppCompat.App; diff --git a/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.shared.cs b/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.shared.cs index ce58d8cdc4..fb168af753 100644 --- a/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.shared.cs +++ b/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.shared.cs @@ -1,8 +1,13 @@ using System.ComponentModel; +using System.Runtime.Versioning; namespace CommunityToolkit.Maui.Core.Services; +/// +/// An Android-specific service to support on modal pages +/// [EditorBrowsable(EditorBrowsableState.Never)] +[SupportedOSPlatform("Android")] public sealed partial class DialogFragmentService { From 0462d856c3961a1c47852a914fc91f1e69c9a81f Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Fri, 10 Jan 2025 12:28:54 -0800 Subject: [PATCH 18/19] Add `[EditorBrowsable(EditorBrowsableState.Never)]` --- .../Services/DialogFragmentService.android.cs | 70 +++++++++++-------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.android.cs b/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.android.cs index af4d219247..0aa2635f01 100644 --- a/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.android.cs +++ b/src/CommunityToolkit.Maui.Core/Services/DialogFragmentService.android.cs @@ -1,4 +1,5 @@ -using System.Diagnostics.CodeAnalysis; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using Android.Content; using Android.Views; using AndroidX.AppCompat.App; @@ -10,42 +11,52 @@ namespace CommunityToolkit.Maui.Core.Services; sealed partial class DialogFragmentService : IDialogFragmentService { + [EditorBrowsable(EditorBrowsableState.Never)] public void OnFragmentAttached(FragmentManager fm, Fragment f, Context context) { } + [EditorBrowsable(EditorBrowsableState.Never)] public void OnFragmentCreated(FragmentManager fm, Fragment f, Bundle? savedInstanceState) { } + [EditorBrowsable(EditorBrowsableState.Never)] public void OnFragmentDestroyed(FragmentManager fm, Fragment f) { } + [EditorBrowsable(EditorBrowsableState.Never)] public void OnFragmentDetached(FragmentManager fm, Fragment f) { } + [EditorBrowsable(EditorBrowsableState.Never)] public void OnFragmentPaused(FragmentManager fm, Fragment f) { } + [EditorBrowsable(EditorBrowsableState.Never)] public void OnFragmentPreAttached(FragmentManager fm, Fragment f, Context context) { } + [EditorBrowsable(EditorBrowsableState.Never)] public void OnFragmentPreCreated(FragmentManager fm, Fragment f, Bundle? savedInstanceState) { } + [EditorBrowsable(EditorBrowsableState.Never)] public void OnFragmentResumed(FragmentManager fm, Fragment f) { } + [EditorBrowsable(EditorBrowsableState.Never)] public void OnFragmentSaveInstanceState(FragmentManager fm, Fragment f, Bundle outState) { } + [EditorBrowsable(EditorBrowsableState.Never)] public void OnFragmentStarted(FragmentManager fm, Fragment f) { if (!TryConvertToDialogFragment(f, out var dialogFragment) || Microsoft.Maui.ApplicationModel.Platform.CurrentActivity is not AppCompatActivity activity) @@ -56,6 +67,34 @@ public void OnFragmentStarted(FragmentManager fm, Fragment f) HandleStatusBarColor(dialogFragment, activity); } + [EditorBrowsable(EditorBrowsableState.Never)] + public void OnFragmentStopped(FragmentManager fm, Fragment f) + { + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public void OnFragmentViewCreated(FragmentManager fm, Fragment f, Android.Views.View v, Bundle? savedInstanceState) + { + } + + [EditorBrowsable(EditorBrowsableState.Never)] + 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; + } + static void HandleStatusBarColor(DialogFragment dialogFragment, AppCompatActivity activity) { if (activity.Window is null) @@ -90,8 +129,8 @@ static void HandleStatusBarColor(DialogFragment dialogFragment, AppCompatActivit else { windowInsetsController.SetSystemBarsAppearance( - isColorTransparent ? 0 : (int)WindowInsetsControllerAppearance.LightStatusBars, - (int)WindowInsetsControllerAppearance.LightStatusBars); + isColorTransparent ? 0 : (int)WindowInsetsControllerAppearance.LightStatusBars, + (int)WindowInsetsControllerAppearance.LightStatusBars); } dialogWindow.SetStatusBarColor(platformColor); @@ -121,29 +160,4 @@ static void HandleStatusBarColor(DialogFragment dialogFragment, AppCompatActivit } } } - - 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; - } } \ No newline at end of file From b2e101939eb137e835b73554071c15dcce441d18 Mon Sep 17 00:00:00 2001 From: Pedro Jesus Date: Sat, 1 Feb 2025 19:44:23 -0300 Subject: [PATCH 19/19] Show it to the world --- .../Services/FragmentLifecycleManager.android.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CommunityToolkit.Maui.Core/Services/FragmentLifecycleManager.android.cs b/src/CommunityToolkit.Maui.Core/Services/FragmentLifecycleManager.android.cs index 6d68be885c..b5d6eba7e7 100644 --- a/src/CommunityToolkit.Maui.Core/Services/FragmentLifecycleManager.android.cs +++ b/src/CommunityToolkit.Maui.Core/Services/FragmentLifecycleManager.android.cs @@ -4,7 +4,6 @@ namespace CommunityToolkit.Maui.Core.Services; -[EditorBrowsable(EditorBrowsableState.Never)] public sealed class FragmentLifecycleManager(IDialogFragmentService dialogFragmentService) : FragmentManager.FragmentLifecycleCallbacks { readonly IDialogFragmentService dialogFragmentService = dialogFragmentService;