dansiegel / Prism.Plugin.Popups

This provides extensibility for Prism.Forms INavigationService to handle Popup Views
http://popups.prismplugins.com
MIT License
190 stars 39 forks source link

MAUI support #289

Open BerndStoltefuss opened 1 year ago

BerndStoltefuss commented 1 year ago

Hi,

will this plugin be updated to support the new MAUI implementation of Rg.Plugins.Popups named Mopups (https://github.com/LuckyDucko/Mopups) and the MAUI implementation of Prism itself?

Or do you recommend a different solution for popups in MAUI (f.e. from CommunityToolkit)?

Thanks,

Bernd

dansiegel commented 1 year ago

@BerndStoltefuss actually I was not aware of a Maui port for the Popup Plugin... it is something I will take a look at after the next Prism.Maui release which adds .NET 7 support. Also to be clear it will only support Popups for Dialogs. The Navigation Service in Prism.Plugin.Popups is legacy at this point and you should only be using it for Dialogs anyway

BerndStoltefuss commented 1 year ago

@dansiegel thanks for the quick response. But i am not sure i understand your "will only support Popups for Dialogs". We currently have a bunch of pages (f.e. DetailViews or views to enter or select something) which are implemented as PopupPage and are navigated to by using NavigateAsync(page, params, modal=true). So the "main/master" view is still visible behind it. That's the same way as in your current sample/MainPageViewModel and the only way i was aware of using the current version of your plugin. So i am not sure what you mean by "only support support popups for dialogs"?

dansiegel commented 1 year ago

When the popup plugin was first introduced that was what we needed to do. Popup Pages aren't really pages at all they are dialogs. While I have kept the Navigation Service around for legacy support you should be migrating to use Popup Pages as Dialogs.

BerndStoltefuss commented 1 year ago

Hmm. So to replicate the old navigation behaviour and regarding to your answers here (https://stackoverflow.com/questions/63801160/difference-between-prism-dialogs-vs-popup) i should be able to drop Rg.Plugin.Popups at all and just use a modal page with transparent background instead? If this is possible it would be my prefered way as i could get rid of 2 dependencies when porting to MAUI (or even before when using Prism 8 according to your post)

Rg.Plugin.Popups(Mopups for MAUI) would only be needed when a) we need animations or b) for some strange reason need to popup something outside the navigation stack. Correct?

The other way would be to use "popup pages as dialogs" as suggested, resulting in a view which is not part of the navigation stack (this could be an advantage or disadvantage depending on the situation) and is not stackable(or are dialogs stackable as well? We have some detail views which might display dialogs (warnings etc). itself)). Correct?

Sorry for the questions but we have until now only used prisms dialog service to display simple messages or yes/no questions and not for something like detail views.

JulianPasque commented 1 year ago

Any update on this topic @dansiegel?

BerndStoltefuss commented 1 year ago

FYI: for the maui port of our product i replaced the RG.Popup/Mopup stuff with modal pages (using transparent background). No problems so far and i got rid of two dependencies on one/two men projects ,-)

Still looking forward for dialogs in Prism.Maui, as they could be a good replacement at some places where true navigation is not necessary.

maxchu92 commented 1 year ago

Guys, after researching the source code of Prism NavigationService, and fiddling with hours, I came out with a service to navigate mopup pages similarly to Prism on MAUI. My codes aren't perfect, but it works well for me. It would be very helpful if someone can fine tune the code for me.

  1. Setup Mopups accordingly, including .
  2. Make sure you add the service onto PrismStartup.cs
private static void ConfigureServices(IServiceCollection services) {
    ...
    services.AddSingleton(MopupService.Instance);
    ...
}
  1. Add IPopupPageNavigation.cs
    
    namespace MyApp.Interfaces;

public interface IPopupPageNavigation { Task NavigateAsync(string name); Task NavigateAsync(string name, INavigationParameters parameters); Task GoBackAsync(); Task GoBackAsync(INavigationParameters parameters); Task GoBackToRootAsync(); Task GoBackToRootAsync(INavigationParameters parameters); }


