Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<ItemGroup>
<!--Fix vulnerabilities-->
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="System.Private.Uri" Version="4.3.2" />
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ public void ShowPopupAsync_WithViewType_SetsCorrectDefaults()
navigation.ShowPopup(label);

popupPage = (PopupPage)navigation.ModalStack[0];
autogeneratedPopup = (Popup)(((Border)popupPage.Content.Children[0]).Content ?? throw new InvalidOperationException("Border Content cannot be null"));
autogeneratedPopup = (Popup)(((Border)popupPage.Content.Children.Last()).Content ?? throw new InvalidOperationException("Border Content cannot be null"));

// Assert
Assert.Equal(PopupDefaults.BackgroundColor, autogeneratedPopup.BackgroundColor);
Expand Down Expand Up @@ -435,7 +435,7 @@ public void ShowPopupAsync_WithCustomOptions_AppliesOptions()

var popupPage = (PopupPage)navigation.ModalStack[0];
var popupPageContent = popupPage.Content;
var border = (Border)popupPageContent.Children[0];
var border = popupPageContent.Children.OfType<Border>().Single();
var popup = border.Content;

// Assert
Expand Down Expand Up @@ -507,7 +507,7 @@ public void ShowPopupAsync_Shell_WithCustomOptions_AppliesOptions()

var popupPage = (PopupPage)shellNavigation.ModalStack[0];
var popupPageContent = popupPage.Content;
var border = (Border)popupPageContent.Children[0];
var border = popupPageContent.Children.OfType<Border>().Single();
var popup = border.Content;

// Assert
Expand Down Expand Up @@ -579,7 +579,7 @@ public void ShowPopupAsyncWithView_WithCustomOptions_AppliesOptions()

var popupPage = (PopupPage)navigation.ModalStack[0];
var popupPageContent = popupPage.Content;
var border = (Border)popupPageContent.Children[0];
var border = popupPageContent.Children.OfType<Border>().Single();
var popup = (Popup)(border.Content ?? throw new InvalidCastException());

// Assert
Expand Down Expand Up @@ -660,7 +660,7 @@ public void ShowPopupAsyncWithView_Shell_WithCustomOptions_AppliesOptions()

var popupPage = (PopupPage)shellNavigation.ModalStack[0];
var popupPageContent = popupPage.Content;
var border = (Border)popupPageContent.Children[0];
var border = popupPageContent.Children.OfType<Border>().Single();
var popup = (Popup)(border.Content ?? throw new InvalidCastException());

// Assert
Expand Down Expand Up @@ -1149,7 +1149,7 @@ public async Task ShowPopupAsync_ReferenceTypeShouldReturnNull_WhenPopupTapGestu
var popupPage = (PopupPage)navigation.ModalStack[0];
popupPage.PopupClosed += HandlePopupClosed;

var tapGestureRecognizer = (TapGestureRecognizer)popupPage.Content.GestureRecognizers[0];
var tapGestureRecognizer = GetTapOutsideGestureRecognizer(popupPage);
tapGestureRecognizer.Command?.Execute(null);

var popupClosedResult = await popupClosedTCS.Task;
Expand Down Expand Up @@ -1184,7 +1184,7 @@ public async Task ShowPopupAsync_Shell_ReferenceTypeShouldReturnNull_WhenPopupTa
var popupPage = (PopupPage)shellNavigation.ModalStack[0];
popupPage.PopupClosed += HandlePopupClosed;

var tapGestureRecognizer = (TapGestureRecognizer)popupPage.Content.GestureRecognizers[0];
var tapGestureRecognizer = GetTapOutsideGestureRecognizer(popupPage);
tapGestureRecognizer.Command?.Execute(null);

var popupClosedResult = await popupClosedTCS.Task;
Expand Down Expand Up @@ -1212,7 +1212,7 @@ public async Task ShowPopupAsync_NullableValueTypeShouldReturnResult_WhenPopupIs
var popupPage = (PopupPage)navigation.ModalStack[0];
popupPage.PopupClosed += HandlePopupClosed;

var tapGestureRecognizer = (TapGestureRecognizer)popupPage.Content.GestureRecognizers[0];
var tapGestureRecognizer = GetTapOutsideGestureRecognizer(popupPage);
tapGestureRecognizer.Command?.Execute(null);

