Skip to content

Commit 98449fc

Browse files
[Popup] Add Popup.CanBeDismissedByTappingOutsideOfPopup (#2753)
* Add `Popup.CanBeDismissedByTappingOutsideOfPopup` * Add GetCanBeDismissedByTappingOutsideOfPopup() * Add PopupDefaults Test * Update PopupPageTests.cs
1 parent 6133ad1 commit 98449fc

File tree

5 files changed

+90
-7
lines changed

5 files changed

+90
-7
lines changed

src/CommunityToolkit.Maui.UnitTests/Views/Popup/PopupPageTests.cs

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
using CommunityToolkit.Maui.Core;
22
using CommunityToolkit.Maui.Extensions;
3-
using CommunityToolkit.Maui.UnitTests.Extensions;
4-
using CommunityToolkit.Maui.UnitTests.Services;
53
using CommunityToolkit.Maui.Views;
64
using FluentAssertions;
75
using Microsoft.Maui.Controls.PlatformConfiguration;
@@ -219,6 +217,50 @@ public void PopupPageT_Close_ShouldThrowOperationCanceledException_WhenTokenIsCa
219217
act.Should().ThrowAsync<OperationCanceledException>();
220218
}
221219

220+
[Fact]
221+
public void TapGestureRecognizer_VerifyCanBeDismissedByTappingOutsideOfPopup_ShouldNotExecuteWhenEitherFalse()
222+
{
223+
// Arrange
224+
var view = new Popup();
225+
var popupOptions = new PopupOptions();
226+
227+
// Act
228+
var popupPage = new PopupPage(view, popupOptions);
229+
var tapGestureRecognizer = popupPage.Content.Children.OfType<BoxView>().Single().GestureRecognizers.OfType<TapGestureRecognizer>().Single();
230+
231+
// Assert
232+
Assert.True(tapGestureRecognizer.Command?.CanExecute(null));
233+
234+
// Act
235+
view.CanBeDismissedByTappingOutsideOfPopup = false;
236+
popupOptions.CanBeDismissedByTappingOutsideOfPopup = false;
237+
238+
// Assert
239+
Assert.False(tapGestureRecognizer.Command?.CanExecute(null));
240+
241+
// Act
242+
view.CanBeDismissedByTappingOutsideOfPopup = true;
243+
popupOptions.CanBeDismissedByTappingOutsideOfPopup = false;
244+
245+
// Assert
246+
Assert.False(tapGestureRecognizer.Command?.CanExecute(null));
247+
248+
// Act
249+
view.CanBeDismissedByTappingOutsideOfPopup = false;
250+
popupOptions.CanBeDismissedByTappingOutsideOfPopup = true;
251+
252+
// Assert
253+
Assert.False(tapGestureRecognizer.Command?.CanExecute(null));
254+
255+
// Act
256+
view.CanBeDismissedByTappingOutsideOfPopup = true;
257+
popupOptions.CanBeDismissedByTappingOutsideOfPopup = true;
258+
259+
// Assert
260+
Assert.True(tapGestureRecognizer.Command?.CanExecute(null));
261+
262+
}
263+
222264
[Fact]
223265
public void Constructor_WithViewAndPopupOptions_SetsCorrectProperties()
224266
{

src/CommunityToolkit.Maui.UnitTests/Views/Popup/PopupTests.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ public void PopupBackgroundColor_DefaultValue_ShouldBeWhite()
1414
{
1515
Assert.Equal(PopupDefaults.BackgroundColor, Colors.White);
1616
}
17+
18+
[Fact]
19+
public void CanBeDismissedByTappingOutsideOfPopup_DefaultValue_ShouldBeTrue()
20+
{
21+
var popup = new Popup();
22+
Assert.Equal(PopupDefaults.CanBeDismissedByTappingOutsideOfPopup, popup.CanBeDismissedByTappingOutsideOfPopup);
23+
}
1724

1825
[Fact]
1926
public void Margin_DefaultValue_ShouldBeDefaultThickness()

src/CommunityToolkit.Maui/Primitives/Defaults/PopupDefaults.shared.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,9 @@ static class PopupDefaults
3131
/// Default value for <see cref="VisualElement.BackgroundColor"/> BackgroundColor
3232
/// </summary>
3333
public static Color BackgroundColor { get; } = Colors.White;
34+
35+
/// <summary>
36+
/// Default value for <see cref="Popup.CanBeDismissedByTappingOutsideOfPopup"/>
37+
/// </summary>
38+
public const bool CanBeDismissedByTappingOutsideOfPopup = PopupOptionsDefaults.CanBeDismissedByTappingOutsideOfPopup;
3439
}

