From 9e17cd131d16e7c0f9b416737164c5fd3ef5dbab Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+TheCodeTraveler@users.noreply.github.com> Date: Sun, 25 May 2025 14:46:20 -0700 Subject: [PATCH 01/10] Add `Close()` --- .../Extensions/PopupExtensions.shared.cs | 31 +++++++++++++++++++ .../Views/Popup/Popup.shared.cs | 5 +-- .../Views/Popup/PopupPage.shared.cs | 2 +- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/CommunityToolkit.Maui/Extensions/PopupExtensions.shared.cs b/src/CommunityToolkit.Maui/Extensions/PopupExtensions.shared.cs index e1ba2c9d28..3ac9234316 100644 --- a/src/CommunityToolkit.Maui/Extensions/PopupExtensions.shared.cs +++ b/src/CommunityToolkit.Maui/Extensions/PopupExtensions.shared.cs @@ -196,6 +196,37 @@ void HandlePopupClosed(object? sender, IPopupResult e) } } + /// + /// Close the Popup + /// + public static Task ClosePopup(this Page page) + { + ArgumentNullException.ThrowIfNull(page); + + return ClosePopup(page.Navigation); + } + + /// + /// Close the Popup + /// + public static Task ClosePopup(this INavigation navigation) + { + ArgumentNullException.ThrowIfNull(navigation); + + var currentVisibleModalPage = navigation.ModalStack.LastOrDefault(); + if (currentVisibleModalPage is null) + { + throw new PopupNotFoundException(); + } + + if (currentVisibleModalPage is not PopupPage) + { + throw new PopupBlockedException(currentVisibleModalPage); + } + + return navigation.PopModalAsync(false); + } + static PopupResult GetPopupResult(in IPopupResult result) { return result switch diff --git a/src/CommunityToolkit.Maui/Views/Popup/Popup.shared.cs b/src/CommunityToolkit.Maui/Views/Popup/Popup.shared.cs index 894ace2b01..a006d555bc 100644 --- a/src/CommunityToolkit.Maui/Views/Popup/Popup.shared.cs +++ b/src/CommunityToolkit.Maui/Views/Popup/Popup.shared.cs @@ -130,5 +130,6 @@ public partial class Popup : Popup public virtual Task Close(T result, CancellationToken token = default) => GetPopupPage().Close(new PopupResult(result, false), token); } -class InvalidPopupOperationException(string message) : InvalidOperationException(message); -sealed class PopupNotFoundException() : InvalidPopupOperationException($"Unable to close popup: could not locate {nameof(PopupPage)}. {nameof(PopupExtensions.ShowPopup)} or {nameof(PopupExtensions.ShowPopupAsync)} must be called before {nameof(Popup.Close)}. If using a custom implementation of {nameof(Popup)}, override the {nameof(Popup.Close)} method"); \ No newline at end of file +sealed class PopupNotFoundException() : InvalidPopupOperationException($"Unable to close popup: could not locate {nameof(PopupPage)}. {nameof(PopupExtensions.ShowPopup)} or {nameof(PopupExtensions.ShowPopupAsync)} must be called before {nameof(Popup.Close)}. If using a custom implementation of {nameof(Popup)}, override the {nameof(Popup.Close)} method"); +sealed class PopupBlockedException(in Page currentVisibleModalPage): InvalidPopupOperationException($"Unable to close Popup because it is blocked by the Modal Page {currentVisibleModalPage.GetType().FullName}. Please call `{nameof(Page.Navigation)}.{nameof(Page.Navigation.PopModalAsync)}()` to first remove {currentVisibleModalPage.GetType().FullName} from the {nameof(Page.Navigation.ModalStack)}"); +class InvalidPopupOperationException(in string message) : InvalidOperationException(message); diff --git a/src/CommunityToolkit.Maui/Views/Popup/PopupPage.shared.cs b/src/CommunityToolkit.Maui/Views/Popup/PopupPage.shared.cs index 550ed6de91..3a8a975158 100644 --- a/src/CommunityToolkit.Maui/Views/Popup/PopupPage.shared.cs +++ b/src/CommunityToolkit.Maui/Views/Popup/PopupPage.shared.cs @@ -95,7 +95,7 @@ public async Task Close(PopupResult result, CancellationToken token = default) if (Navigation.ModalStack[^1] is Microsoft.Maui.Controls.Page currentVisibleModalPage && currentVisibleModalPage != popupPageToClose) { - throw new InvalidPopupOperationException($"Unable to close Popup because it is blocked by the Modal Page {currentVisibleModalPage.GetType().FullName}. Please call `{nameof(Navigation)}.{nameof(Navigation.PopModalAsync)}()` to first remove {currentVisibleModalPage.GetType().FullName} from the {nameof(Navigation.ModalStack)}"); + throw new PopupBlockedException(currentVisibleModalPage); } // We call `.ThrowIfCancellationRequested()` again to avoid a race condition where a developer cancels the CancellationToken after we check for an InvalidOperationException From 82552c6927d845d890ebe04b9a2074c7d0e78fa1 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+TheCodeTraveler@users.noreply.github.com> Date: Sun, 25 May 2025 14:47:40 -0700 Subject: [PATCH 02/10] Update PopupExtensions.shared.cs --- .../Extensions/PopupExtensions.shared.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CommunityToolkit.Maui/Extensions/PopupExtensions.shared.cs b/src/CommunityToolkit.Maui/Extensions/PopupExtensions.shared.cs index 3ac9234316..f4fc0281a3 100644 --- a/src/CommunityToolkit.Maui/Extensions/PopupExtensions.shared.cs +++ b/src/CommunityToolkit.Maui/Extensions/PopupExtensions.shared.cs @@ -197,7 +197,7 @@ void HandlePopupClosed(object? sender, IPopupResult e) } /// - /// Close the Popup + /// Close the Visible Popup /// public static Task ClosePopup(this Page page) { @@ -207,7 +207,7 @@ public static Task ClosePopup(this Page page) } /// - /// Close the Popup + /// Close the Visible Popup /// public static Task ClosePopup(this INavigation navigation) { From 6558dbdc33bc58460f51c8a5cc696bf9eb213d8a Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+TheCodeTraveler@users.noreply.github.com> Date: Sun, 25 May 2025 14:52:00 -0700 Subject: [PATCH 03/10] Add CancellationToken --- .../Extensions/PopupExtensions.shared.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/CommunityToolkit.Maui/Extensions/PopupExtensions.shared.cs b/src/CommunityToolkit.Maui/Extensions/PopupExtensions.shared.cs index f4fc0281a3..f0b8e92e7b 100644 --- a/src/CommunityToolkit.Maui/Extensions/PopupExtensions.shared.cs +++ b/src/CommunityToolkit.Maui/Extensions/PopupExtensions.shared.cs @@ -199,17 +199,17 @@ void HandlePopupClosed(object? sender, IPopupResult e) /// /// Close the Visible Popup /// - public static Task ClosePopup(this Page page) + public static Task ClosePopup(this Page page, CancellationToken token = default) { ArgumentNullException.ThrowIfNull(page); - return ClosePopup(page.Navigation); + return ClosePopup(page.Navigation, token); } /// /// Close the Visible Popup /// - public static Task ClosePopup(this INavigation navigation) + public static Task ClosePopup(this INavigation navigation, CancellationToken token = default) { ArgumentNullException.ThrowIfNull(navigation); @@ -223,8 +223,14 @@ public static Task ClosePopup(this INavigation navigation) { throw new PopupBlockedException(currentVisibleModalPage); } - - return navigation.PopModalAsync(false); + + // At first glance, calling `token.ThrowIfCancellationRequested()` may look redundant given that we are using `.WaitAsync(token)` in the next step, + // However, `Navigation.PopModalAsync()` may return a completed Task, and when a completed Task is returned, `.WaitAsync(token)` is never invoked. + // In other words, `.WaitAsync(token)` may not throw an `OperationCanceledException` as expected which is why we call `.ThrowIfCancellationRequested()` again here + // Here's the .NET MAUI Source code demonstrating that `Navigation.PopModalAsync()` sometimes returns `Task.FromResult()`: https://github.com/dotnet/maui/blob/e5c252ec7f430cbaf28c8a815a249e3270b49844/src/Controls/src/Core/NavigationProxy.cs#L192-L196 + token.ThrowIfCancellationRequested(); + + return navigation.PopModalAsync(false).WaitAsync(token); } static PopupResult GetPopupResult(in IPopupResult result) From 1492eb276a8b9ad074d7ab1a481eec0f0f40eeca Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+TheCodeTraveler@users.noreply.github.com> Date: Sun, 25 May 2025 15:08:18 -0700 Subject: [PATCH 04/10] Add Unit Tests --- .../Extensions/PopupExtensionsTests.cs | 57 +++++++++++++++++-- .../Extensions/PopupExtensions.shared.cs | 2 + 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/CommunityToolkit.Maui.UnitTests/Extensions/PopupExtensionsTests.cs b/src/CommunityToolkit.Maui.UnitTests/Extensions/PopupExtensionsTests.cs index baef743785..e42fe9a2a0 100644 --- a/src/CommunityToolkit.Maui.UnitTests/Extensions/PopupExtensionsTests.cs +++ b/src/CommunityToolkit.Maui.UnitTests/Extensions/PopupExtensionsTests.cs @@ -31,8 +31,45 @@ public PopupExtensionsTests() navigation = page.Navigation; } - [Fact] - public void ShowPopupAsync_WithPopupType_ShowsPopup() + [Fact(Timeout = (int)TestDuration.Short)] + public async Task ClosePopup_TokenExpired_ShouldThrowOperationCancelledException() + { + // Arrange + var cts = new CancellationTokenSource(); + + // Act + await cts.CancelAsync(); + + // Assert + await Assert.ThrowsAsync(() => navigation.ClosePopup(cts.Token)); + } + + [Fact(Timeout = (int)TestDuration.Short)] + public async Task ClosePopup_NoExistingPopup_ShouldThrowPopupNotFoundException() + { + // Arrange + + // Act + + // Assert + await Assert.ThrowsAsync(() => navigation.ClosePopup(TestContext.Current.CancellationToken)); + } + + [Fact(Timeout = (int)TestDuration.Short)] + public async Task ClosePopup_PopupBlocked_ShouldThrowPopupBlockedException() + { + // Arrange + + // Act + navigation.ShowPopup(new Button()); + await navigation.PushModalAsync(new ContentPage()); + + // Assert + await Assert.ThrowsAsync(() => navigation.ClosePopup(TestContext.Current.CancellationToken)); + } + + [Fact(Timeout = (int)TestDuration.Short)] + public async Task ShowPopupAsync_WithPopupType_ShowsPopupAndClosesPopup() { // Arrange var selfClosingPopup = ServiceProvider.GetRequiredService() ?? throw new InvalidOperationException(); @@ -43,10 +80,16 @@ public void ShowPopupAsync_WithPopupType_ShowsPopup() // Assert Assert.Single(navigation.ModalStack); Assert.IsType(navigation.ModalStack[0]); + + // Act + await navigation.ClosePopup(TestContext.Current.CancellationToken); + + // Assert + Assert.Empty(navigation.ModalStack); } - [Fact] - public void ShowPopupAsync_Shell_WithPopupType_ShowsPopup() + [Fact(Timeout = (int)TestDuration.Short)] + public async Task ShowPopupAsync_Shell_WithPopupType_ShowsPopupAndClosesPopup() { // Arrange var shell = new Shell(); @@ -63,6 +106,12 @@ public void ShowPopupAsync_Shell_WithPopupType_ShowsPopup() // Assert Assert.Single(shellNavigation.ModalStack); Assert.IsType(shellNavigation.ModalStack[0]); + + // Act + await navigation.ClosePopup(TestContext.Current.CancellationToken); + + // Assert + Assert.Empty(navigation.ModalStack); } [Fact] diff --git a/src/CommunityToolkit.Maui/Extensions/PopupExtensions.shared.cs b/src/CommunityToolkit.Maui/Extensions/PopupExtensions.shared.cs index f0b8e92e7b..1cffb5b102 100644 --- a/src/CommunityToolkit.Maui/Extensions/PopupExtensions.shared.cs +++ b/src/CommunityToolkit.Maui/Extensions/PopupExtensions.shared.cs @@ -211,6 +211,8 @@ public static Task ClosePopup(this Page page, CancellationToken token = default) /// public static Task ClosePopup(this INavigation navigation, CancellationToken token = default) { + token.ThrowIfCancellationRequested(); + ArgumentNullException.ThrowIfNull(navigation); var currentVisibleModalPage = navigation.ModalStack.LastOrDefault(); From f8dfe65bf7a6c8a748ee022247947e7c9b908bc1 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+TheCodeTraveler@users.noreply.github.com> Date: Sun, 25 May 2025 15:21:02 -0700 Subject: [PATCH 05/10] Add Support for Shell --- .../Extensions/PopupExtensionsTests.cs | 2 +- .../Views/Popup/PopupPageTests.cs | 3 ++- .../Extensions/PopupExtensions.shared.cs | 17 +++++++---------- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/CommunityToolkit.Maui.UnitTests/Extensions/PopupExtensionsTests.cs b/src/CommunityToolkit.Maui.UnitTests/Extensions/PopupExtensionsTests.cs index e42fe9a2a0..83a0c5b16b 100644 --- a/src/CommunityToolkit.Maui.UnitTests/Extensions/PopupExtensionsTests.cs +++ b/src/CommunityToolkit.Maui.UnitTests/Extensions/PopupExtensionsTests.cs @@ -166,7 +166,7 @@ public async Task ShowPopupAsync_AwaitingShowPopupAsync_EnsurePreviousPopupClose } [Fact(Timeout = (int)TestDuration.Short)] - public async Task qShowPopupAsync_Shell_AwaitingShowPopupAsync_EnsurePreviousPopupClosed() + public async Task ShowPopupAsync_Shell_AwaitingShowPopupAsync_EnsurePreviousPopupClosed() { // Arrange var shell = new Shell(); diff --git a/src/CommunityToolkit.Maui.UnitTests/Views/Popup/PopupPageTests.cs b/src/CommunityToolkit.Maui.UnitTests/Views/Popup/PopupPageTests.cs index 23746cecc6..6eff6d506c 100644 --- a/src/CommunityToolkit.Maui.UnitTests/Views/Popup/PopupPageTests.cs +++ b/src/CommunityToolkit.Maui.UnitTests/Views/Popup/PopupPageTests.cs @@ -196,7 +196,8 @@ public async Task PopupPageT_CloseAfterAdditionalModalPage_ShouldThrowInvalidOpe await navigation.PushModalAsync(new ContentPage()); // Assert - await Assert.ThrowsAsync(async () => await popupPage.Close(new PopupResult(false), CancellationToken.None)); + await Assert.ThrowsAsync(async () => await popupPage.Close(new PopupResult(false), CancellationToken.None)); + await Assert.ThrowsAnyAsync(async () => await popupPage.Close(new PopupResult(false), CancellationToken.None)); await Assert.ThrowsAnyAsync(async () => await popupPage.Close(new PopupResult(false), CancellationToken.None)); } diff --git a/src/CommunityToolkit.Maui/Extensions/PopupExtensions.shared.cs b/src/CommunityToolkit.Maui/Extensions/PopupExtensions.shared.cs index 1cffb5b102..7954406c38 100644 --- a/src/CommunityToolkit.Maui/Extensions/PopupExtensions.shared.cs +++ b/src/CommunityToolkit.Maui/Extensions/PopupExtensions.shared.cs @@ -215,24 +215,21 @@ public static Task ClosePopup(this INavigation navigation, CancellationToken tok ArgumentNullException.ThrowIfNull(navigation); - var currentVisibleModalPage = navigation.ModalStack.LastOrDefault(); + var currentVisibleModalPage = Shell.Current is null + ? navigation.ModalStack.LastOrDefault() + : Shell.Current.Navigation.ModalStack.LastOrDefault(); + if (currentVisibleModalPage is null) { throw new PopupNotFoundException(); } - if (currentVisibleModalPage is not PopupPage) + if (currentVisibleModalPage is not PopupPage popupPage) { throw new PopupBlockedException(currentVisibleModalPage); } - - // At first glance, calling `token.ThrowIfCancellationRequested()` may look redundant given that we are using `.WaitAsync(token)` in the next step, - // However, `Navigation.PopModalAsync()` may return a completed Task, and when a completed Task is returned, `.WaitAsync(token)` is never invoked. - // In other words, `.WaitAsync(token)` may not throw an `OperationCanceledException` as expected which is why we call `.ThrowIfCancellationRequested()` again here - // Here's the .NET MAUI Source code demonstrating that `Navigation.PopModalAsync()` sometimes returns `Task.FromResult()`: https://github.com/dotnet/maui/blob/e5c252ec7f430cbaf28c8a815a249e3270b49844/src/Controls/src/Core/NavigationProxy.cs#L192-L196 - token.ThrowIfCancellationRequested(); - - return navigation.PopModalAsync(false).WaitAsync(token); + + return popupPage.Close(new PopupResult(false), token); } static PopupResult GetPopupResult(in IPopupResult result) From 02e1ef890ba4ff4e9e8e8f10716d1b375bed3517 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+TheCodeTraveler@users.noreply.github.com> Date: Sun, 25 May 2025 16:03:50 -0700 Subject: [PATCH 06/10] Add .NET 10 Compiler Error This ensures we do not forget to remove the Obsolete Popup classes in our .NET 10 release --- .../Handlers/Popup/PopupHandler.shared.cs | 3 +++ src/CommunityToolkit.Maui.Core/Interfaces/IPopup.shared.cs | 3 +++ .../Views/Popup/MauiPopup.android.cs | 3 +++ src/CommunityToolkit.Maui.Core/Views/Popup/MauiPopup.macios.cs | 3 +++ src/CommunityToolkit.Maui.Core/Views/Popup/MauiPopup.tizen.cs | 3 +++ .../Views/Popup/PopupExtensions.android.cs | 3 +++ .../Views/Popup/PopupExtensions.macios.cs | 3 +++ .../Views/Popup/PopupExtensions.windows.cs | 3 +++ .../Views/Popup/PopupOverlay.windows.cs | 3 +++ 9 files changed, 27 insertions(+) diff --git a/src/CommunityToolkit.Maui.Core/Handlers/Popup/PopupHandler.shared.cs b/src/CommunityToolkit.Maui.Core/Handlers/Popup/PopupHandler.shared.cs index 73eee85143..52ef719e8a 100644 --- a/src/CommunityToolkit.Maui.Core/Handlers/Popup/PopupHandler.shared.cs +++ b/src/CommunityToolkit.Maui.Core/Handlers/Popup/PopupHandler.shared.cs @@ -3,6 +3,9 @@ namespace CommunityToolkit.Maui.Core.Handlers; /// /// Handler Popup control /// +#if NET10_0_OR_GREATER +#error Remove PopupHandler +#endif [Obsolete($"{nameof(PopupHandler)} is no longer used by {nameof(CommunityToolkit)}.{nameof(Maui)} and will be removed in .NET 10")] public partial class PopupHandler { diff --git a/src/CommunityToolkit.Maui.Core/Interfaces/IPopup.shared.cs b/src/CommunityToolkit.Maui.Core/Interfaces/IPopup.shared.cs index bc048dc5ff..94552c27ec 100644 --- a/src/CommunityToolkit.Maui.Core/Interfaces/IPopup.shared.cs +++ b/src/CommunityToolkit.Maui.Core/Interfaces/IPopup.shared.cs @@ -6,6 +6,9 @@ namespace CommunityToolkit.Maui.Core; /// /// Represents a small View that pops up at front the Page. /// +#if NET10_0_OR_GREATER +#error Remove IPopup +#endif [Obsolete($"{nameof(IPopup)} is no longer used by {nameof(CommunityToolkit)}.{nameof(Maui)} and will be removed in .NET 10")] public interface IPopup : IElement, IVisualTreeElement, IAsynchronousHandler { diff --git a/src/CommunityToolkit.Maui.Core/Views/Popup/MauiPopup.android.cs b/src/CommunityToolkit.Maui.Core/Views/Popup/MauiPopup.android.cs index 36515ba057..bc7466a867 100644 --- a/src/CommunityToolkit.Maui.Core/Views/Popup/MauiPopup.android.cs +++ b/src/CommunityToolkit.Maui.Core/Views/Popup/MauiPopup.android.cs @@ -9,6 +9,9 @@ namespace CommunityToolkit.Maui.Core.Views; /// /// The native implementation of Popup control. /// +#if NET10_0_OR_GREATER +#error Remove MauiPopup +#endif [Obsolete($"{nameof(MauiPopup)} is no longer used by {nameof(CommunityToolkit)}.{nameof(Maui)} and will be removed in .NET 10")] public class MauiPopup : Dialog, IDialogInterfaceOnCancelListener { diff --git a/src/CommunityToolkit.Maui.Core/Views/Popup/MauiPopup.macios.cs b/src/CommunityToolkit.Maui.Core/Views/Popup/MauiPopup.macios.cs index e0842fd4ca..f8e3717bdf 100644 --- a/src/CommunityToolkit.Maui.Core/Views/Popup/MauiPopup.macios.cs +++ b/src/CommunityToolkit.Maui.Core/Views/Popup/MauiPopup.macios.cs @@ -13,6 +13,9 @@ namespace CommunityToolkit.Maui.Core.Views; /// /// An instance of . /// If is null an exception will be thrown. +#if NET10_0_OR_GREATER +#error Remove MauiPopup +#endif [Obsolete($"{nameof(MauiPopup)} is no longer used by {nameof(CommunityToolkit)}.{nameof(Maui)} and will be removed in .NET 10")] public class MauiPopup(IMauiContext mauiContext) : UIViewController { diff --git a/src/CommunityToolkit.Maui.Core/Views/Popup/MauiPopup.tizen.cs b/src/CommunityToolkit.Maui.Core/Views/Popup/MauiPopup.tizen.cs index 8c157130eb..3a390597ed 100644 --- a/src/CommunityToolkit.Maui.Core/Views/Popup/MauiPopup.tizen.cs +++ b/src/CommunityToolkit.Maui.Core/Views/Popup/MauiPopup.tizen.cs @@ -10,6 +10,9 @@ namespace CommunityToolkit.Maui.Core.Views; /// /// The native implementation of Popup control. /// +#if NET10_0_OR_GREATER +#error Remove MauiPopup +#endif [Obsolete($"{nameof(MauiPopup)} is no longer used by {nameof(CommunityToolkit)}.{nameof(Maui)} and will be removed in .NET 10")] public class MauiPopup : Popup { diff --git a/src/CommunityToolkit.Maui.Core/Views/Popup/PopupExtensions.android.cs b/src/CommunityToolkit.Maui.Core/Views/Popup/PopupExtensions.android.cs index f9d5f4d21f..562d854b00 100644 --- a/src/CommunityToolkit.Maui.Core/Views/Popup/PopupExtensions.android.cs +++ b/src/CommunityToolkit.Maui.Core/Views/Popup/PopupExtensions.android.cs @@ -14,6 +14,9 @@ namespace CommunityToolkit.Maui.Core.Views; /// /// Extension class where Helper methods for Popup lives. /// +#if NET10_0_OR_GREATER +#error Remove MauiPopup +#endif [Obsolete($"{nameof(PopupExtensions)} is no longer used by {nameof(CommunityToolkit)}.{nameof(Maui)} and will be removed in .NET 10")] public static class PopupExtensions { diff --git a/src/CommunityToolkit.Maui.Core/Views/Popup/PopupExtensions.macios.cs b/src/CommunityToolkit.Maui.Core/Views/Popup/PopupExtensions.macios.cs index 9342c90fd4..016b05fb29 100644 --- a/src/CommunityToolkit.Maui.Core/Views/Popup/PopupExtensions.macios.cs +++ b/src/CommunityToolkit.Maui.Core/Views/Popup/PopupExtensions.macios.cs @@ -5,6 +5,9 @@ namespace CommunityToolkit.Maui.Core.Views; /// /// Extension class where Helper methods for Popup lives. /// +#if NET10_0_OR_GREATER +#error Remove PopupExtensions +#endif [Obsolete($"{nameof(PopupExtensions)} is no longer used by {nameof(CommunityToolkit)}.{nameof(Maui)} and will be removed in .NET 10")] public static class PopupExtensions { diff --git a/src/CommunityToolkit.Maui.Core/Views/Popup/PopupExtensions.windows.cs b/src/CommunityToolkit.Maui.Core/Views/Popup/PopupExtensions.windows.cs index 6a910d143d..1f2b73e5b6 100644 --- a/src/CommunityToolkit.Maui.Core/Views/Popup/PopupExtensions.windows.cs +++ b/src/CommunityToolkit.Maui.Core/Views/Popup/PopupExtensions.windows.cs @@ -10,6 +10,9 @@ namespace CommunityToolkit.Maui.Core.Views; /// /// Extension class where Helper methods for Popup lives. /// +#if NET10_0_OR_GREATER +#error Remove PopupExtensions +#endif [Obsolete($"{nameof(PopupExtensions)} is no longer used by {nameof(CommunityToolkit)}.{nameof(Maui)} and will be removed in .NET 10")] public static class PopupExtensions { diff --git a/src/CommunityToolkit.Maui.Core/Views/Popup/PopupOverlay.windows.cs b/src/CommunityToolkit.Maui.Core/Views/Popup/PopupOverlay.windows.cs index d6cb21b566..c7019535bd 100644 --- a/src/CommunityToolkit.Maui.Core/Views/Popup/PopupOverlay.windows.cs +++ b/src/CommunityToolkit.Maui.Core/Views/Popup/PopupOverlay.windows.cs @@ -3,6 +3,9 @@ /// /// Displays Overlay in the Popup background. /// +#if NET10_0_OR_GREATER +#error Remove PopupOverlay +#endif [Obsolete($"{nameof(PopupOverlay)} is no longer used by {nameof(CommunityToolkit)}.{nameof(Maui)} and will be removed in .NET 10")] class PopupOverlay : WindowOverlay { From db98d1c6cba23e7479b968eb7cefda655500c594 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+TheCodeTraveler@users.noreply.github.com> Date: Mon, 26 May 2025 12:26:39 -0700 Subject: [PATCH 07/10] Rename `Close()` -> `CloseAsync()` --- .../Views/Popups/ButtonPopup.xaml.cs | 2 +- .../Views/Popups/MultipleButtonPopup.xaml.cs | 4 ++-- .../Popups/NoOutsideTapDismissPopup.xaml.cs | 2 +- .../Views/Popups/ReturnResultPopup.xaml.cs | 2 +- .../Views/Popups/TransparentPopup.xaml.cs | 2 +- .../Extensions/PopupExtensionsTests.cs | 12 +++++------ .../Services/PopupServiceTests.cs | 4 ++-- .../Views/Popup/PopupPageTests.cs | 18 ++++++++--------- .../Views/Popup/PopupTests.cs | 20 +++++++++---------- .../Extensions/PopupExtensions.shared.cs | 6 +++--- .../Services/PopupService.shared.cs | 4 ++-- .../Views/Popup/Popup.shared.cs | 10 +++++----- .../Views/Popup/PopupPage.shared.cs | 8 ++++---- 13 files changed, 47 insertions(+), 47 deletions(-) diff --git a/samples/CommunityToolkit.Maui.Sample/Views/Popups/ButtonPopup.xaml.cs b/samples/CommunityToolkit.Maui.Sample/Views/Popups/ButtonPopup.xaml.cs index 28972839ba..06840d20cc 100644 --- a/samples/CommunityToolkit.Maui.Sample/Views/Popups/ButtonPopup.xaml.cs +++ b/samples/CommunityToolkit.Maui.Sample/Views/Popups/ButtonPopup.xaml.cs @@ -9,6 +9,6 @@ public ButtonPopup() void Button_Clicked(object? sender, EventArgs e) { - Close(); + CloseAsync(); } } \ No newline at end of file diff --git a/samples/CommunityToolkit.Maui.Sample/Views/Popups/MultipleButtonPopup.xaml.cs b/samples/CommunityToolkit.Maui.Sample/Views/Popups/MultipleButtonPopup.xaml.cs index 1406b4da52..a671119f2b 100644 --- a/samples/CommunityToolkit.Maui.Sample/Views/Popups/MultipleButtonPopup.xaml.cs +++ b/samples/CommunityToolkit.Maui.Sample/Views/Popups/MultipleButtonPopup.xaml.cs @@ -9,11 +9,11 @@ public MultipleButtonPopup() async void Cancel_Clicked(object? sender, EventArgs e) { - await Close(false); + await CloseAsync(false); } async void Okay_Clicked(object? sender, EventArgs e) { - await Close(true); + await CloseAsync(true); } } \ No newline at end of file diff --git a/samples/CommunityToolkit.Maui.Sample/Views/Popups/NoOutsideTapDismissPopup.xaml.cs b/samples/CommunityToolkit.Maui.Sample/Views/Popups/NoOutsideTapDismissPopup.xaml.cs index 10bc6a2a3b..2949a1d246 100644 --- a/samples/CommunityToolkit.Maui.Sample/Views/Popups/NoOutsideTapDismissPopup.xaml.cs +++ b/samples/CommunityToolkit.Maui.Sample/Views/Popups/NoOutsideTapDismissPopup.xaml.cs @@ -11,7 +11,7 @@ public NoOutsideTapDismissPopup() async void Button_Clicked(object? sender, EventArgs e) { - await Close(); + await CloseAsync(); await Toast.Make("Popup Dismissed By Button").Show(); } } \ No newline at end of file diff --git a/samples/CommunityToolkit.Maui.Sample/Views/Popups/ReturnResultPopup.xaml.cs b/samples/CommunityToolkit.Maui.Sample/Views/Popups/ReturnResultPopup.xaml.cs index 166aa1f746..a2699efcc8 100644 --- a/samples/CommunityToolkit.Maui.Sample/Views/Popups/ReturnResultPopup.xaml.cs +++ b/samples/CommunityToolkit.Maui.Sample/Views/Popups/ReturnResultPopup.xaml.cs @@ -9,6 +9,6 @@ public ReturnResultPopup() async void Button_Clicked(object? sender, EventArgs e) { - await Close("Close button tapped"); + await CloseAsync("Close button tapped"); } } \ No newline at end of file diff --git a/samples/CommunityToolkit.Maui.Sample/Views/Popups/TransparentPopup.xaml.cs b/samples/CommunityToolkit.Maui.Sample/Views/Popups/TransparentPopup.xaml.cs index aa325fd9a7..cfca5f7043 100644 --- a/samples/CommunityToolkit.Maui.Sample/Views/Popups/TransparentPopup.xaml.cs +++ b/samples/CommunityToolkit.Maui.Sample/Views/Popups/TransparentPopup.xaml.cs @@ -6,6 +6,6 @@ public partial class TransparentPopup : Maui.Views.Popup void CloseButtonClicked(object? sender, EventArgs args) { - Close(); + CloseAsync(); } } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.UnitTests/Extensions/PopupExtensionsTests.cs b/src/CommunityToolkit.Maui.UnitTests/Extensions/PopupExtensionsTests.cs index 66e85dd084..9a99a292f9 100644 --- a/src/CommunityToolkit.Maui.UnitTests/Extensions/PopupExtensionsTests.cs +++ b/src/CommunityToolkit.Maui.UnitTests/Extensions/PopupExtensionsTests.cs @@ -858,7 +858,7 @@ public async Task ShowPopupAsyncWithView_ShouldValidateProperBindingContext() var popupPage = (PopupPage)navigation.ModalStack[0]; - await popupPage.Close(new PopupResult(null, false), TestContext.Current.CancellationToken); + await popupPage.CloseAsync(new PopupResult(null, false), TestContext.Current.CancellationToken); await showPopupTask; // Assert @@ -884,7 +884,7 @@ public async Task ShowPopupAsyncWithView_Shell_ShouldValidateProperBindingContex var showPopupTask = shell.ShowPopupAsync(view, PopupOptions.Empty, shellParameters, TestContext.Current.CancellationToken); var popupPage = (PopupPage)shellNavigation.ModalStack[0]; - await popupPage.Close(new PopupResult(null, false), TestContext.Current.CancellationToken); + await popupPage.CloseAsync(new PopupResult(null, false), TestContext.Current.CancellationToken); await showPopupTask; @@ -946,7 +946,7 @@ public async Task ShowPopupAsyncWithView_ShouldReturnResultOnceClosed() var popupPage = (PopupPage)navigation.ModalStack[0]; - await popupPage.Close(expectedPopupResult, TestContext.Current.CancellationToken); + await popupPage.CloseAsync(expectedPopupResult, TestContext.Current.CancellationToken); var actualPopupResult = await showPopupTask; // Assert @@ -976,7 +976,7 @@ public async Task ShowPopupAsyncWithView_Shell_ShouldReturnResultOnceClosed() var popupPage = (PopupPage)shellNavigation.ModalStack[0]; - await popupPage.Close(expectedPopupResult, TestContext.Current.CancellationToken); + await popupPage.CloseAsync(expectedPopupResult, TestContext.Current.CancellationToken); var actualPopupResult = await showPopupTask; // Assert @@ -1054,7 +1054,7 @@ public async Task ShowPopupAsync_ShouldReturnNullResult_WhenPopupIsClosedWithout var showPopupTask = navigation.ShowPopupAsync(new Popup(), PopupOptions.Empty, TestContext.Current.CancellationToken); var popupPage = (PopupPage)navigation.ModalStack.Last(); - await popupPage.Close(new PopupResult(true), TestContext.Current.CancellationToken); + await popupPage.CloseAsync(new PopupResult(true), TestContext.Current.CancellationToken); var result = await showPopupTask; // Assert @@ -1078,7 +1078,7 @@ public async Task ShowPopupAsync_Shell_ShouldReturnNullResult_WhenPopupIsClosedW var showPopupTask = shell.ShowPopupAsync(new Popup(), PopupOptions.Empty, shellParameters, TestContext.Current.CancellationToken); var popupPage = (PopupPage)shellNavigation.ModalStack.Last(); - await popupPage.Close(new PopupResult(true), TestContext.Current.CancellationToken); + await popupPage.CloseAsync(new PopupResult(true), TestContext.Current.CancellationToken); var result = await showPopupTask; // Assert diff --git a/src/CommunityToolkit.Maui.UnitTests/Services/PopupServiceTests.cs b/src/CommunityToolkit.Maui.UnitTests/Services/PopupServiceTests.cs index 9de351c99f..06acca41e7 100644 --- a/src/CommunityToolkit.Maui.UnitTests/Services/PopupServiceTests.cs +++ b/src/CommunityToolkit.Maui.UnitTests/Services/PopupServiceTests.cs @@ -303,11 +303,11 @@ async void HandleTick(object? sender, EventArgs e) timer.Tick -= HandleTick; try { - await Close(Result); + await CloseAsync(Result); } catch (InvalidOperationException) { - // If test has already ended, Popup.Close will throw an InvalidOperationException + // If test has already ended, Popup.CloseAsync will throw an InvalidOperationException // because all Popups are removed from ModalStack in BaseHandlerTest.DisposeAsyncCore() } } diff --git a/src/CommunityToolkit.Maui.UnitTests/Views/Popup/PopupPageTests.cs b/src/CommunityToolkit.Maui.UnitTests/Views/Popup/PopupPageTests.cs index 6eff6d506c..c125891ae3 100644 --- a/src/CommunityToolkit.Maui.UnitTests/Views/Popup/PopupPageTests.cs +++ b/src/CommunityToolkit.Maui.UnitTests/Views/Popup/PopupPageTests.cs @@ -56,8 +56,8 @@ public async Task Close_ShouldThrowInvalidOperationException_NoPopupPageFound() var popupPage = new PopupPage(view, popupOptions); // Act / Assert - await Assert.ThrowsAsync(async () => await popupPage.Close(new PopupResult(false), CancellationToken.None)); - await Assert.ThrowsAnyAsync(async () => await popupPage.Close(new PopupResult(false), CancellationToken.None)); + await Assert.ThrowsAsync(async () => await popupPage.CloseAsync(new PopupResult(false), CancellationToken.None)); + await Assert.ThrowsAnyAsync(async () => await popupPage.CloseAsync(new PopupResult(false), CancellationToken.None)); } [Fact] @@ -80,7 +80,7 @@ public async Task Close_ShouldSetResultAndPopModalAsync() await navigation.PushModalAsync(popupPage); - await popupPage.Close(expectedResult, CancellationToken.None); + await popupPage.CloseAsync(expectedResult, CancellationToken.None); var actualResult = await tcs.Task; // Assert @@ -112,7 +112,7 @@ public async Task Close_ShouldThrowOperationCanceledException_WhenTokenIsCancell await cts.CancelAsync(); // Assert - await Assert.ThrowsAnyAsync(() => popupPage.Close(result, cts.Token)); + await Assert.ThrowsAnyAsync(() => popupPage.CloseAsync(result, cts.Token)); Assert.Single(mainPage.Navigation.ModalStack.OfType()); } @@ -196,9 +196,9 @@ public async Task PopupPageT_CloseAfterAdditionalModalPage_ShouldThrowInvalidOpe await navigation.PushModalAsync(new ContentPage()); // Assert - await Assert.ThrowsAsync(async () => await popupPage.Close(new PopupResult(false), CancellationToken.None)); - await Assert.ThrowsAnyAsync(async () => await popupPage.Close(new PopupResult(false), CancellationToken.None)); - await Assert.ThrowsAnyAsync(async () => await popupPage.Close(new PopupResult(false), CancellationToken.None)); + await Assert.ThrowsAsync(async () => await popupPage.CloseAsync(new PopupResult(false), CancellationToken.None)); + await Assert.ThrowsAnyAsync(async () => await popupPage.CloseAsync(new PopupResult(false), CancellationToken.None)); + await Assert.ThrowsAnyAsync(async () => await popupPage.CloseAsync(new PopupResult(false), CancellationToken.None)); } [Fact] @@ -403,7 +403,7 @@ public async Task PopupClosedEvent_ShouldTriggerOnce_WhenPopupIsClosed() var popupPage = (PopupPage)navigation.ModalStack[0]; popupPage.PopupClosed += (sender, args) => eventTriggered = true; - await popupPage.Close(new PopupResult(false), CancellationToken.None); + await popupPage.CloseAsync(new PopupResult(false), CancellationToken.None); // Assert Assert.True(eventTriggered); @@ -456,7 +456,7 @@ public async Task Close_ShouldThrowException_WhenCalledOnNonModalPopup() var popupPage = new PopupPage(view, popupOptions); // Act & Assert - await Assert.ThrowsAsync(async () => await popupPage.Close(new PopupResult(false), CancellationToken.None)); + await Assert.ThrowsAsync(async () => await popupPage.CloseAsync(new PopupResult(false), CancellationToken.None)); } [Fact] diff --git a/src/CommunityToolkit.Maui.UnitTests/Views/Popup/PopupTests.cs b/src/CommunityToolkit.Maui.UnitTests/Views/Popup/PopupTests.cs index b76a063532..6047e94574 100644 --- a/src/CommunityToolkit.Maui.UnitTests/Views/Popup/PopupTests.cs +++ b/src/CommunityToolkit.Maui.UnitTests/Views/Popup/PopupTests.cs @@ -113,8 +113,8 @@ public async Task Popup_Close_ShouldThrowExceptionWhenCalledBeforeShown() var popup = new Popup(); // Assert - await Assert.ThrowsAsync(() => popup.Close(TestContext.Current.CancellationToken)); - await Assert.ThrowsAnyAsync(() => popup.Close(TestContext.Current.CancellationToken)); + await Assert.ThrowsAsync(() => popup.CloseAsync(TestContext.Current.CancellationToken)); + await Assert.ThrowsAnyAsync(() => popup.CloseAsync(TestContext.Current.CancellationToken)); } [Fact(Timeout = (int)TestDuration.Short)] @@ -124,8 +124,8 @@ public async Task PopupT_Close_ShouldThrowExceptionWhenCalledBeforeShown() var popup = new Popup(); // Assert - await Assert.ThrowsAsync(() => popup.Close("Hello", TestContext.Current.CancellationToken)); - await Assert.ThrowsAnyAsync(() => popup.Close("Hello", TestContext.Current.CancellationToken)); + await Assert.ThrowsAsync(() => popup.CloseAsync("Hello", TestContext.Current.CancellationToken)); + await Assert.ThrowsAnyAsync(() => popup.CloseAsync("Hello", TestContext.Current.CancellationToken)); } [Fact(Timeout = (int)TestDuration.Short)] @@ -135,7 +135,7 @@ public async Task Popup_Close_ShouldNotThrowExceptionWhenCloseIsOverridden() var popup = new PopupOverridingClose(); // Assert - await popup.Close(TestContext.Current.CancellationToken); + await popup.CloseAsync(TestContext.Current.CancellationToken); } [Fact(Timeout = (int)TestDuration.Short)] @@ -145,18 +145,18 @@ public async Task PopupT_Close_ShouldNotThrowExceptionWhenCloseIsOverridden() var popup = new PopupTOverridingClose(); // Assert - await popup.Close(TestContext.Current.CancellationToken); - await popup.Close("Hello", TestContext.Current.CancellationToken); + await popup.CloseAsync(TestContext.Current.CancellationToken); + await popup.CloseAsync("Hello", TestContext.Current.CancellationToken); } sealed class PopupOverridingClose : Popup { - public override Task Close(CancellationToken token = default) => Task.CompletedTask; + public override Task CloseAsync(CancellationToken token = default) => Task.CompletedTask; } sealed class PopupTOverridingClose : Popup { - public override Task Close(CancellationToken token = default) => Task.CompletedTask; - public override Task Close(string result, CancellationToken token = default) => Task.CompletedTask; + public override Task CloseAsync(CancellationToken token = default) => Task.CompletedTask; + public override Task CloseAsync(string result, CancellationToken token = default) => Task.CompletedTask; } } \ No newline at end of file diff --git a/src/CommunityToolkit.Maui/Extensions/PopupExtensions.shared.cs b/src/CommunityToolkit.Maui/Extensions/PopupExtensions.shared.cs index 7954406c38..34fe0f1231 100644 --- a/src/CommunityToolkit.Maui/Extensions/PopupExtensions.shared.cs +++ b/src/CommunityToolkit.Maui/Extensions/PopupExtensions.shared.cs @@ -197,7 +197,7 @@ void HandlePopupClosed(object? sender, IPopupResult e) } /// - /// Close the Visible Popup + /// CloseAsync the Visible Popup /// public static Task ClosePopup(this Page page, CancellationToken token = default) { @@ -207,7 +207,7 @@ public static Task ClosePopup(this Page page, CancellationToken token = default) } /// - /// Close the Visible Popup + /// CloseAsync the Visible Popup /// public static Task ClosePopup(this INavigation navigation, CancellationToken token = default) { @@ -229,7 +229,7 @@ public static Task ClosePopup(this INavigation navigation, CancellationToken tok throw new PopupBlockedException(currentVisibleModalPage); } - return popupPage.Close(new PopupResult(false), token); + return popupPage.CloseAsync(new PopupResult(false), token); } static PopupResult GetPopupResult(in IPopupResult result) diff --git a/src/CommunityToolkit.Maui/Services/PopupService.shared.cs b/src/CommunityToolkit.Maui/Services/PopupService.shared.cs index e0115a9ca0..e2032784b3 100644 --- a/src/CommunityToolkit.Maui/Services/PopupService.shared.cs +++ b/src/CommunityToolkit.Maui/Services/PopupService.shared.cs @@ -97,7 +97,7 @@ public async Task ClosePopupAsync(INavigation navigation, CancellationToken canc cancellationToken.ThrowIfCancellationRequested(); var popupPage = GetCurrentPopupPage(navigation); - await popupPage.Close(new PopupResult(false), cancellationToken); + await popupPage.CloseAsync(new PopupResult(false), cancellationToken); } /// @@ -106,7 +106,7 @@ public async Task ClosePopupAsync(INavigation navigation, T result, Cancellat cancellationToken.ThrowIfCancellationRequested(); var popupPage = GetCurrentPopupPage(navigation); - await popupPage.Close(new PopupResult(result, false), cancellationToken); + await popupPage.CloseAsync(new PopupResult(result, false), cancellationToken); } internal static void AddPopup<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TPopupView>( diff --git a/src/CommunityToolkit.Maui/Views/Popup/Popup.shared.cs b/src/CommunityToolkit.Maui/Views/Popup/Popup.shared.cs index a006d555bc..49c9b0f8d3 100644 --- a/src/CommunityToolkit.Maui/Views/Popup/Popup.shared.cs +++ b/src/CommunityToolkit.Maui/Views/Popup/Popup.shared.cs @@ -85,9 +85,9 @@ public Popup() } /// - /// Close the Popup. + /// CloseAsync the Popup. /// - public virtual Task Close(CancellationToken token = default) => GetPopupPage().Close(new PopupResult(false), token); + public virtual Task CloseAsync(CancellationToken token = default) => GetPopupPage().CloseAsync(new PopupResult(false), token); internal void NotifyPopupIsOpened() { @@ -123,13 +123,13 @@ private protected PopupPage GetPopupPage() public partial class Popup : Popup { /// - /// Close the Popup with a result. + /// CloseAsync the Popup with a result. /// /// Popup result /// - public virtual Task Close(T result, CancellationToken token = default) => GetPopupPage().Close(new PopupResult(result, false), token); + public virtual Task CloseAsync(T result, CancellationToken token = default) => GetPopupPage().CloseAsync(new PopupResult(result, false), token); } -sealed class PopupNotFoundException() : InvalidPopupOperationException($"Unable to close popup: could not locate {nameof(PopupPage)}. {nameof(PopupExtensions.ShowPopup)} or {nameof(PopupExtensions.ShowPopupAsync)} must be called before {nameof(Popup.Close)}. If using a custom implementation of {nameof(Popup)}, override the {nameof(Popup.Close)} method"); +sealed class PopupNotFoundException() : InvalidPopupOperationException($"Unable to close popup: could not locate {nameof(PopupPage)}. {nameof(PopupExtensions.ShowPopup)} or {nameof(PopupExtensions.ShowPopupAsync)} must be called before {nameof(Popup.CloseAsync)}. If using a custom implementation of {nameof(Popup)}, override the {nameof(Popup.CloseAsync)} method"); sealed class PopupBlockedException(in Page currentVisibleModalPage): InvalidPopupOperationException($"Unable to close Popup because it is blocked by the Modal Page {currentVisibleModalPage.GetType().FullName}. Please call `{nameof(Page.Navigation)}.{nameof(Page.Navigation.PopModalAsync)}()` to first remove {currentVisibleModalPage.GetType().FullName} from the {nameof(Page.Navigation.ModalStack)}"); class InvalidPopupOperationException(in string message) : InvalidOperationException(message); diff --git a/src/CommunityToolkit.Maui/Views/Popup/PopupPage.shared.cs b/src/CommunityToolkit.Maui/Views/Popup/PopupPage.shared.cs index 3a8a975158..dd98d44891 100644 --- a/src/CommunityToolkit.Maui/Views/Popup/PopupPage.shared.cs +++ b/src/CommunityToolkit.Maui/Views/Popup/PopupPage.shared.cs @@ -16,7 +16,7 @@ public PopupPage(View view, IPopupOptions popupOptions) { } - public Task Close(PopupResult result, CancellationToken token = default) => base.Close(result, token); + public Task Close(PopupResult result, CancellationToken token = default) => base.CloseAsync(result, token); } partial class PopupPage : ContentPage, IQueryAttributable @@ -51,7 +51,7 @@ public PopupPage(Popup popup, IPopupOptions popupOptions) tapOutsideOfPopupCommand = new Command(async () => { popupOptions.OnTappingOutsideOfPopup?.Invoke(); - await Close(new PopupResult(true)); + await CloseAsync(new PopupResult(true)); }, () => popupOptions.CanBeDismissedByTappingOutsideOfPopup); Content.GestureRecognizers.Add(new TapGestureRecognizer { Command = tapOutsideOfPopupCommand }); @@ -78,10 +78,10 @@ public event EventHandler PopupClosed // Casts `PopupPage.Content` to return typeof(PopupPageLayout) internal new PopupPageLayout Content => (PopupPageLayout)base.Content; - public async Task Close(PopupResult result, CancellationToken token = default) + public async Task CloseAsync(PopupResult result, CancellationToken token = default) { // We first call `.ThrowIfCancellationRequested()` to ensure we don't throw one of the `InvalidOperationException`s (below) if the `CancellationToken` has already been canceled. - // This ensures we throw the correct `OperationCanceledException` in the rare scenario where a developer cancels the token, then manually calls `Navigation.PopModalAsync()` before calling `Popup.Close()` + // This ensures we throw the correct `OperationCanceledException` in the rare scenario where a developer cancels the token, then manually calls `Navigation.PopModalAsync()` before calling `Popup.CloseAsync()` // It may feel a bit redundant, given that we again call `ThrowIfCancellationRequested` later in this method, however, this ensures we propagate the correct Exception to the developer. token.ThrowIfCancellationRequested(); From 3f6304effef2df151418a373addd03ad1d13de1f Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+TheCodeTraveler@users.noreply.github.com> Date: Mon, 26 May 2025 12:29:40 -0700 Subject: [PATCH 08/10] Add `[EditorBrowsable(EditorBrowsableState.Never)]` to Obsolete classes --- .../Handlers/Popup/PopupHandler.shared.cs | 4 +++- src/CommunityToolkit.Maui.Core/Interfaces/IPopup.shared.cs | 3 ++- .../Views/Popup/MauiPopup.android.cs | 5 +++-- .../Views/Popup/MauiPopup.macios.cs | 3 ++- .../Views/Popup/MauiPopup.tizen.cs | 5 +++-- .../Views/Popup/PopupExtensions.android.cs | 5 +++-- .../Views/Popup/PopupExtensions.macios.cs | 3 ++- .../Views/Popup/PopupExtensions.windows.cs | 3 ++- .../Views/Popup/PopupOverlay.windows.cs | 5 +++-- 9 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/CommunityToolkit.Maui.Core/Handlers/Popup/PopupHandler.shared.cs b/src/CommunityToolkit.Maui.Core/Handlers/Popup/PopupHandler.shared.cs index 52ef719e8a..29cf972747 100644 --- a/src/CommunityToolkit.Maui.Core/Handlers/Popup/PopupHandler.shared.cs +++ b/src/CommunityToolkit.Maui.Core/Handlers/Popup/PopupHandler.shared.cs @@ -1,3 +1,5 @@ +using System.ComponentModel; + namespace CommunityToolkit.Maui.Core.Handlers; /// @@ -6,7 +8,7 @@ namespace CommunityToolkit.Maui.Core.Handlers; #if NET10_0_OR_GREATER #error Remove PopupHandler #endif -[Obsolete($"{nameof(PopupHandler)} is no longer used by {nameof(CommunityToolkit)}.{nameof(Maui)} and will be removed in .NET 10")] +[EditorBrowsable(EditorBrowsableState.Never), Obsolete($"{nameof(PopupHandler)} is no longer used by {nameof(CommunityToolkit)}.{nameof(Maui)} and will be removed in .NET 10")] public partial class PopupHandler { /// diff --git a/src/CommunityToolkit.Maui.Core/Interfaces/IPopup.shared.cs b/src/CommunityToolkit.Maui.Core/Interfaces/IPopup.shared.cs index 94552c27ec..3ae3d056ea 100644 --- a/src/CommunityToolkit.Maui.Core/Interfaces/IPopup.shared.cs +++ b/src/CommunityToolkit.Maui.Core/Interfaces/IPopup.shared.cs @@ -1,3 +1,4 @@ +using System.ComponentModel; using IElement = Microsoft.Maui.IElement; using LayoutAlignment = Microsoft.Maui.Primitives.LayoutAlignment; @@ -9,7 +10,7 @@ namespace CommunityToolkit.Maui.Core; #if NET10_0_OR_GREATER #error Remove IPopup #endif -[Obsolete($"{nameof(IPopup)} is no longer used by {nameof(CommunityToolkit)}.{nameof(Maui)} and will be removed in .NET 10")] +[EditorBrowsable(EditorBrowsableState.Never), Obsolete($"{nameof(IPopup)} is no longer used by {nameof(CommunityToolkit)}.{nameof(Maui)} and will be removed in .NET 10")] public interface IPopup : IElement, IVisualTreeElement, IAsynchronousHandler { /// diff --git a/src/CommunityToolkit.Maui.Core/Views/Popup/MauiPopup.android.cs b/src/CommunityToolkit.Maui.Core/Views/Popup/MauiPopup.android.cs index bc7466a867..a89ed9e250 100644 --- a/src/CommunityToolkit.Maui.Core/Views/Popup/MauiPopup.android.cs +++ b/src/CommunityToolkit.Maui.Core/Views/Popup/MauiPopup.android.cs @@ -1,4 +1,5 @@ -using System.Diagnostics.CodeAnalysis; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using Android.Content; using Android.Views; using Microsoft.Maui.Platform; @@ -12,7 +13,7 @@ namespace CommunityToolkit.Maui.Core.Views; #if NET10_0_OR_GREATER #error Remove MauiPopup #endif -[Obsolete($"{nameof(MauiPopup)} is no longer used by {nameof(CommunityToolkit)}.{nameof(Maui)} and will be removed in .NET 10")] +[EditorBrowsable(EditorBrowsableState.Never), Obsolete($"{nameof(MauiPopup)} is no longer used by {nameof(CommunityToolkit)}.{nameof(Maui)} and will be removed in .NET 10")] public class MauiPopup : Dialog, IDialogInterfaceOnCancelListener { readonly IMauiContext mauiContext; diff --git a/src/CommunityToolkit.Maui.Core/Views/Popup/MauiPopup.macios.cs b/src/CommunityToolkit.Maui.Core/Views/Popup/MauiPopup.macios.cs index f8e3717bdf..4860b76ff7 100644 --- a/src/CommunityToolkit.Maui.Core/Views/Popup/MauiPopup.macios.cs +++ b/src/CommunityToolkit.Maui.Core/Views/Popup/MauiPopup.macios.cs @@ -1,3 +1,4 @@ +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using CommunityToolkit.Maui.Core.Extensions; using Microsoft.Maui.ApplicationModel; @@ -16,7 +17,7 @@ namespace CommunityToolkit.Maui.Core.Views; #if NET10_0_OR_GREATER #error Remove MauiPopup #endif -[Obsolete($"{nameof(MauiPopup)} is no longer used by {nameof(CommunityToolkit)}.{nameof(Maui)} and will be removed in .NET 10")] +[EditorBrowsable(EditorBrowsableState.Never), Obsolete($"{nameof(MauiPopup)} is no longer used by {nameof(CommunityToolkit)}.{nameof(Maui)} and will be removed in .NET 10")] public class MauiPopup(IMauiContext mauiContext) : UIViewController { readonly IMauiContext mauiContext = mauiContext ?? throw new ArgumentNullException(nameof(mauiContext)); diff --git a/src/CommunityToolkit.Maui.Core/Views/Popup/MauiPopup.tizen.cs b/src/CommunityToolkit.Maui.Core/Views/Popup/MauiPopup.tizen.cs index 3a390597ed..6c6b0129d9 100644 --- a/src/CommunityToolkit.Maui.Core/Views/Popup/MauiPopup.tizen.cs +++ b/src/CommunityToolkit.Maui.Core/Views/Popup/MauiPopup.tizen.cs @@ -1,4 +1,5 @@ -using Microsoft.Maui.Platform; +using System.ComponentModel; +using Microsoft.Maui.Platform; using Microsoft.Maui.Primitives; using Tizen.NUI; using Tizen.UIExtensions.NUI; @@ -13,7 +14,7 @@ namespace CommunityToolkit.Maui.Core.Views; #if NET10_0_OR_GREATER #error Remove MauiPopup #endif -[Obsolete($"{nameof(MauiPopup)} is no longer used by {nameof(CommunityToolkit)}.{nameof(Maui)} and will be removed in .NET 10")] +[EditorBrowsable(EditorBrowsableState.Never), Obsolete($"{nameof(MauiPopup)} is no longer used by {nameof(CommunityToolkit)}.{nameof(Maui)} and will be removed in .NET 10")] public class MauiPopup : Popup { readonly IMauiContext mauiContext; diff --git a/src/CommunityToolkit.Maui.Core/Views/Popup/PopupExtensions.android.cs b/src/CommunityToolkit.Maui.Core/Views/Popup/PopupExtensions.android.cs index 562d854b00..64d6d462d1 100644 --- a/src/CommunityToolkit.Maui.Core/Views/Popup/PopupExtensions.android.cs +++ b/src/CommunityToolkit.Maui.Core/Views/Popup/PopupExtensions.android.cs @@ -1,4 +1,5 @@ -using System.Diagnostics.CodeAnalysis; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using Android.Graphics.Drawables; using Android.Views; using CommunityToolkit.Maui.Core.Handlers; @@ -17,7 +18,7 @@ namespace CommunityToolkit.Maui.Core.Views; #if NET10_0_OR_GREATER #error Remove MauiPopup #endif -[Obsolete($"{nameof(PopupExtensions)} is no longer used by {nameof(CommunityToolkit)}.{nameof(Maui)} and will be removed in .NET 10")] +[EditorBrowsable(EditorBrowsableState.Never), Obsolete($"{nameof(PopupExtensions)} is no longer used by {nameof(CommunityToolkit)}.{nameof(Maui)} and will be removed in .NET 10")] public static class PopupExtensions { /// diff --git a/src/CommunityToolkit.Maui.Core/Views/Popup/PopupExtensions.macios.cs b/src/CommunityToolkit.Maui.Core/Views/Popup/PopupExtensions.macios.cs index 016b05fb29..dd14e0d74c 100644 --- a/src/CommunityToolkit.Maui.Core/Views/Popup/PopupExtensions.macios.cs +++ b/src/CommunityToolkit.Maui.Core/Views/Popup/PopupExtensions.macios.cs @@ -1,3 +1,4 @@ +using System.ComponentModel; using Microsoft.Maui.Platform; using ObjCRuntime; @@ -8,7 +9,7 @@ namespace CommunityToolkit.Maui.Core.Views; #if NET10_0_OR_GREATER #error Remove PopupExtensions #endif -[Obsolete($"{nameof(PopupExtensions)} is no longer used by {nameof(CommunityToolkit)}.{nameof(Maui)} and will be removed in .NET 10")] +[EditorBrowsable(EditorBrowsableState.Never), Obsolete($"{nameof(PopupExtensions)} is no longer used by {nameof(CommunityToolkit)}.{nameof(Maui)} and will be removed in .NET 10")] public static class PopupExtensions { static readonly nfloat defaultPopoverLayoutMargin = 0.0001f; diff --git a/src/CommunityToolkit.Maui.Core/Views/Popup/PopupExtensions.windows.cs b/src/CommunityToolkit.Maui.Core/Views/Popup/PopupExtensions.windows.cs index 1f2b73e5b6..1bf50b9f24 100644 --- a/src/CommunityToolkit.Maui.Core/Views/Popup/PopupExtensions.windows.cs +++ b/src/CommunityToolkit.Maui.Core/Views/Popup/PopupExtensions.windows.cs @@ -1,3 +1,4 @@ +using System.ComponentModel; using CommunityToolkit.Maui.Core.Handlers; using Microsoft.Maui.Platform; using Microsoft.Maui.Primitives; @@ -13,7 +14,7 @@ namespace CommunityToolkit.Maui.Core.Views; #if NET10_0_OR_GREATER #error Remove PopupExtensions #endif -[Obsolete($"{nameof(PopupExtensions)} is no longer used by {nameof(CommunityToolkit)}.{nameof(Maui)} and will be removed in .NET 10")] +[EditorBrowsable(EditorBrowsableState.Never), Obsolete($"{nameof(PopupExtensions)} is no longer used by {nameof(CommunityToolkit)}.{nameof(Maui)} and will be removed in .NET 10")] public static class PopupExtensions { /// diff --git a/src/CommunityToolkit.Maui.Core/Views/Popup/PopupOverlay.windows.cs b/src/CommunityToolkit.Maui.Core/Views/Popup/PopupOverlay.windows.cs index c7019535bd..a72d4896d5 100644 --- a/src/CommunityToolkit.Maui.Core/Views/Popup/PopupOverlay.windows.cs +++ b/src/CommunityToolkit.Maui.Core/Views/Popup/PopupOverlay.windows.cs @@ -1,4 +1,5 @@ -namespace CommunityToolkit.Maui.Core.Views; +using System.ComponentModel; +namespace CommunityToolkit.Maui.Core.Views; /// /// Displays Overlay in the Popup background. @@ -6,7 +7,7 @@ #if NET10_0_OR_GREATER #error Remove PopupOverlay #endif -[Obsolete($"{nameof(PopupOverlay)} is no longer used by {nameof(CommunityToolkit)}.{nameof(Maui)} and will be removed in .NET 10")] +[EditorBrowsable(EditorBrowsableState.Never), Obsolete($"{nameof(PopupOverlay)} is no longer used by {nameof(CommunityToolkit)}.{nameof(Maui)} and will be removed in .NET 10")] class PopupOverlay : WindowOverlay { readonly IWindowOverlayElement popupOverlayElement; From 7aa6e88501c161aef9837e55f498b453b8359b6f Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+TheCodeTraveler@users.noreply.github.com> Date: Mon, 26 May 2025 12:56:40 -0700 Subject: [PATCH 09/10] Update Samples --- .../Views/Popup/PopupLayoutAlignmentPage.xaml | 2 +- .../Pages/Views/Popup/PopupPositionPage.xaml | 2 +- .../Pages/Views/Popup/PopupsPage.xaml | 3 +++ .../Pages/Views/Popup/PopupsPage.xaml.cs | 14 ++++++++++++++ .../Resources/Styles/Styles.xaml | 2 +- .../Views/Popups/ButtonPopup.xaml | 2 +- .../Views/Popups/MultipleButtonPopup.xaml | 3 ++- .../Extensions/PopupExtensionsTests.cs | 10 +++++----- .../Extensions/PopupExtensions.shared.cs | 6 +++--- 9 files changed, 31 insertions(+), 13 deletions(-) diff --git a/samples/CommunityToolkit.Maui.Sample/Pages/Views/Popup/PopupLayoutAlignmentPage.xaml b/samples/CommunityToolkit.Maui.Sample/Pages/Views/Popup/PopupLayoutAlignmentPage.xaml index 92d0c606d9..5ebba4dc66 100644 --- a/samples/CommunityToolkit.Maui.Sample/Pages/Views/Popup/PopupLayoutAlignmentPage.xaml +++ b/samples/CommunityToolkit.Maui.Sample/Pages/Views/Popup/PopupLayoutAlignmentPage.xaml @@ -6,6 +6,7 @@ xmlns:viewModels="clr-namespace:CommunityToolkit.Maui.Sample.ViewModels.Views" x:TypeArguments="viewModels:PopupLayoutAlignmentViewModel" x:DataType="viewModels:PopupLayoutAlignmentViewModel" + Padding="20" Title="Popup Layout Alignment">