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

[Enhancement] Make it easier to access IDialogService from Singletons #3094

Merged
merged 3 commits into from
Feb 29, 2024
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
38 changes: 28 additions & 10 deletions src/Maui/Prism.Maui/Common/MvvmHelpers.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.ComponentModel;
using System.ComponentModel;
using System.Reflection;
using Prism.Dialogs;
using Prism.Navigation;
using Prism.Navigation.Regions;
using Prism.Navigation.Xaml;
Expand All @@ -8,9 +9,9 @@

namespace Prism.Common;

public static class MvvmHelpers

Check warning on line 12 in src/Maui/Prism.Maui/Common/MvvmHelpers.cs

View workflow job for this annotation

GitHub Actions / build-prism-maui / Build Prism.Maui

Missing XML comment for publicly visible type or member 'MvvmHelpers'
{
public static void InvokeViewAndViewModelAction<T>(object view, Action<T> action) where T : class

Check warning on line 14 in src/Maui/Prism.Maui/Common/MvvmHelpers.cs

View workflow job for this annotation

GitHub Actions / build-prism-maui / Build Prism.Maui

Missing XML comment for publicly visible type or member 'MvvmHelpers.InvokeViewAndViewModelAction<T>(object, Action<T>)'
{
if (view is T viewAsT)
{
Expand All @@ -31,7 +32,7 @@
}
}

public static async Task InvokeViewAndViewModelActionAsync<T>(object view, Func<T, Task> action) where T : class

Check warning on line 35 in src/Maui/Prism.Maui/Common/MvvmHelpers.cs

View workflow job for this annotation

GitHub Actions / build-prism-maui / Build Prism.Maui

Missing XML comment for publicly visible type or member 'MvvmHelpers.InvokeViewAndViewModelActionAsync<T>(object, Func<T, Task>)'
{
if (view is T viewAsT)
{
Expand All @@ -52,7 +53,7 @@
}
}

public static void DestroyPage(IView view)

Check warning on line 56 in src/Maui/Prism.Maui/Common/MvvmHelpers.cs

View workflow job for this annotation

GitHub Actions / build-prism-maui / Build Prism.Maui

Missing XML comment for publicly visible type or member 'MvvmHelpers.DestroyPage(IView)'
{
try
{
Expand Down Expand Up @@ -95,7 +96,7 @@
}
}

public static void DestroyWithModalStack(Page page, IList<Page> modalStack)

Check warning on line 99 in src/Maui/Prism.Maui/Common/MvvmHelpers.cs

View workflow job for this annotation

GitHub Actions / build-prism-maui / Build Prism.Maui

Missing XML comment for publicly visible type or member 'MvvmHelpers.DestroyWithModalStack(Page, IList<Page>)'
{
foreach (var childPage in modalStack.Reverse())
{
Expand All @@ -104,7 +105,7 @@
DestroyPage(page);
}

public static T GetImplementerFromViewOrViewModel<T>(object view)

Check warning on line 108 in src/Maui/Prism.Maui/Common/MvvmHelpers.cs

View workflow job for this annotation

GitHub Actions / build-prism-maui / Build Prism.Maui

Missing XML comment for publicly visible type or member 'MvvmHelpers.GetImplementerFromViewOrViewModel<T>(object)'
where T : class
{
if (view is T viewAsT)
Expand Down Expand Up @@ -272,27 +273,44 @@
return EvaluateCurrentPage(page);
};

private static Page EvaluateCurrentPage(Page target)
private static Page GetTarget(Page target)
{
Page child = null;
return target switch
{
FlyoutPage flyout => GetTarget(flyout.Detail),
TabbedPage tabbed => GetTarget(tabbed.CurrentPage),
NavigationPage navigation => GetTarget(navigation.CurrentPage),
ContentPage page => page,
_ => throw new NotSupportedException($"The page type '{target.GetType().FullName}' is not supported.")
};
}

if (target is FlyoutPage flyout)
child = flyout.Detail;
else if (target is TabbedPage tabbed)
child = tabbed.CurrentPage;
else if (target is NavigationPage np)
child = np.Navigation.NavigationStack.Last();
private static Page EvaluateCurrentPage(Page target)
{
Page child = GetTarget(target);

if (child != null)
if (child is not null)
target = GetOnNavigatedToTargetFromChild(child);

if (target is Page page)
{
if (target is IDialogContainer)
{
if (page.Parent is Page parentPage)
{
return GetTarget(parentPage);
}

throw new InvalidOperationException("Unable to determine the current page.");
}

return page.Parent switch
{
TabbedPage tab when tab.CurrentPage != target => EvaluateCurrentPage(tab.CurrentPage),
NavigationPage nav when nav.CurrentPage != target => EvaluateCurrentPage(nav.CurrentPage),
_ => target
};
}

return null;
}
Expand Down
191 changes: 4 additions & 187 deletions src/Maui/Prism.Maui/Dialogs/DialogService.cs
Original file line number Diff line number Diff line change
@@ -1,209 +1,26 @@
using Prism.Commands;
using Prism.Common;
using Prism.Dialogs.Xaml;
using Prism.Mvvm;
using Prism.Navigation;

#nullable enable
namespace Prism.Dialogs;

/// <summary>
/// Provides the ability to display dialogs from ViewModels.
/// Provides a default scoped implementation of the <see cref="IDialogService"/>.
/// </summary>
public sealed class DialogService : IDialogService
public sealed class DialogService : DialogServiceBase
{
private readonly IContainerProvider _container;
private readonly IPageAccessor _pageAccessor;

/// <summary>
/// Creates a new instance of the <see cref="DialogService"/> for Maui Applications
/// </summary>
/// <param name="container">The <see cref="IContainerProvider"/> that will be used to help resolve the Dialog Views.</param>
/// <param name="pageAccessor">The <see cref="IPageAccessor"/> used to determine where in the Navigation Stack we need to process the Dialog.</param>
/// <exception cref="ArgumentNullException">Throws when any constructor arguments are null.</exception>
public DialogService(IContainerProvider container, IPageAccessor pageAccessor)
public DialogService(IPageAccessor pageAccessor)
{
ArgumentNullException.ThrowIfNull(container);
ArgumentNullException.ThrowIfNull(pageAccessor);
_container = container;
_pageAccessor = pageAccessor;
}

/// <inheritdoc/>
public void ShowDialog(string name, IDialogParameters parameters, DialogCallback callback)
{
IDialogContainer? dialogModal = null;
try
{
parameters = UriParsingHelper.GetSegmentParameters(name, parameters ?? new DialogParameters());

// This needs to be resolved when called as a Module could load any time
// and register new dialogs
var registry = _container.Resolve<IDialogViewRegistry>();
var view = registry.CreateView(_container, UriParsingHelper.GetSegmentName(name)) as View
?? throw new ViewCreationException(name, ViewType.Dialog);

var currentPage = _pageAccessor.Page;
dialogModal = _container.Resolve<IDialogContainer>();
IDialogContainer.DialogStack.Add(dialogModal);
var dialogAware = GetDialogController(view);

async Task DialogAware_RequestClose(IDialogResult outResult)
{
bool didCloseDialog = true;
try
{
var result = await CloseDialogAsync(outResult ?? new DialogResult(), currentPage, dialogModal);
if (result.Exception is DialogException de && de.Message == DialogException.CanCloseIsFalse)
{
didCloseDialog = false;
return;
}

await callback.Invoke(result);
GC.Collect();
}
catch (DialogException dex)
{
if (dex.Message == DialogException.CanCloseIsFalse)
{
didCloseDialog = false;
return;
}

var result = new DialogResult
{
Exception = dex,
Parameters = parameters,
Result = ButtonResult.None
};

if (dex.Message != DialogException.CanCloseIsFalse)
{
await DialogService.InvokeError(callback, dex, parameters);
}
}
catch (Exception ex)
{
await DialogService.InvokeError(callback, ex, parameters);
}
finally
{
if (didCloseDialog && dialogModal is not null)
{
IDialogContainer.DialogStack.Remove(dialogModal);
}
}
}

DialogUtilities.InitializeListener(dialogAware, DialogAware_RequestClose);

dialogAware.OnDialogOpened(parameters);

if (!parameters.TryGetValue<bool>(KnownDialogParameters.CloseOnBackgroundTapped, out var closeOnBackgroundTapped))
{
var dialogLayoutCloseOnBackgroundTapped = DialogLayout.GetCloseOnBackgroundTapped(view);
if (dialogLayoutCloseOnBackgroundTapped.HasValue)
{
closeOnBackgroundTapped = dialogLayoutCloseOnBackgroundTapped.Value;
}
}

var dismissCommand = new DelegateCommand(() => dialogAware.RequestClose.Invoke(), dialogAware.CanCloseDialog);

PageNavigationService.NavigationSource = PageNavigationSource.DialogService;
dialogModal.ConfigureLayout(_pageAccessor.Page, view, closeOnBackgroundTapped, dismissCommand, parameters);
PageNavigationService.NavigationSource = PageNavigationSource.Device;

MvvmHelpers.InvokeViewAndViewModelAction<IActiveAware>(currentPage, aa => aa.IsActive = false);
MvvmHelpers.InvokeViewAndViewModelAction<IActiveAware>(view, aa => aa.IsActive = true);
}
catch (Exception ex)
{
callback.Invoke(ex);
}
}

private static async Task InvokeError(DialogCallback callback, Exception exception, IDialogParameters parameters)
{
var result = new DialogResult
{
Parameters = parameters,
Exception = exception,
Result = ButtonResult.None
};
await callback.Invoke(result);
}

private static async Task<IDialogResult> CloseDialogAsync(IDialogResult result, Page currentPage, IDialogContainer dialogModal)
{
try
{
PageNavigationService.NavigationSource = PageNavigationSource.DialogService;

result ??= new DialogResult();
if (result.Parameters is null)
{
result = new DialogResult
{
Exception = result.Exception,
Parameters = new DialogParameters(),
Result = result.Result
};
}

var view = dialogModal.DialogView;
var dialogAware = GetDialogController(view);

if (!dialogAware.CanCloseDialog())
{
throw new DialogException(DialogException.CanCloseIsFalse);
}

PageNavigationService.NavigationSource = PageNavigationSource.DialogService;
await dialogModal.DoPop(currentPage);
PageNavigationService.NavigationSource = PageNavigationSource.Device;

MvvmHelpers.InvokeViewAndViewModelAction<IActiveAware>(view, aa => aa.IsActive = false);
MvvmHelpers.InvokeViewAndViewModelAction<IActiveAware>(currentPage, aa => aa.IsActive = true);
dialogAware.OnDialogClosed();

return result;
}
catch (DialogException)
{
throw;
}
catch (Exception ex)
{
return new DialogResult
{
Exception = ex,
Parameters = result.Parameters,
Result = result.Result
};
}
finally
{
PageNavigationService.NavigationSource = PageNavigationSource.Device;
}
}

private static IDialogAware GetDialogController(View view)
{
if (view is IDialogAware viewAsDialogAware)
{
return viewAsDialogAware;
}
else if (view.BindingContext is null)
{
throw new DialogException(DialogException.NoViewModel);
}
else if (view.BindingContext is IDialogAware dialogAware)
{
return dialogAware;
}

throw new DialogException(DialogException.ImplementIDialogAware);
}
protected override Page? GetCurrentPage() => _pageAccessor.Page;
}
Loading
Loading