Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 44 additions & 2 deletions src/CommunityToolkit.Maui.UnitTests/Views/Popup/PopupPageTests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
using CommunityToolkit.Maui.Core;
using CommunityToolkit.Maui.Extensions;
using CommunityToolkit.Maui.UnitTests.Extensions;
using CommunityToolkit.Maui.UnitTests.Services;
using CommunityToolkit.Maui.Views;
using FluentAssertions;
using Microsoft.Maui.Controls.PlatformConfiguration;
Expand Down Expand Up @@ -219,6 +217,50 @@ public void PopupPageT_Close_ShouldThrowOperationCanceledException_WhenTokenIsCa
act.Should().ThrowAsync<OperationCanceledException>();
}

[Fact]
public void TapGestureRecognizer_VerifyCanBeDismissedByTappingOutsideOfPopup_ShouldNotExecuteWhenEitherFalse()
{
// Arrange
var view = new Popup();
var popupOptions = new PopupOptions();

// Act
var popupPage = new PopupPage(view, popupOptions);
var tapGestureRecognizer = popupPage.Content.Children.OfType<BoxView>().Single().GestureRecognizers.OfType<TapGestureRecognizer>().Single();

// Assert
Assert.True(tapGestureRecognizer.Command?.CanExecute(null));

// Act
view.CanBeDismissedByTappingOutsideOfPopup = false;
popupOptions.CanBeDismissedByTappingOutsideOfPopup = false;

// Assert
Assert.False(tapGestureRecognizer.Command?.CanExecute(null));

// Act
view.CanBeDismissedByTappingOutsideOfPopup = true;
popupOptions.CanBeDismissedByTappingOutsideOfPopup = false;

// Assert
Assert.False(tapGestureRecognizer.Command?.CanExecute(null));

// Act
view.CanBeDismissedByTappingOutsideOfPopup = false;
popupOptions.CanBeDismissedByTappingOutsideOfPopup = true;

// Assert
Assert.False(tapGestureRecognizer.Command?.CanExecute(null));

// Act
view.CanBeDismissedByTappingOutsideOfPopup = true;
popupOptions.CanBeDismissedByTappingOutsideOfPopup = true;

// Assert
Assert.True(tapGestureRecognizer.Command?.CanExecute(null));

}

[Fact]
public void Constructor_WithViewAndPopupOptions_SetsCorrectProperties()
{
Expand Down
7 changes: 7 additions & 0 deletions src/CommunityToolkit.Maui.UnitTests/Views/Popup/PopupTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ public void PopupBackgroundColor_DefaultValue_ShouldBeWhite()
{
Assert.Equal(PopupDefaults.BackgroundColor, Colors.White);
}

[Fact]
public void CanBeDismissedByTappingOutsideOfPopup_DefaultValue_ShouldBeTrue()
{
var popup = new Popup();
Assert.Equal(PopupDefaults.CanBeDismissedByTappingOutsideOfPopup, popup.CanBeDismissedByTappingOutsideOfPopup);
}

[Fact]
public void Margin_DefaultValue_ShouldBeDefaultThickness()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,9 @@ static class PopupDefaults
/// Default value for <see cref="VisualElement.BackgroundColor"/> BackgroundColor
/// </summary>
public static Color BackgroundColor { get; } = Colors.White;

/// <summary>
/// Default value for <see cref="Popup.CanBeDismissedByTappingOutsideOfPopup"/>
/// </summary>
public const bool CanBeDismissedByTappingOutsideOfPopup = PopupOptionsDefaults.CanBeDismissedByTappingOutsideOfPopup;
}
16 changes: 16 additions & 0 deletions src/CommunityToolkit.Maui/Views/Popup/Popup.shared.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ public partial class Popup : ContentView
/// Bindable property to set the vertical position of the <see cref="Popup"/> when displayed on screen
/// </summary>
public static new readonly BindableProperty VerticalOptionsProperty = View.VerticalOptionsProperty;

