Skip to content

Commit

Permalink
Add Popup.CloseAsync() (#1223)
Browse files Browse the repository at this point in the history
* Add `Popup.CloseAsync()`

* Rename `PopupDismissedTaskCompletionSource`

* Fix Naming

* Update Documentation

* Add `IAsynchronousHandler` (#1236)

* Fix Tizen to use IAsynchronousHandler

* Fix variable name

---------

Co-authored-by: Jay Cho <[email protected]>
  • Loading branch information
brminnick and JoonghyunCho authored Jul 10, 2023
1 parent 426d704 commit 0b86ba1
Show file tree
Hide file tree
Showing 14 changed files with 93 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ public ButtonPopup(PopupSizeConstants popupSizeConstants)
Size = popupSizeConstants.Medium;
}

void Button_Clicked(object? sender, EventArgs e) => Close();
async void Button_Clicked(object? sender, EventArgs e) => await CloseAsync();
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public MultipleButtonPopup(PopupSizeConstants popupSizeConstants)
Size = popupSizeConstants.Medium;
}

void Cancel_Clicked(object? sender, EventArgs e) => Close(false);
async void Cancel_Clicked(object? sender, EventArgs e) => await CloseAsync(false);

void Okay_Clicked(object? sender, EventArgs e) => Close(true);
async void Okay_Clicked(object? sender, EventArgs e) => await CloseAsync(true);
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using CommunityToolkit.Maui.Alerts;
using CommunityToolkit.Maui.Sample.Models;
using CommunityToolkit.Maui.Views;

Expand All @@ -12,5 +13,9 @@ public NoOutsideTapDismissPopup(PopupSizeConstants popupSizeConstants)
Size = popupSizeConstants.Medium;
}

void Button_Clicked(object? sender, System.EventArgs e) => Close();
async void Button_Clicked(object? sender, EventArgs e)
{
await CloseAsync();
await Toast.Make("Popup Dismissed By Button").Show();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ public ReturnResultPopup(PopupSizeConstants popupSizeConstants)
ResultWhenUserTapsOutsideOfPopup = "User Tapped Outside of Popup";
}

