Skip to content

Commit

Permalink
Merge pull request #3094 from PrismLibrary/dev/ds/singleton-dialog
Browse files Browse the repository at this point in the history
[Enhancement] Make it easier to access IDialogService from Singletons
  • Loading branch information
dansiegel committed Feb 29, 2024
2 parents efbd9f7 + d5e9967 commit 20e9d79
Show file tree
Hide file tree
Showing 9 changed files with 346 additions and 208 deletions.
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 Down Expand Up @@ -272,27 +273,44 @@ public static int GetCurrentPageIndex(Page currentPage, System.Collections.Gener
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;
}

0 comments on commit 20e9d79

Please sign in to comment.