Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PopupService #1165

Merged
merged 28 commits into from
Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
a380b84
Added initial PopupService implementation
bijington Feb 20, 2023
3d13946
Tweaks to allowing parameters to be passed to the view model behind a…
bijington Apr 28, 2023
6b5ff39
Tidy up xml docs
bijington Apr 30, 2023
8cd9c7f
Some unit tests
bijington May 6, 2023
48c1291
Only create a view model instance if the BindingContext hasn't been set
bijington May 10, 2023
2653cc6
Remove the reliance on IQueryAttributable in favour of our own interface
bijington May 10, 2023
71eeb15
A better way to find the current Page
bijington May 10, 2023
f519b67
Readonly dictionary and some safety checking around expected BindingC…
bijington May 10, 2023
bddfcd6
Merge branch 'main' into feature/sl/981-add-popupservice
bijington Jul 17, 2023
b616186
A different attempt at passing parameters without an explicit interface
bijington Aug 10, 2023
4e4c4dd
Merge branch 'main' into feature/sl/981-add-popupservice
bijington Aug 11, 2023
9f30cce
Update src/CommunityToolkit.Maui/PopupService.cs
bijington Aug 13, 2023
fc31da8
Now is a time for test
bijington Aug 13, 2023
bfc0517
Merge branch 'feature/sl/981-add-popupservice' of github.com:Communit…
bijington Aug 13, 2023
5977554
Remove unnecessary changes
bijington Aug 13, 2023
2a45736
Merge branch 'main' into feature/sl/981-add-popupservice
bijington Aug 13, 2023
fc9a1e1
Sample to perform a long running process
bijington Aug 15, 2023
95027ec
Merge branch 'feature/sl/981-add-popupservice' of github.com:Communit…
bijington Aug 15, 2023
d1fd1d9
Provide ability to close popup from within popup view model
bijington Aug 16, 2023
bf39d26
Merge branch 'main' into feature/sl/981-add-popupservice
TheCodeTraveler Aug 27, 2023
0f5fed0
Merge branch 'main' into feature/sl/981-add-popupservice
bijington Sep 4, 2023
3f0599f
Prevent unnecessary instance being created
bijington Sep 4, 2023
78a74e9
Merge branch 'feature/sl/981-add-popupservice' of github.com:Communit…
bijington Sep 4, 2023
6739d13
Merge branch 'main' into feature/sl/981-add-popupservice
bijington Sep 17, 2023
e50f1e6
Merge branch 'main' into feature/sl/981-add-popupservice
bijington Oct 26, 2023
5de3828
Merge branch 'main' into feature/sl/981-add-popupservice
TheCodeTraveler Oct 31, 2023
77ffdc9
Refactor `CurrentPage`, Add Default Constructor, Refactor `ValidateBi…
TheCodeTraveler Nov 1, 2023
5276255
Update Unit Tests
TheCodeTraveler Nov 1, 2023
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
5 changes: 3 additions & 2 deletions samples/CommunityToolkit.Maui.Sample/MauiProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,9 @@ static void RegisterViewsAndViewModels(in IServiceCollection services)
services.AddTransientWithShellRoute<StylePopupPage, StylePopupViewModel>();

// Add Popups
services.AddTransient<CsharpBindingPopup, CsharpBindingPopupViewModel>();
services.AddTransient<XamlBindingPopup, XamlBindingPopupViewModel>();
services.AddTransientPopup<CsharpBindingPopup, CsharpBindingPopupViewModel>();
services.AddTransientPopup<UpdatingPopup, UpdatingPopupViewModel>();
services.AddTransientPopup<XamlBindingPopup, XamlBindingPopupViewModel>();
}

static void RegisterEssentials(in IServiceCollection services)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<pages:BasePage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
<pages:BasePage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:pages="clr-namespace:CommunityToolkit.Maui.Sample.Pages"
xmlns:viewModels="clr-namespace:CommunityToolkit.Maui.Sample.ViewModels.Views"
Expand Down Expand Up @@ -28,7 +28,9 @@

<Button Text="XAML Binding Popup" Clicked="HandleXamlBindingPopupPopupButtonClicked" />

<Button Text="C# Binding Popup" Clicked="HandleCsharpBindingPopupButtonClicked" />
<Button Text="C# Binding Popup" Command="{Binding CsharpBindingPopupCommand}" />

<Button Text="Updating Popup" Command="{Binding UpdatingPopupCommand}" />
</VerticalStackLayout>
</ScrollView>
</ContentPage.Content>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,10 @@ public sealed partial class CsharpBindingPopupViewModel : BaseViewModel
{
public string Title { get; } = "C# Binding Popup";

public string Message { get; } = "This is a platform specific popup with a .NET MAUI View being rendered. The behaviors of the popup will confirm to 100% this platform look and feel, but still allows you to use your .NET MAUI Controls.";
public string Message { get; private set; } = "";

internal void Load(string message)
{
Message = message;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
namespace CommunityToolkit.Maui.Sample.ViewModels.Views;
using CommunityToolkit.Maui.Core;
using CommunityToolkit.Mvvm.Input;

public class MultiplePopupViewModel : BaseViewModel
namespace CommunityToolkit.Maui.Sample.ViewModels.Views;

public partial class MultiplePopupViewModel : BaseViewModel
{
readonly IPopupService popupService;

public MultiplePopupViewModel(IPopupService popupService)
{
this.popupService = popupService;
}

[RelayCommand]
Task OnCsharpBindingPopup()
{
return this.popupService.ShowPopupAsync<CsharpBindingPopupViewModel>(
onPresenting: viewModel => viewModel.Load("This is a platform specific popup with a .NET MAUI View being rendered. The behaviors of the popup will confirm to 100% this platform look and feel, but still allows you to use your .NET MAUI Controls."));
}

[RelayCommand]
Task OnUpdatingPopup()
{
return this.popupService.ShowPopupAsync<UpdatingPopupViewModel>(
onPresenting: viewModel => viewModel.PerformUpdates(10));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using CommunityToolkit.Mvvm.Input;

namespace CommunityToolkit.Maui.Sample.ViewModels.Views;

public partial class UpdatingPopupViewModel : BaseViewModel
{
[Mvvm.ComponentModel.ObservableProperty]
string message = "";

[Mvvm.ComponentModel.ObservableProperty]
[Mvvm.ComponentModel.NotifyCanExecuteChangedFor(nameof(FinishCommand))]
double updateProgress;

readonly WeakEventManager finishedEventManager = new();

public event EventHandler<EventArgs> Finished
{
add => finishedEventManager.AddEventHandler(value);
remove => finishedEventManager.RemoveEventHandler(value);
}

public UpdatingPopupViewModel()
{
}

internal async void PerformUpdates(int numberOfUpdates)
{
double updateTotalForPercentage = numberOfUpdates + 1;

for (var update = 1; update <= numberOfUpdates; update++)
{
this.Message = $"Updating {update} of {numberOfUpdates}";

this.UpdateProgress = update / updateTotalForPercentage;

await Task.Delay(TimeSpan.FromSeconds(3));
}

this.UpdateProgress = 1d;
this.Message = "Updates complete";
}

[RelayCommand(CanExecute = nameof(CanFinish))]
void OnFinish()
{
finishedEventManager.HandleEvent(this, EventArgs.Empty, nameof(Finished));
}

bool CanFinish() => this.UpdateProgress == 1d;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8" ?>
<toolkit:Popup
x:Class="CommunityToolkit.Maui.Sample.Views.Popups.UpdatingPopup"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
CanBeDismissedByTappingOutsideOfPopup="False">

<VerticalStackLayout Style="{StaticResource PopupLayout}" Spacing="12" >
<VerticalStackLayout.Resources>
<ResourceDictionary>
<Style x:Key="Title" TargetType="Label">
<Setter Property="FontSize" Value="26" />
<Setter Property="FontAttributes" Value="Bold" />
<Setter Property="TextColor" Value="#000" />
<Setter Property="VerticalTextAlignment" Value="Center" />
<Setter Property="HorizontalTextAlignment" Value="Center" />
</Style>
<Style x:Key="Divider" TargetType="BoxView">
<Setter Property="HeightRequest" Value="1" />
<Setter Property="Margin" Value="50, 25" />
<Setter Property="Color" Value="#c3c3c3" />
</Style>
<Style x:Key="Content" TargetType="Label">
<Setter Property="HorizontalTextAlignment" Value="Center" />
<Setter Property="VerticalTextAlignment" Value="Center" />
</Style>
<Style x:Key="PopupLayout" TargetType="StackLayout">
<Setter Property="Padding" Value="{OnPlatform Android=20, WinUI=20, iOS=12, MacCatalyst=5, Tizen=20}" />
</Style>
<Style x:Key="ConfirmButton" TargetType="Button">
<Setter Property="VerticalOptions" Value="EndAndExpand" />
</Style>

<x:Double x:Key="ComparingValue">1.0</x:Double>
</ResourceDictionary>
</VerticalStackLayout.Resources>

<Label Style="{StaticResource Title}" Text="Updating" />

<BoxView Style="{StaticResource Divider}" />

<ActivityIndicator
IsRunning="{Binding UpdateProgress, Converter={toolkit:CompareConverter ComparingValue={StaticResource ComparingValue}, ComparisonOperator=Smaller, FalseObject=False, TrueObject=True}}"/>

<Label Style="{StaticResource Content}" Text="{Binding Message}" />

<ProgressBar Progress="{Binding UpdateProgress}" />

<Button
Command="{Binding FinishCommand}"
Style="{StaticResource ConfirmButton}"
Text="Finish" />
</VerticalStackLayout>
</toolkit:Popup>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using CommunityToolkit.Maui.Sample.ViewModels.Views;
using CommunityToolkit.Maui.Views;

namespace CommunityToolkit.Maui.Sample.Views.Popups;

public partial class UpdatingPopup : Popup
{
public UpdatingPopup(UpdatingPopupViewModel updatingPopupViewModel)
{
InitializeComponent();
BindingContext = updatingPopupViewModel;

updatingPopupViewModel.Finished += OnUpdatingPopupViewModelFinished;
}

void OnUpdatingPopupViewModelFinished(object? sender, EventArgs e)
{
this.Close();
}
}
54 changes: 54 additions & 0 deletions src/CommunityToolkit.Maui.Core/IPopupService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System.ComponentModel;

namespace CommunityToolkit.Maui.Core;

/// <summary>
/// Provides a mechanism for displaying <see cref="CommunityToolkit.Maui.Core.IPopup"/>s based on the underlying view model.
/// </summary>
public interface IPopupService
{
/// <summary>
/// Resolves and displays a <see cref="CommunityToolkit.Maui.Core.IPopup"/> and <typeparamref name="TViewModel"/> pair that was registered with <c>AddTransientPopup</c>.
/// </summary>
/// <typeparam name="TViewModel">The type of the view model registered with the <see cref="CommunityToolkit.Maui.Core.IPopup"/>.</typeparam>
void ShowPopup<TViewModel>() where TViewModel : INotifyPropertyChanged;
bijington marked this conversation as resolved.
Show resolved Hide resolved
bijington marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Resolves and displays a <see cref="CommunityToolkit.Maui.Core.IPopup"/> and <typeparamref name="TViewModel"/> pair that was registered with <c>AddTransientPopup</c>.
/// The supplied <paramref name="onPresenting"/> provides a mechanism to invoke any methods on your view model in order to load or pass data to it.
/// </summary>
/// <typeparam name="TViewModel">The type of the view model registered with the <see cref="CommunityToolkit.Maui.Core.IPopup"/>.</typeparam>
/// <param name="onPresenting">An <see cref="Action{TViewModel}"/> that will be performed before the popup is presented.</param>
void ShowPopup<TViewModel>(Action<TViewModel> onPresenting) where TViewModel : INotifyPropertyChanged;
TheCodeTraveler marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Resolves and displays a <see cref="CommunityToolkit.Maui.Core.IPopup"/> and <typeparamref name="TViewModel"/> pair that was registered with <c>AddTransientPopup</c>.
/// </summary>
/// <typeparam name="TViewModel">The type of the view model registered with the <see cref="CommunityToolkit.Maui.Core.IPopup"/>.</typeparam>
/// <param name="viewModel">The view model to use as the <c>BindingContext</c> for the <see cref="CommunityToolkit.Maui.Core.IPopup"/>.</param>
void ShowPopup<TViewModel>(TViewModel viewModel) where TViewModel : INotifyPropertyChanged;

/// <summary>
/// Resolves and displays a <see cref="CommunityToolkit.Maui.Core.IPopup"/> and <typeparamref name="TViewModel"/> pair that was registered with <c>AddTransientPopup</c>.
/// </summary>
/// <typeparam name="TViewModel">The type of the view model registered with the <see cref="CommunityToolkit.Maui.Core.IPopup"/>.</typeparam>
/// <returns>A <see cref="Task"/> that can be awaited to return the result of the <see cref="CommunityToolkit.Maui.Core.IPopup"/> once it has been dismissed.</returns>
Task<object?> ShowPopupAsync<TViewModel>() where TViewModel : INotifyPropertyChanged;

/// <summary>
/// Resolves and displays a <see cref="CommunityToolkit.Maui.Core.IPopup"/> and <typeparamref name="TViewModel"/> pair that was registered with <c>AddTransientPopup</c>.
/// The supplied <paramref name="onPresenting"/> provides a mechanism to invoke any methods on your view model in order to load or pass data to it.
/// </summary>
/// <typeparam name="TViewModel">The type of the view model registered with the <see cref="CommunityToolkit.Maui.Core.IPopup"/>.</typeparam>
/// <returns>A <see cref="Task"/> that can be awaited to return the result of the <see cref="CommunityToolkit.Maui.Core.IPopup"/> once it has been dismissed.</returns>
/// <param name="onPresenting">An <see cref="Action{TViewModel}"/> that will be performed before the popup is presented.</param>
Task<object?> ShowPopupAsync<TViewModel>(Action<TViewModel> onPresenting) where TViewModel : INotifyPropertyChanged;

/// <summary>
/// Resolves and displays a <see cref="CommunityToolkit.Maui.Core.IPopup"/> and <typeparamref name="TViewModel"/> pair that was registered with <c>AddTransientPopup</c>.
/// </summary>
/// <typeparam name="TViewModel">The type of the view model registered with the <see cref="CommunityToolkit.Maui.Core.IPopup"/>.</typeparam>
/// <param name="viewModel">The view model to use as the <c>BindingContext</c> for the <see cref="CommunityToolkit.Maui.Core.IPopup"/>.</param>
/// <returns>A <see cref="Task"/> that can be awaited to return the result of the <see cref="CommunityToolkit.Maui.Core.IPopup"/> once it has been dismissed.</returns>
Task<object?> ShowPopupAsync<TViewModel>(TViewModel viewModel) where TViewModel : INotifyPropertyChanged;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@

public class MockPageViewModel : BindableObject
{
public bool HasLoaded { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ public class MockPopupHandler : ElementHandler<IPopup, object>
{
public static CommandMapper<IPopup, MockPopupHandler> PopUpCommandMapper = new(ElementCommandMapper)
{
[nameof(IPopup.OnOpened)] = MapOnOpened
[nameof(IPopup.OnOpened)] = MapOnOpened,
[nameof(IPopup.OnClosed)] = MapOnClosed,
};

public MockPopupHandler() : base(new PropertyMapper<IView>(), PopUpCommandMapper)
Expand All @@ -30,5 +31,11 @@ protected override object CreatePlatformElement()
static void MapOnOpened(MockPopupHandler arg1, IPopup arg2, object? arg3)
{
arg1.OnOpenedCount++;
arg2.OnOpened();
}

static void MapOnClosed(MockPopupHandler handler, IPopup view, object? result)
{
view.HandlerCompleteTCS.TrySetResult();
}
}
Loading