void Button_Clicked(object? sender, EventArgs e) => Close("Close button tapped");
async void Button_Clicked(object? sender, EventArgs e) => await CloseAsync("Close button tapped");
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ public partial class TransparentPopup : Popup
{
public TransparentPopup() => InitializeComponent();

public void CloseButtonClicked(object? sender, EventArgs args)
public async void CloseButtonClicked(object? sender, EventArgs args)
{
Close();
await CloseAsync();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public static void MapOnClosed(PopupHandler handler, IPopup view, object? result
popup.Dismiss();
}

view.HandlerCompleteTCS.TrySetResult();

handler.DisconnectHandler(popup);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public partial class PopupHandler : ElementHandler<IPopup, MauiPopup>
/// <param name="result">The result that should return from this Popup.</param>
public static void MapOnClosed(PopupHandler handler, IPopup view, object? result)
{
view.HandlerCompleteTCS.TrySetResult();
handler.DisconnectHandler(handler.PlatformView);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ public static async void MapOnClosed(PopupHandler handler, IPopup view, object?
await vc.DismissViewControllerAsync(true);
}

view.HandlerCompleteTCS.TrySetResult();

handler.DisconnectHandler(handler.PlatformView);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public partial class PopupHandler : Microsoft.Maui.Handlers.ElementHandler<IPopu
/// <param name="result">The result that should return from this Popup.</param>
public static void MapOnClosed(PopupHandler handler, IPopup view, object? result)
{
throw new NotSupportedException();
}

/// <summary>
Expand All @@ -23,6 +24,7 @@ public static void MapOnClosed(PopupHandler handler, IPopup view, object? result
/// <param name="result">We don't need to provide the result parameter here.</param>
public static void MapOnOpened(PopupHandler handler, IPopup view, object? result)
{
throw new NotSupportedException();
}

/// <summary>
Expand All @@ -33,6 +35,7 @@ public static void MapOnOpened(PopupHandler handler, IPopup view, object? result
/// <param name="result">The result that should return from this Popup.</param>
public static void MapOnDismissedByTappingOutsideOfPopup(PopupHandler handler, IPopup view, object? result)
{
throw new NotSupportedException();
}

/// <summary>
Expand All @@ -42,6 +45,7 @@ public static void MapOnDismissedByTappingOutsideOfPopup(PopupHandler handler, I
/// <param name="view">An instance of <see cref="IPopup"/>.</param>
public static void MapAnchor(PopupHandler handler, IPopup view)
{
throw new NotSupportedException();
}

/// <summary>
Expand All @@ -51,6 +55,7 @@ public static void MapAnchor(PopupHandler handler, IPopup view)
/// <param name="view">An instance of <see cref="IPopup"/>.</param>
public static void MapCanBeDismissedByTappingOutsideOfPopup(PopupHandler handler, IPopup view)
{
throw new NotSupportedException();
}

/// <summary>
Expand All @@ -60,6 +65,7 @@ public static void MapCanBeDismissedByTappingOutsideOfPopup(PopupHandler handler
/// <param name="view">An instance of <see cref="IPopup"/>.</param>
public static void MapColor(PopupHandler handler, IPopup view)
{
throw new NotSupportedException();
}

/// <summary>
Expand All @@ -69,5 +75,6 @@ public static void MapColor(PopupHandler handler, IPopup view)
/// <param name="view">An instance of <see cref="IPopup"/>.</param>
public static void MapSize(PopupHandler handler, IPopup view)
{
throw new NotSupportedException();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public static void MapOnClosed(PopupHandler handler, IPopup view, object? result
{
popup.Close();
}
view.HandlerCompleteTCS.TrySetResult();

handler.DisconnectHandler(popup);
}
Expand Down
13 changes: 13 additions & 0 deletions src/CommunityToolkit.Maui.Core/Interfaces/IAsynchronousHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace CommunityToolkit.Maui.Core;

/// <summary>
/// Interface that allows asynchronous completion of .NET MAUI Handlers
/// </summary>
public interface IAsynchronousHandler
{
/// <summary>
/// A <see cref="TaskCompletionSource"/> to provide Handlers an asynchronous way to complete
/// </summary>
TaskCompletionSource HandlerCompleteTCS { get; }
}

2 changes: 1 addition & 1 deletion src/CommunityToolkit.Maui.Core/Interfaces/IPopup.shared.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace CommunityToolkit.Maui.Core;
/// <summary>
/// Represents a small View that pops up at front the Page.
/// </summary>
public interface IPopup : IElement, IVisualTreeElement
public interface IPopup : IElement, IVisualTreeElement, IAsynchronousHandler
{
/// <summary>
/// Gets the View that Popup will be anchored.
Expand Down
25 changes: 17 additions & 8 deletions src/CommunityToolkit.Maui.UnitTests/Views/Popup/PopupTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,11 @@ public void PopupDismissedByTappingOutsideOfPopup()
}

[Fact]
public void OnDismissedWithResult()
public async Task OnDismissedWithResult()
{
object? result = null;
var isPopupDismissed = false;
var closedTCS = new TaskCompletionSource();
var app = Application.Current ?? throw new NullReferenceException();

var page = new ContentPage
Expand All @@ -133,17 +134,19 @@ public void OnDismissedWithResult()
{
result = e.Result;
isPopupDismissed = true;
closedTCS.TrySetResult();
};

((MockPopup)popup).Close(new object());
await closedTCS.Task;

Assert.True(isPopupDismissed);
Assert.NotNull(result);
}


[Fact]
public void OnDismissedWithoutResult()
public async Task OnDismissedWithoutResult()
{
object? result = null;
var isPopupDismissed = false;
Expand Down Expand Up @@ -174,7 +177,7 @@ public void OnDismissedWithoutResult()
isPopupDismissed = true;
};

((MockPopup)popup).Close();
await ((MockPopup)popup).CloseAsync();

Assert.True(isPopupDismissed);
Assert.Null(result);
Expand All @@ -184,25 +187,31 @@ public void OnDismissedWithoutResult()
public void NullColorThrowsArgumentNullException()
{
var popupViewModel = new PopupViewModel();
var popupWithBinding = new Maui.Views.Popup
var popupWithBinding = new Popup
{
BindingContext = popupViewModel
};
popupWithBinding.SetBinding(Maui.Views.Popup.ColorProperty, nameof(PopupViewModel.Color));
popupWithBinding.SetBinding(Popup.ColorProperty, nameof(PopupViewModel.Color));

#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
Assert.Throws<ArgumentNullException>(() => new Maui.Views.Popup { Color = null });
Assert.Throws<ArgumentNullException>(() => new Maui.Views.Popup().Color = null);
Assert.Throws<ArgumentNullException>(() => new Popup { Color = null });
Assert.Throws<ArgumentNullException>(() => new Popup().Color = null);
Assert.Throws<ArgumentNullException>(() => popupViewModel.Color = null);
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
}

class MockPopup : Maui.Views.Popup
class MockPopup : Popup
{
public MockPopup()
{
ResultWhenUserTapsOutsideOfPopup = resultWhenUserTapsOutsideOfPopup;
}

protected override Task OnClosed(object? result, bool wasDismissedByTappingOutsideOfPopup)
{
((IPopup)this).HandlerCompleteTCS.TrySetResult();
return base.OnClosed(result, wasDismissedByTappingOutsideOfPopup);
}
}

class PopupViewModel : INotifyPropertyChanged
Expand Down
48 changes: 37 additions & 11 deletions src/CommunityToolkit.Maui/Views/Popup/Popup.shared.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ public partial class Popup : Element, IPopup, IWindowController, IPropertyPropag
readonly WeakEventManager openedWeakEventManager = new();
readonly Lazy<PlatformConfigurationRegistry<Popup>> platformConfigurationRegistry;

TaskCompletionSource<object?> taskCompletionSource = new();
TaskCompletionSource popupDismissedTaskCompletionSource = new();
TaskCompletionSource<object?> resultTaskCompletionSource = new();
Window? window;

/// <summary>
Expand Down Expand Up @@ -81,7 +82,7 @@ public event EventHandler<PopupOpenedEventArgs> Opened
/// <summary>
/// Gets the final result of the dismissed popup.
/// </summary>
public Task<object?> Result => taskCompletionSource.Task;
public Task<object?> Result => resultTaskCompletionSource.Task;

/// <summary>
/// Gets or sets the <see cref="View"/> content to render in the Popup.
Expand Down Expand Up @@ -192,22 +193,44 @@ public Window? Window
/// <inheritdoc/>
IView? IPopup.Content => Content;

/// <inheritdoc/>
TaskCompletionSource IAsynchronousHandler.HandlerCompleteTCS => popupDismissedTaskCompletionSource;

/// <summary>
/// Resets the Popup.
/// </summary>
public void Reset() => taskCompletionSource = new();
public void Reset()
{
resultTaskCompletionSource = new();
popupDismissedTaskCompletionSource = new();
}

/// <summary>
/// Close the current popup.
/// </summary>
/// <remarks>
/// <see cref="Close(object?)"/> is an <see langword="async"/> <see langword="void"/> method, commonly referred to as a fire-and-forget method.
/// It will complete and return to the calling thread before the operating system has dismissed the <see cref="Popup"/> from the screen.
/// If you need to pause the execution of your method until the operating system has dismissed the <see cref="Popup"/> from the screen, use instead <see cref="CloseAsync(object?)"/>.
/// </remarks>
/// <param name="result">
/// The result to return.
/// </param>
public async void Close(object? result = null) => await CloseAsync(result);

/// <summary>
/// Close the current popup.
/// </summary>
/// <remarks>
/// Returns once the operating system has dismissed the <see cref="IPopup"/> from the page
/// </remarks>
/// <param name="result">
/// The result to return.
/// </param>
public void Close(object? result = null)
public async Task CloseAsync(object? result = null)
{
taskCompletionSource.TrySetResult(result);
OnClosed(result, false);
await OnClosed(result, false);
resultTaskCompletionSource.TrySetResult(result);
}

/// <summary>
Expand All @@ -225,19 +248,22 @@ internal virtual void OnOpened() =>
/// /// <param name="wasDismissedByTappingOutsideOfPopup">
/// Sets the <see cref="PopupClosedEventArgs"/> Property of <see cref="PopupClosedEventArgs.WasDismissedByTappingOutsideOfPopup"/>/>.
/// </param>
protected void OnClosed(object? result, bool wasDismissedByTappingOutsideOfPopup)
protected virtual async Task OnClosed(object? result, bool wasDismissedByTappingOutsideOfPopup)
{
((IPopup)this).OnClosed(result);

await popupDismissedTaskCompletionSource.Task;

dismissWeakEventManager.HandleEvent(this, new PopupClosedEventArgs(result, wasDismissedByTappingOutsideOfPopup), nameof(Closed));
}

/// <summary>
/// Invoked when the popup is dismissed by tapping outside of the popup.
/// </summary>
protected internal virtual void OnDismissedByTappingOutsideOfPopup()
protected internal virtual async Task OnDismissedByTappingOutsideOfPopup()
{
taskCompletionSource.TrySetResult(ResultWhenUserTapsOutsideOfPopup);
OnClosed(ResultWhenUserTapsOutsideOfPopup, true);
await OnClosed(ResultWhenUserTapsOutsideOfPopup, true);
resultTaskCompletionSource.TrySetResult(ResultWhenUserTapsOutsideOfPopup);
}

/// <summary>
Expand Down Expand Up @@ -269,7 +295,7 @@ static void OnColorChanged(BindableObject bindable, object oldValue, object newV

void IPopup.OnOpened() => OnOpened();

void IPopup.OnDismissedByTappingOutsideOfPopup() => OnDismissedByTappingOutsideOfPopup();
async void IPopup.OnDismissedByTappingOutsideOfPopup() => await OnDismissedByTappingOutsideOfPopup();

void IPropertyPropagationController.PropagatePropertyChanged(string propertyName) =>
PropertyPropagationExtensions.PropagatePropertyChanged(propertyName, this, ((IVisualTreeElement)this).GetVisualChildren());
Expand Down

0 comments on commit 0b86ba1

Please sign in to comment.