src/CommunityToolkit.Maui/Views/Popup/Popup.shared.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ public partial class Popup : ContentView
2626
/// Bindable property to set the vertical position of the <see cref="Popup"/> when displayed on screen
2727
/// </summary>
2828
public static new readonly BindableProperty VerticalOptionsProperty = View.VerticalOptionsProperty;
29+
30+
/// <summary>
31+
/// Backing BindableProperty for the <see cref="CanBeDismissedByTappingOutsideOfPopup"/> property.
32+
/// </summary>
33+
public static readonly BindableProperty CanBeDismissedByTappingOutsideOfPopupProperty = BindableProperty.Create(nameof(CanBeDismissedByTappingOutsideOfPopup), typeof(bool), typeof(Popup), PopupDefaults.CanBeDismissedByTappingOutsideOfPopup);
2934

3035
/// <summary>
3136
/// Initializes Popup
@@ -83,6 +88,17 @@ public Popup()
8388
get => base.VerticalOptions;
8489
set => base.VerticalOptions = value;
8590
}
91+
92+
/// <inheritdoc cref="IPopupOptions.CanBeDismissedByTappingOutsideOfPopup"/> />
93+
/// <remarks>
94+
/// When true and the user taps outside the popup, it will dismiss.
95+
/// On Android - when false the hardware back button is disabled.
96+
/// </remarks>
97+
public bool CanBeDismissedByTappingOutsideOfPopup
98+
{
99+
get => (bool)GetValue(CanBeDismissedByTappingOutsideOfPopupProperty);
100+
set => SetValue(CanBeDismissedByTappingOutsideOfPopupProperty, value);
101+
}
86102

87103
/// <summary>
88104
/// Close the Popup.

src/CommunityToolkit.Maui/Views/Popup/PopupPage.shared.cs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,15 @@ public PopupPage(Popup popup, IPopupOptions popupOptions)
4646
{
4747
popupOptions.OnTappingOutsideOfPopup?.Invoke();
4848
await CloseAsync(new PopupResult(true));
49-
}, () => popupOptions.CanBeDismissedByTappingOutsideOfPopup);
49+
}, () => GetCanBeDismissedByTappingOutsideOfPopup(popup, popupOptions));
5050

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

54+
popup.PropertyChanged += HandlePopupPropertyChanged;
5455
if (popupOptions is BindableObject bindablePopupOptions)
5556
{
56-
bindablePopupOptions.PropertyChanged += HandlePopupPropertyChanged;
57+
bindablePopupOptions.PropertyChanged += HandlePopupOptionsPropertyChanged;
5758
}
5859

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

107108
protected override bool OnBackButtonPressed()
108109
{
109-
// Only close the Popup if PopupOptions.CanBeDismissedByTappingOutsideOfPopup is true
110-
if (popupOptions.CanBeDismissedByTappingOutsideOfPopup)
110+
// Only close the Popup if CanBeDismissedByTappingOutsideOfPopup is true
111+
if (GetCanBeDismissedByTappingOutsideOfPopup(popup, popupOptions))
111112
{
112113
CloseAsync(new PopupResult(true), CancellationToken.None).SafeFireAndForget();
113114
}
@@ -152,13 +153,25 @@ protected override void OnNavigatedTo(NavigatedToEventArgs args)
152153
return popup;
153154
}
154155

155-
void HandlePopupPropertyChanged(object? sender, PropertyChangedEventArgs e)
156+
// Only dismiss when a user taps outside Popup when **both** Popup.CanBeDismissedByTappingOutsideOfPopup and PopupOptions.CanBeDismissedByTappingOutsideOfPopup are true
157+
// If either value is false, do not dismiss Popup
158+
static bool GetCanBeDismissedByTappingOutsideOfPopup(in Popup popup, in IPopupOptions popupOptions) => popup.CanBeDismissedByTappingOutsideOfPopup & popupOptions.CanBeDismissedByTappingOutsideOfPopup;
159+
160+
void HandlePopupOptionsPropertyChanged(object? sender, PropertyChangedEventArgs e)
156161
{
157162
if (e.PropertyName == nameof(IPopupOptions.CanBeDismissedByTappingOutsideOfPopup))
158163
{
159164
tapOutsideOfPopupCommand.ChangeCanExecute();
160165
}
161166
}
167+
168+
void HandlePopupPropertyChanged(object? sender, PropertyChangedEventArgs e)
169+
{
170+
if (e.PropertyName == Popup.CanBeDismissedByTappingOutsideOfPopupProperty.PropertyName)
171+
{
172+
tapOutsideOfPopupCommand.ChangeCanExecute();
173+
}
174+
}
162175

163176
void IQueryAttributable.ApplyQueryAttributes(IDictionary<string, object> query)
164177
{

0 commit comments

Comments
 (0)