var popupClosedResult = await popupClosedTCS.Task;
Expand Down Expand Up @@ -1247,7 +1247,7 @@ public async Task ShowPopupAsync_Shell_NullableValueTypeShouldReturnResult_WhenP
var popupPage = (PopupPage)shellNavigation.ModalStack[0];
popupPage.PopupClosed += HandlePopupClosed;

var tapGestureRecognizer = (TapGestureRecognizer)popupPage.Content.GestureRecognizers[0];
var tapGestureRecognizer = GetTapOutsideGestureRecognizer(popupPage);
tapGestureRecognizer.Command?.Execute(null);

var popupClosedResult = await popupClosedTCS.Task;
Expand Down Expand Up @@ -1275,7 +1275,7 @@ public async Task ShowPopupAsync_ValueTypeShouldReturnResult_WhenPopupIsClosedBy
var popupPage = (PopupPage)navigation.ModalStack[0];
popupPage.PopupClosed += HandlePopupClosed;

var tapGestureRecognizer = (TapGestureRecognizer)popupPage.Content.GestureRecognizers[0];
var tapGestureRecognizer = GetTapOutsideGestureRecognizer(popupPage);
tapGestureRecognizer.Command?.Execute(null);

var popupClosedResult = await popupClosedTCS.Task;
Expand Down Expand Up @@ -1311,7 +1311,7 @@ public async Task ShowPopupAsync_Shell_ValueTypeShouldReturnResult_WhenPopupIsCl
var popupPage = (PopupPage)shellNavigation.ModalStack[0];
popupPage.PopupClosed += HandlePopupClosed;

var tapGestureRecognizer = (TapGestureRecognizer)popupPage.Content.GestureRecognizers[0];
var tapGestureRecognizer = GetTapOutsideGestureRecognizer(popupPage);
tapGestureRecognizer.Command?.Execute(null);

var popupClosedResult = await popupClosedTCS.Task;
Expand Down Expand Up @@ -1459,6 +1459,9 @@ public async Task ShowPopupAsync_TaskShouldCompleteWhenCloseAsyncIsCalled()
Assert.Equal(expectedResult, popupResult.Result);
Assert.False(popupResult.WasDismissedByTappingOutsideOfPopup);
}

static TapGestureRecognizer GetTapOutsideGestureRecognizer(PopupPage popupPage) =>
(TapGestureRecognizer)popupPage.Content.Children.OfType<BoxView>().Single().GestureRecognizers[0];
}

sealed class ViewWithIQueryAttributable : Button, IQueryAttributable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ public void ShowPopupAsync_WithCustomOptions_AppliesOptions()

var popupPage = (PopupPage)navigation.ModalStack[0];
var popupPageLayout = popupPage.Content;
var border = (Border)popupPageLayout.Children[0];
var border = popupPageLayout.Children.OfType<Border>().Single();
var popup = border.Content;

// Assert
Expand Down
28 changes: 20 additions & 8 deletions src/CommunityToolkit.Maui.UnitTests/Views/Popup/PopupPageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -250,13 +250,22 @@ public void Constructor_WithViewAndPopupOptions_SetsCorrectProperties()
Assert.Equal(UIModalPresentationStyle.OverFullScreen, popupPage.On<iOS>().ModalPresentationStyle());

// Verify content has tap gesture recognizer attached
Assert.Single(popupPage.Content.GestureRecognizers);
Assert.IsType<TapGestureRecognizer>(popupPage.Content.GestureRecognizers[0]);
var gestureRecognizers = popupPage.Content.Children.OfType<BoxView>().Single().GestureRecognizers;
Assert.Single(gestureRecognizers);
Assert.IsType<TapGestureRecognizer>(gestureRecognizers[0]);

// Verify PopupPageLayout structure
var pageContent = popupPage.Content;
Assert.Single(pageContent.Children);
Assert.IsType<Border>(pageContent.Children[0]);
Assert.Collection(
pageContent.Children,
first =>
{
first.Should().BeOfType<BoxView>();
},
second =>
{
second.Should().BeOfType<Border>();
});

