|
#nullable disable
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Maui.Controls.Internals;
namespace Microsoft.Maui.Controls
{
// This contains the messaging required to communicate with legacy renderers
/// <include file="../../../docs/Microsoft.Maui.Controls/NavigationPage.xml" path="Type[@FullName='Microsoft.Maui.Controls.NavigationPage']/Docs/*" />
public partial class NavigationPage : INavigationPageController
{
async Task<Page> PopAsyncInner(
bool animated,
bool fast)
{
if (NavigationPageController.StackDepth == 1)
{
return null;
}
var page = (Page)InternalChildren.Last();
var previousPage = CurrentPage;
SendNavigating();
var removedPage = await RemoveAsyncInner(page, animated, fast);
SendNavigated(previousPage, NavigationType.Pop);
return removedPage;
}
async Task<Page> RemoveAsyncInner(
Page page,
bool animated,
bool fast)
{
if (NavigationPageController.StackDepth == 1)
{
return null;
}
FireDisappearing(page);
if (InternalChildren.Last() == page)
FireAppearing((Page)InternalChildren[NavigationPageController.StackDepth - 2]);
var args = new NavigationRequestedEventArgs(page, animated);
var removed = true;
EventHandler<NavigationRequestedEventArgs> requestPop = _popRequested;
if (requestPop != null)
{
requestPop(this, args);
if (args.Task != null && !fast)
removed = await args.Task;
}
if (!removed && !fast)
return CurrentPage;
RemoveFromInnerChildren(page);
CurrentPage = (Page)InternalChildren.Last();
if (Popped != null)
Popped(this, args);
return page;
}
Task<Page> INavigationPageController.PopAsyncInner(bool animated, bool fast)
{
return PopAsyncInner(animated, fast);
}
Task<Page> INavigationPageController.RemoveAsyncInner(Page page, bool animated, bool fast)
{
return RemoveAsyncInner(page, animated, fast);
}
EventHandler<NavigationRequestedEventArgs> _popRequested;
event EventHandler<NavigationRequestedEventArgs> INavigationPageController.PopRequested
{
add => _popRequested += value;
remove => _popRequested -= value;
}
EventHandler<NavigationRequestedEventArgs> _popToRootRequested;
event EventHandler<NavigationRequestedEventArgs> INavigationPageController.PopToRootRequested
{
add => _popToRootRequested += value;
remove => _popToRootRequested -= value;
}
EventHandler<NavigationRequestedEventArgs> _pushRequested;
event EventHandler<NavigationRequestedEventArgs> INavigationPageController.PushRequested
{
add => _pushRequested += value;
remove => _pushRequested -= value;
}
EventHandler<NavigationRequestedEventArgs> _removePageRequested;
event EventHandler<NavigationRequestedEventArgs> INavigationPageController.RemovePageRequested
{
add => _removePageRequested += value;
remove => _removePageRequested -= value;
}
EventHandler<NavigationRequestedEventArgs> _insertPageBeforeRequested;
event EventHandler<NavigationRequestedEventArgs> INavigationPageController.InsertPageBeforeRequested
{
add => _insertPageBeforeRequested += value;
remove => _insertPageBeforeRequested -= value;
}
void InsertPageBefore(Page page, Page before)
{
if (page == null)
throw new ArgumentNullException($"{nameof(page)} cannot be null.");
if (before == null)
throw new ArgumentNullException($"{nameof(before)} cannot be null.");
if (!InternalChildren.Contains(before))
throw new ArgumentException($"{nameof(before)} must be a child of the NavigationPage", nameof(before));
if (InternalChildren.Contains(page))
throw new ArgumentException("Cannot insert page which is already in the navigation stack");
_insertPageBeforeRequested?.Invoke(this, new NavigationRequestedEventArgs(page, before, false));
int index = InternalChildren.IndexOf(before);
InternalChildren.Insert(index, page);
if (index == 0)
RootPage = page;
// Shouldn't be required?
if (Width > 0 && Height > 0)
ForceLayout();
}
async Task PopToRootAsyncInner(bool animated)
{
if (NavigationPageController.StackDepth == 1)
return;
var previousPage = CurrentPage;
SendNavigating();
FireDisappearing(CurrentPage);
FireAppearing((Page)InternalChildren[0]);
Element[] childrenToRemove = InternalChildren.Skip(1).ToArray();
foreach (Element child in childrenToRemove)
{
RemoveFromInnerChildren(child);
}
CurrentPage = RootPage;
var args = new NavigationRequestedEventArgs(RootPage, animated);
EventHandler<NavigationRequestedEventArgs> requestPopToRoot = _popToRootRequested;
if (requestPopToRoot != null)
{
requestPopToRoot(this, args);
if (args.Task != null)
await args.Task;
}
PoppedToRoot?.Invoke(this, new PoppedToRootEventArgs(RootPage, childrenToRemove.OfType<Page>().ToList()));
SendNavigated(previousPage, NavigationType.PopToRoot);
}
async Task PushAsyncInner(Page page, bool animated)
{
if (InternalChildren.Contains(page))
return;
var previousPage = CurrentPage;
SendNavigating();
FireDisappearing(CurrentPage);
FireAppearing(page);
PushPage(page);
var args = new NavigationRequestedEventArgs(page, animated);
EventHandler<NavigationRequestedEventArgs> requestPush = _pushRequested;
if (requestPush != null)
{
requestPush(this, args);
if (args.Task != null)
await args.Task;
}
SendNavigated(previousPage, NavigationType.Push);
Pushed?.Invoke(this, args);
}
#if IOS
// Because iOS currently doesn't use our `IStackNavigationView` structures
// there are scenarios where the legacy handler needs to alert the xplat
// code of when a navigation has occurred.
// For example, initial load and when the user taps the back button
internal void SendNavigatedFromHandler(Page previousPage, NavigationType navigationType)
{
if (CurrentPage.HasNavigatedTo)
return;
SendNavigated(previousPage, navigationType);
}
#endif
void PushPage(Page page)
{
InternalChildren.Add(page);
if (InternalChildren.Count == 1)
RootPage = page;
CurrentPage = page;
}
void RemovePage(Page page)
{
if (page == null)
throw new ArgumentNullException($"{nameof(page)} cannot be null.");
if (page == CurrentPage && CurrentPage == RootPage)
throw new InvalidOperationException("Cannot remove root page when it is also the currently displayed page.");
if (page == CurrentPage)
{
Application.Current?.FindMauiContext()?.CreateLogger<NavigationPage>()?.LogWarning("RemovePage called for CurrentPage object. This can result in undesired behavior, consider calling PopAsync instead.");
PopAsync();
return;
}
if (!InternalChildren.Contains(page))
throw new ArgumentException("Page to remove must be contained on this Navigation Page");
_removePageRequested?.Invoke(this, new NavigationRequestedEventArgs(page, true));
RemoveFromInnerChildren(page);
if (RootPage == page)
RootPage = (Page)InternalChildren.First();
}
class NavigationImpl : NavigationProxy
{
readonly Lazy<ReadOnlyCastingList<Page, Element>> _castingList;
public NavigationImpl(NavigationPage owner)
{
Owner = owner;
_castingList = new Lazy<ReadOnlyCastingList<Page, Element>>(() => new ReadOnlyCastingList<Page, Element>(Owner.InternalChildren));
}
NavigationPage Owner { get; }
protected override IReadOnlyList<Page> GetNavigationStack()
{
return _castingList.Value;
}
protected override void OnInsertPageBefore(Page page, Page before)
{
Owner.InsertPageBefore(page, before);
}
protected override Task<Page> OnPopAsync(bool animated)
{
return Owner.PopAsync(animated);
}
protected override Task OnPopToRootAsync(bool animated)
{
return Owner.PopToRootAsync(animated);
}
protected override Task OnPushAsync(Page root, bool animated)
{
return Owner.PushAsync(root, animated);
}
protected override void OnRemovePage(Page page)
{
Owner.RemovePage(page);
}
}
}
}
|