4. Add `PopupPageavigation.cs`

using DryIoc; using Meteor.LalaPartsSellerApp.Interfaces; using Mopups.Interfaces; using Mopups.Pages; using Prism.Common;

namespace MyApp.Services; public class PopupPageNavigation : IPopupPageNavigation { private readonly IPopupNavigation _popupNavigation; private readonly IPageAccessor _pageAccessor; private readonly IContainerExtension _containerExtension;

public IContainerProvider _container => _containerExtension;
public IViewRegistry Registry => _container.Resolve<INavigationRegistry>();

private Window _window;
protected Window Window {
    get {
        if (_window is null && _pageAccessor.Page is not null) {
            _window = _pageAccessor.Page.GetParentWindow();
        }

        return _window;
    }
}

public PopupPageNavigation(
    IPopupNavigation popupNavigation,
    IPageAccessor pageAccessor) {
    _popupNavigation = popupNavigation;
    _pageAccessor = pageAccessor;
    _containerExtension = ContainerLocator.Current;
}

public Task NavigateAsync(string name) => NavigateAsync(name, new NavigationParameters());
public async Task NavigateAsync(string name, INavigationParameters parameters) {
    var fromPage = GetCurrentPage();
    var uri = UriParsingHelper.Parse(name);
    var segments = UriParsingHelper.GetUriSegments(uri);

    do {
        var nextSegment = segments.Dequeue();
        var segmentParameters = UriParsingHelper.GetSegmentParameters(nextSegment, parameters);
        var toPage = CreatePageFromSegment(nextSegment);

        var canNavigate = await MvvmHelpers.CanNavigateAsync(fromPage, segmentParameters);
        if (!canNavigate) {
            throw new NavigationException(NavigationException.IConfirmNavigationReturnedFalse, toPage);
        }

        MvvmHelpers.OnNavigatedFrom(fromPage, segmentParameters);

        if (toPage is PopupPage popupPage) {
            await MvvmHelpers.OnInitializedAsync(popupPage, segmentParameters);
            await _popupNavigation.PushAsync(popupPage);
            MvvmHelpers.OnNavigatedTo(popupPage, segmentParameters);
        }
    } while (segments.Count > 0);
}

public Task GoBackAsync() => GoBackAsync(new NavigationParameters());

public async Task GoBackAsync(INavigationParameters parameters) {
    var page = GetCurrentPage();
    var canNavigate = await MvvmHelpers.CanNavigateAsync(page, parameters);
    if (!canNavigate) {
        throw new NavigationException(NavigationException.IConfirmNavigationReturnedFalse, page);
    }

    var previousPage = MvvmHelpers.GetOnNavigatedToTarget(page, Window?.Page, true);

    await _popupNavigation.PopAsync(IsAnimated(parameters));
    MvvmHelpers.OnNavigatedFrom(page, parameters);
    MvvmHelpers.OnNavigatedTo(previousPage, parameters);
    MvvmHelpers.DestroyPage(page);
}

public Task GoBackToRootAsync() => GoBackToRootAsync(new NavigationParameters());
public async Task GoBackToRootAsync(INavigationParameters parameters) {
    var page = GetCurrentPage();
    var animated = IsAnimated(parameters);
    var canNavigate = await MvvmHelpers.CanNavigateAsync(page, parameters);
    if (!canNavigate) {
        throw new NavigationException(NavigationException.IConfirmNavigationReturnedFalse, page);
    }

    var pagesToDestroy = page.Navigation.NavigationStack.ToList(); // get all pages to destroy
    pagesToDestroy.Reverse(); // destroy them in reverse order
    var root = pagesToDestroy.Last();
    pagesToDestroy.Remove(root); //don't destroy the root page

    await _popupNavigation.PopAllAsync(animated);
    await page.Navigation.PopToRootAsync(animated);

    foreach (var destroyPage in pagesToDestroy) {
        MvvmHelpers.OnNavigatedFrom(destroyPage, parameters);
        MvvmHelpers.DestroyPage(destroyPage);
    }

    MvvmHelpers.OnNavigatedTo(root, parameters);
}

protected virtual Page GetCurrentPage() {
    PopupPage sourcePage = _popupNavigation.PopupStack.LastOrDefault();
    if (sourcePage != null) return sourcePage;

    return _pageAccessor.Page is not null ? _pageAccessor.Page : GetPageFromWindow();
}

private Page GetPageFromWindow() {
    try {
        return Window?.Page;
    }

if DEBUG

    catch (Exception ex) {
        Console.Error.WriteLine(ex);
        return null;
    }

else

    catch
    {
        return null;
    }

endif

}

protected virtual Page CreatePageFromSegment(string segment) {
    string segmentName = UriParsingHelper.GetSegmentName(segment);
    var page = CreatePage(segmentName);
    if (page is null) {
        var innerException = new NullReferenceException(string.Format("{0} could not be created. Please make sure you have registered {0} for navigation.", segmentName));
        throw new NavigationException(NavigationException.NoPageIsRegistered, segmentName, _pageAccessor.Page, innerException);
    }

    return page;
}

protected virtual Page CreatePage(string segmentName) {
    try {
        var scope = _container.CreateScope();
        var page = (Page)Registry.CreateView(scope, segmentName);

        if (page is null)
            throw new NullReferenceException($"The resolved type for {segmentName} was null. You may be attempting to navigate to a Non-Page type");

        return page;
    } catch (NavigationException) {
        throw;
    } catch (KeyNotFoundException knfe) {
        throw new NavigationException(NavigationException.NoPageIsRegistered, segmentName, knfe);
    } catch (ViewModelCreationException vmce) {
        throw new NavigationException(NavigationException.ErrorCreatingViewModel, segmentName, _pageAccessor.Page, vmce);
    }
    //catch(ViewCreationException viewCreationException)
    //{
    //    if(!string.IsNullOrEmpty(viewCreationException.InnerException?.Message) && viewCreationException.InnerException.Message.Contains("Maui"))
    //        throw new NavigationException(NavigationException.)
    //}
    catch (Exception ex) {
        var inner = ex.InnerException;
        while (inner is not null) {
            if (inner.Message.Contains("thread with a dispatcher"))
                throw new NavigationException(NavigationException.UnsupportedMauiCreation, segmentName, _pageAccessor.Page, ex);
            inner = inner.InnerException;
        }
        throw new NavigationException(NavigationException.ErrorCreatingPage, segmentName, ex);
    }
}

internal bool UseModalGoBack(Page currentPage, INavigationParameters parameters) {
    if (parameters.ContainsKey(KnownNavigationParameters.UseModalNavigation))
        return parameters.GetValue<bool>(KnownNavigationParameters.UseModalNavigation);
    else if (currentPage is NavigationPage navPage)
        return GoBackModal(navPage);
    else
        return true;
}

private bool GoBackModal(NavigationPage navPage) {
    var rootPage = GetPageFromWindow();
    if (navPage.CurrentPage != navPage.RootPage)
        return false;
    else if (navPage.CurrentPage == navPage.RootPage && navPage.Parent is Application && rootPage != navPage)
        return true;
    else if (navPage.Parent is TabbedPage tabbed && tabbed != rootPage)
        return true;

    return false;
}

private bool IsAnimated(INavigationParameters parameters) {
    return parameters.ContainsKey(KnownNavigationParameters.Animated) ? parameters.GetValue<bool>(KnownNavigationParameters.Animated) : true;
}

}


5. Register `IPopupPageNavigation` in `PrismStartup.cs`

private static void RegisterTypes(IContainerRegistry containerRegistry) { containerRegistry.RegisterScoped<IPopupPageNavigation, PopupPageNavigation>(); }


6. Just add `IPopupPageNavigation` in your ViewModel constructor to use it.

await _popupPageNavigation.NavigateAsync("ViewPopupPage", new NavigationParameters { { "param1" , "value1" } });

dansiegel commented 1 year ago

Popup Support for .NET MAUI will only be available to Commercial Plus license holders. No further releases will be made for Xamarin.Forms.