// Verify content binding context is set correctly
Assert.Equal(view.BindingContext, pageContent.BindingContext);
Expand Down Expand Up @@ -316,7 +325,7 @@ public async Task TapGestureRecognizer_ShouldClosePopupWhenCanBeDismissedIsTrue(

var popupPage = new PopupPage(view, popupOptions);

var tapGestureRecognizer = (TapGestureRecognizer)popupPage.Content.GestureRecognizers[0];
var tapGestureRecognizer = GetTapOutsideGestureRecognizer(popupPage);
var command = tapGestureRecognizer.Command;
Assert.NotNull(command);

Expand All @@ -341,7 +350,7 @@ public void TapGestureRecognizer_ShouldNotExecuteWhenCanBeDismissedIsFalse()
};

var popupPage = new PopupPage(view, popupOptions);
var tapGestureRecognizer = (TapGestureRecognizer)popupPage.Content.GestureRecognizers[0];
var tapGestureRecognizer = GetTapOutsideGestureRecognizer(popupPage);
var command = tapGestureRecognizer.Command;

// Act & Assert
Expand Down Expand Up @@ -421,7 +430,7 @@ public void TappingOutside_ShouldNotClosePopup_WhenCanBeDismissedIsFalse()
var popupPage = new PopupPage(view, popupOptions);

// Act
var tapGestureRecognizer = (TapGestureRecognizer)popupPage.Content.GestureRecognizers[0];
var tapGestureRecognizer = GetTapOutsideGestureRecognizer(popupPage);
var command = tapGestureRecognizer.Command;

// Assert
Expand Down Expand Up @@ -472,12 +481,15 @@ public void PopupPage_ShouldRespectLayoutOptions()

// Act
var popupPage = new PopupPage(view, PopupOptions.Empty);
var border = (Border)popupPage.Content.Children[0];
var border = popupPage.Content.Children.OfType<Border>().Single();

// Assert
Assert.Equal(LayoutOptions.Start, border.VerticalOptions);
Assert.Equal(LayoutOptions.End, border.HorizontalOptions);
}

static TapGestureRecognizer GetTapOutsideGestureRecognizer(PopupPage popupPage) =>
(TapGestureRecognizer)popupPage.Content.Children.OfType<BoxView>().Single().GestureRecognizers[0];

// Helper class for testing protected methods
sealed class TestablePopupPage(View view, IPopupOptions popupOptions) : PopupPage(view, popupOptions)
Expand Down
20 changes: 14 additions & 6 deletions src/CommunityToolkit.Maui/Views/Popup/PopupPage.shared.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.ComponentModel;
using System.Globalization;
using System.Windows.Input;
using CommunityToolkit.Maui.Converters;
using CommunityToolkit.Maui.Core;
using CommunityToolkit.Maui.Extensions;
Expand Down Expand Up @@ -41,16 +42,14 @@ public PopupPage(Popup popup, IPopupOptions popupOptions)
this.popup = popup;
this.popupOptions = 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 = new Command(async () =>
{
popupOptions.OnTappingOutsideOfPopup?.Invoke();
await CloseAsync(new PopupResult(true));
}, () => popupOptions.CanBeDismissedByTappingOutsideOfPopup);

Content.GestureRecognizers.Add(new TapGestureRecognizer { Command = tapOutsideOfPopupCommand });

// 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);

if (popupOptions is BindableObject bindablePopupOptions)
{
Expand Down Expand Up @@ -176,7 +175,7 @@ void IQueryAttributable.ApplyQueryAttributes(IDictionary<string, object> query)

internal sealed partial class PopupPageLayout : Grid
{
public PopupPageLayout(in Popup popupContent, in IPopupOptions options)
public PopupPageLayout(in Popup popupContent, in IPopupOptions options, ICommand tapOutsideOfPopupCommand)
{
Background = BackgroundColor = null;

Expand All @@ -185,6 +184,15 @@ public PopupPageLayout(in Popup popupContent, in IPopupOptions options)
BackgroundColor = popupContent.BackgroundColor ??= PopupDefaults.BackgroundColor,
Content = popupContent
};

var backgroundGrid = new BoxView
{
BackgroundColor = Colors.Transparent,
};

backgroundGrid.GestureRecognizers.Add(new TapGestureRecognizer { Command = tapOutsideOfPopupCommand });

Children.Add(backgroundGrid);

// Bind `Popup` values through to Border using OneWay Bindings
border.SetBinding(Border.MarginProperty, static (Popup popup) => popup.Margin, source: popupContent, mode: BindingMode.OneWay);
Expand Down
Loading