/// <summary>
/// Backing BindableProperty for the <see cref="CanBeDismissedByTappingOutsideOfPopup"/> property.
/// </summary>
public static readonly BindableProperty CanBeDismissedByTappingOutsideOfPopupProperty = BindableProperty.Create(nameof(CanBeDismissedByTappingOutsideOfPopup), typeof(bool), typeof(Popup), PopupDefaults.CanBeDismissedByTappingOutsideOfPopup);

/// <summary>
/// Initializes Popup
Expand Down Expand Up @@ -83,6 +88,17 @@ public Popup()
get => base.VerticalOptions;
set => base.VerticalOptions = value;
}

/// <inheritdoc cref="IPopupOptions.CanBeDismissedByTappingOutsideOfPopup"/> />
/// <remarks>
/// When true and the user taps outside the popup, it will dismiss.
/// On Android - when false the hardware back button is disabled.
/// </remarks>
public bool CanBeDismissedByTappingOutsideOfPopup
{
get => (bool)GetValue(CanBeDismissedByTappingOutsideOfPopupProperty);
set => SetValue(CanBeDismissedByTappingOutsideOfPopupProperty, value);
}

/// <summary>
/// Close the Popup.
Expand Down
23 changes: 18 additions & 5 deletions src/CommunityToolkit.Maui/Views/Popup/PopupPage.shared.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,15 @@ public PopupPage(Popup popup, IPopupOptions popupOptions)
{
popupOptions.OnTappingOutsideOfPopup?.Invoke();
await CloseAsync(new PopupResult(true));
}, () => popupOptions.CanBeDismissedByTappingOutsideOfPopup);
}, () => GetCanBeDismissedByTappingOutsideOfPopup(popup, popupOptions));

// Only set the content if the parent constructor hasn't set the content already; don't override content if it already exists
base.Content = new PopupPageLayout(popup, popupOptions, tapOutsideOfPopupCommand);

popup.PropertyChanged += HandlePopupPropertyChanged;
if (popupOptions is BindableObject bindablePopupOptions)
{
bindablePopupOptions.PropertyChanged += HandlePopupPropertyChanged;
bindablePopupOptions.PropertyChanged += HandlePopupOptionsPropertyChanged;
}

this.SetBinding(BindingContextProperty, static (Popup x) => x.BindingContext, source: popup, mode: BindingMode.OneWay);
Expand Down Expand Up @@ -106,8 +107,8 @@ public async Task CloseAsync(PopupResult result, CancellationToken token = defau

protected override bool OnBackButtonPressed()
{
// Only close the Popup if PopupOptions.CanBeDismissedByTappingOutsideOfPopup is true
if (popupOptions.CanBeDismissedByTappingOutsideOfPopup)
// Only close the Popup if CanBeDismissedByTappingOutsideOfPopup is true
if (GetCanBeDismissedByTappingOutsideOfPopup(popup, popupOptions))
{
CloseAsync(new PopupResult(true), CancellationToken.None).SafeFireAndForget();
}
Expand Down Expand Up @@ -152,13 +153,25 @@ protected override void OnNavigatedTo(NavigatedToEventArgs args)
return popup;
}

void HandlePopupPropertyChanged(object? sender, PropertyChangedEventArgs e)
// Only dismiss when a user taps outside Popup when **both** Popup.CanBeDismissedByTappingOutsideOfPopup and PopupOptions.CanBeDismissedByTappingOutsideOfPopup are true
// If either value is false, do not dismiss Popup
static bool GetCanBeDismissedByTappingOutsideOfPopup(in Popup popup, in IPopupOptions popupOptions) => popup.CanBeDismissedByTappingOutsideOfPopup & popupOptions.CanBeDismissedByTappingOutsideOfPopup;

void HandlePopupOptionsPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(IPopupOptions.CanBeDismissedByTappingOutsideOfPopup))
{
tapOutsideOfPopupCommand.ChangeCanExecute();
}
}

void HandlePopupPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == Popup.CanBeDismissedByTappingOutsideOfPopupProperty.PropertyName)
{
tapOutsideOfPopupCommand.ChangeCanExecute();
}
}

void IQueryAttributable.ApplyQueryAttributes(IDictionary<string, object> query)
{
Expand Down
Loading