--- /dev/null
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using NUnit.Framework;
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class ShellLifeCycleTests : ShellTestBase
+ {
+ const string ContentRoute = "content";
+ const string SectionRoute = "section";
+ const string ItemRoute = "item";
+
+ [Test]
+ public void AppearingOnCreate()
+ {
+ Shell shell = new Shell();
+
+ FlyoutItem flyoutItem = new FlyoutItem();
+ Tab tab = new Tab();
+ ShellContent content = new ShellContent() { Content = new ContentPage() };
+
+ bool flyoutAppearing = false;
+ bool tabAppearing = false;
+ bool contentAppearing = false;
+
+ flyoutItem.Appearing += (_, __) => flyoutAppearing = true;
+ tab.Appearing += (_, __) => tabAppearing = true;
+ content.Appearing += (_, __) => contentAppearing = true;
+
+ shell.Items.Add(flyoutItem);
+ flyoutItem.Items.Add(tab);
+ tab.Items.Add(content);
+
+ Assert.True(flyoutAppearing, "Flyout appearing");
+ Assert.True(tabAppearing, "Tab Appearing");
+ Assert.True(contentAppearing, "Content Appearing");
+ }
+
+
+ [Test]
+ public void AppearingOnCreateFromTemplate()
+ {
+ Shell shell = new Shell();
+
+ FlyoutItem flyoutItem = new FlyoutItem();
+ Tab tab = new Tab();
+ ContentPage page = new ContentPage();
+ ShellContent content = new ShellContent() { ContentTemplate = new DataTemplate(() => page) };
+
+ bool flyoutAppearing = false;
+ bool tabAppearing = false;
+ bool contentAppearing = false;
+ bool pageAppearing = false;
+
+ flyoutItem.Appearing += (_, __) => flyoutAppearing = true;
+ tab.Appearing += (_, __) => tabAppearing = true;
+ content.Appearing += (_, __) => contentAppearing = true;
+ page.Appearing += (_, __) => pageAppearing = true;
+
+ shell.Items.Add(flyoutItem);
+ flyoutItem.Items.Add(tab);
+ tab.Items.Add(content);
+
+ Assert.True(flyoutAppearing, "Flyout appearing");
+ Assert.True(tabAppearing, "Tab Appearing");
+
+ // Because the is a page template the content appearing events won't fire until the page is created
+ Assert.IsFalse(contentAppearing, "Content Appearing");
+ Assert.IsFalse(pageAppearing, "Page Appearing");
+
+ var createdContent = (content as IShellContentController).GetOrCreateContent();
+
+ Assert.AreEqual(createdContent, page);
+ Assert.IsTrue(contentAppearing, "Content Appearing");
+ Assert.IsTrue(pageAppearing, "Page Appearing");
+ }
+
+
+
+ [Test]
+ public async Task NavigatedFiresAfterContentIsCreatedWhenUsingTemplate()
+ {
+ Shell shell = new Shell();
+
+ FlyoutItem flyoutItem = new FlyoutItem();
+ Tab tab = new Tab();
+ ContentPage page = null;
+ ShellContent content = new ShellContent()
+ {
+ Route = "destination",
+ ContentTemplate = new DataTemplate(() =>
+ {
+ page = new ContentPage();
+ return page;
+ })
+ };
+
+ flyoutItem.Items.Add(tab);
+ tab.Items.Add(content);
+
+ shell.Items.Add(CreateShellItem());
+ shell.Items.Add(flyoutItem);
+
+ Assert.AreNotEqual(shell.CurrentItem.CurrentItem.CurrentItem, content);
+ Assert.IsNull(page);
+
+ bool navigated = false;
+ shell.Navigated += (_, __) =>
+ {
+ Assert.IsNotNull(page);
+ Assert.IsNotNull((content as IShellContentController).Page);
+ navigated = true;
+ };
+
+ await shell.GoToAsync("///destination");
+
+ // content hasn't been created yet
+ Assert.IsFalse(navigated);
+
+ var createPage = (content as IShellContentController).GetOrCreateContent();
+ Assert.AreEqual(createPage, page);
+ Assert.IsTrue(navigated);
+ }
+
+ [Test]
+ public void AppearingOnShellContentChanged()
+ {
+ Shell shell = new Shell();
+ var item = CreateShellItem(shellContentRoute: ContentRoute, shellSectionRoute: SectionRoute, shellItemRoute: ItemRoute);
+ var section = item.SearchForRoute<ShellSection>(SectionRoute);
+
+ var content = new ShellContent();
+ section.Items.Insert(0, content);
+ section.CurrentItem = content;
+ shell.Items.Add(item);
+ ShellLifeCycleState state = new ShellLifeCycleState(shell);
+
+ state.AllFalse();
+
+ Assert.AreEqual(content, section.CurrentItem);
+
+ section.CurrentItem = shell.SearchForRoute<ShellContent>(ContentRoute);
+
+ Assert.IsTrue(state.ContentAppearing);
+
+ section.CurrentItem = content;
+
+ Assert.IsFalse(state.ContentAppearing);
+ }
+
+
+ [Test]
+ public void AppearingOnShellSectionChanged()
+ {
+ Shell shell = new Shell();
+ var item = CreateShellItem(shellContentRoute: ContentRoute, shellSectionRoute: SectionRoute, shellItemRoute: ItemRoute);
+ var section = item.SearchForRoute<ShellSection>(SectionRoute);
+ var newSection = CreateShellSection();
+ item.Items.Insert(0, newSection);
+ item.CurrentItem = newSection;
+ shell.Items.Add(item);
+ ShellLifeCycleState state = new ShellLifeCycleState(shell);
+ state.AllFalse();
+
+
+ Assert.AreEqual(newSection, item.CurrentItem);
+ Assert.AreNotEqual(section, item.CurrentItem);
+
+ item.CurrentItem = section;
+
+ Assert.IsTrue(state.SectionAppearing);
+ Assert.IsTrue(state.ContentAppearing);
+
+ item.CurrentItem = newSection;
+
+ Assert.IsFalse(state.SectionAppearing);
+ Assert.IsFalse(state.ContentAppearing);
+ }
+
+ [Test]
+ public void AppearingOnShellItemChanged()
+ {
+ Shell shell = new Shell();
+ var item = CreateShellItem(shellContentRoute: ContentRoute, shellSectionRoute: SectionRoute, shellItemRoute: ItemRoute);
+ var item2 = CreateShellItem();
+ shell.Items.Add(item2);
+ shell.Items.Add(item);
+ ShellLifeCycleState state = new ShellLifeCycleState(shell);
+ state.AllFalse();
+
+ Assert.AreEqual(shell.CurrentItem, item2);
+ Assert.AreNotEqual(shell.CurrentItem, item);
+
+ shell.CurrentItem = item;
+
+ Assert.IsTrue(state.ItemAppearing);
+ Assert.IsTrue(state.SectionAppearing);
+ Assert.IsTrue(state.ContentAppearing);
+
+ shell.CurrentItem = item2;
+
+ Assert.IsFalse(state.ItemAppearing);
+ Assert.IsFalse(state.SectionAppearing);
+ Assert.IsFalse(state.ContentAppearing);
+ }
+
+
+ [Test]
+ public async Task ShellPartWithModalPush()
+ {
+ Shell shell = new Shell();
+ var item = CreateShellItem(shellContentRoute: ContentRoute, shellSectionRoute: SectionRoute, shellItemRoute: ItemRoute);
+ ShellLifeCycleState lifeCycleState = new ShellLifeCycleState(item);
+ shell.Items.Add(item);
+
+ lifeCycleState.AllTrue();
+
+ ContentPage page = new ContentPage();
+ await shell.Navigation.PushModalAsync(page);
+ lifeCycleState.AllFalse();
+ await shell.Navigation.PopModalAsync();
+ lifeCycleState.AllTrue();
+ }
+
+
+ [Test]
+ public async Task ShellPartWithPagePush()
+ {
+ Shell shell = new Shell();
+ var item = CreateShellItem(shellContentRoute: ContentRoute, shellSectionRoute: SectionRoute, shellItemRoute: ItemRoute);
+ ShellLifeCycleState lifeCycleState = new ShellLifeCycleState(item);
+ shell.Items.Add(item);
+
+ lifeCycleState.AllTrue();
+
+ await shell.Navigation.PushAsync(new ContentPage());
+ //if you're just pushing a page then the section and item are still visible but the content is not
+ Assert.IsFalse(lifeCycleState.PageAppearing);
+ Assert.IsFalse(lifeCycleState.ContentAppearing);
+ Assert.IsTrue(lifeCycleState.SectionAppearing);
+ Assert.IsTrue(lifeCycleState.ItemAppearing);
+
+ await shell.Navigation.PushAsync(new ContentPage());
+ Assert.IsFalse(lifeCycleState.PageAppearing);
+ Assert.IsFalse(lifeCycleState.ContentAppearing);
+ Assert.IsTrue(lifeCycleState.SectionAppearing);
+ Assert.IsTrue(lifeCycleState.ItemAppearing);
+
+ await shell.Navigation.PopAsync();
+ Assert.IsFalse(lifeCycleState.PageAppearing);
+ Assert.IsFalse(lifeCycleState.ContentAppearing);
+ Assert.IsTrue(lifeCycleState.SectionAppearing);
+ Assert.IsTrue(lifeCycleState.ItemAppearing);
+ await shell.Navigation.PopAsync();
+ lifeCycleState.AllTrue();
+ }
+
+ [Test]
+ public async Task ShellPartWithPopToRoot()
+ {
+ Shell shell = new Shell();
+ var item = CreateShellItem(shellContentRoute: ContentRoute, shellSectionRoute: SectionRoute, shellItemRoute: ItemRoute);
+ ShellLifeCycleState lifeCycleState = new ShellLifeCycleState(item);
+ shell.Items.Add(item);
+
+ lifeCycleState.AllTrue();
+
+ await shell.Navigation.PushAsync(new ContentPage());
+ await shell.Navigation.PushAsync(new ContentPage());
+ await shell.Navigation.PushAsync(new ContentPage());
+ await shell.Navigation.PushAsync(new ContentPage());
+ Assert.IsFalse(lifeCycleState.PageAppearing);
+ Assert.IsFalse(lifeCycleState.ContentAppearing);
+ Assert.IsTrue(lifeCycleState.SectionAppearing);
+ Assert.IsTrue(lifeCycleState.ItemAppearing);
+
+ await shell.Navigation.PopToRootAsync();
+ lifeCycleState.AllTrue();
+ }
+
+
+ [Test]
+ public async Task PagePushModal()
+ {
+ Shell shell = new Shell();
+ var item = CreateShellItem(shellContentRoute: ContentRoute, shellSectionRoute: SectionRoute, shellItemRoute: ItemRoute);
+ ShellLifeCycleState lifeCycleState = new ShellLifeCycleState(item);
+ shell.Items.Add(item);
+
+ ContentPage page = new ContentPage();
+
+ bool appearing = false;
+
+ page.Appearing += (_, __) => appearing = true;
+ page.Disappearing += (_, __) => appearing = false;
+
+ await shell.Navigation.PushModalAsync(page);
+ Assert.IsTrue(appearing);
+ lifeCycleState.AllFalse();
+
+ await shell.Navigation.PopModalAsync();
+ Assert.IsFalse(appearing);
+ lifeCycleState.AllTrue();
+ }
+
+ [Test]
+ public async Task PagePush()
+ {
+ Shell shell = new Shell();
+ var item = CreateShellItem(shellContentRoute: ContentRoute, shellSectionRoute: SectionRoute, shellItemRoute: ItemRoute);
+ shell.Items.Add(item);
+
+ ContentPage page = new ContentPage();
+ bool appearing = false;
+
+ page.Appearing += (_, __) => appearing = true;
+ page.Disappearing += (_, __) => appearing = false;
+
+ await shell.Navigation.PushAsync(page);
+ Assert.IsTrue(appearing);
+ await shell.Navigation.PopAsync();
+ Assert.IsFalse(appearing);
+ }
+
+
+ [Test]
+ public async Task OnNavigatedOnlyFiresOnce()
+ {
+ int navigated = 0;
+ Shell shell = new Shell();
+ shell.Navigated += (_, __) =>
+ {
+ navigated++;
+ };
+
+ var item = CreateShellItem(shellContentRoute: ContentRoute, shellSectionRoute: SectionRoute, shellItemRoute: ItemRoute);
+ shell.Items.Add(item);
+ Assert.AreEqual(1, navigated);
+ }
+
+
+
+
+ class ShellLifeCycleState
+ {
+ public bool ItemAppearing;
+ public bool SectionAppearing;
+ public bool ContentAppearing;
+ public bool PageAppearing;
+
+ public ShellLifeCycleState(Shell shell)
+ {
+ var shellContent = shell.SearchForRoute<ShellContent>(ContentRoute);
+ var contentPage = (shellContent as IShellContentController).GetOrCreateContent();
+
+ shell.SearchForRoute(ItemRoute).Appearing += (_, __) => ItemAppearing = true;
+ shell.SearchForRoute(SectionRoute).Appearing += (_, __) => SectionAppearing = true;
+ shell.SearchForRoute(ContentRoute).Appearing += (_, __) => ContentAppearing = true;
+ shellContent.Appearing += (_, __) => ContentAppearing = true;
+ contentPage.Appearing += (_, __) => PageAppearing = true;
+
+ shell.SearchForRoute(ItemRoute).Disappearing += (_, __) => ItemAppearing = false;
+ shell.SearchForRoute(SectionRoute).Disappearing += (_, __) => SectionAppearing = false;
+ shellContent.Disappearing += (_, __) => ContentAppearing = false;
+ contentPage.Disappearing += (_, __) => PageAppearing = false;
+ }
+ public ShellLifeCycleState(BaseShellItem baseShellItem)
+ {
+ var shellContent = baseShellItem.SearchForRoute<ShellContent>(ContentRoute);
+ var contentPage = (shellContent as IShellContentController).GetOrCreateContent();
+
+ baseShellItem.SearchForRoute(ItemRoute).Appearing += (_, __) => ItemAppearing = true;
+ baseShellItem.SearchForRoute(SectionRoute).Appearing += (_, __) => SectionAppearing = true;
+ shellContent.Appearing += (_, __) => ContentAppearing = true;
+ contentPage.Appearing += (_, __) => PageAppearing = true;
+
+ baseShellItem.SearchForRoute(ItemRoute).Disappearing += (_, __) => ItemAppearing = false;
+ baseShellItem.SearchForRoute(SectionRoute).Disappearing += (_, __) => SectionAppearing = false;
+ shellContent.Disappearing += (_, __) => ContentAppearing = false;
+ contentPage.Disappearing += (_, __) => PageAppearing = false;
+ }
+
+ public void AllFalse()
+ {
+ Assert.IsFalse(ItemAppearing);
+ Assert.IsFalse(SectionAppearing);
+ Assert.IsFalse(ContentAppearing);
+ Assert.IsFalse(PageAppearing);
+ }
+ public void AllTrue()
+ {
+ Assert.IsTrue(ItemAppearing);
+ Assert.IsTrue(SectionAppearing);
+ Assert.IsTrue(ContentAppearing);
+ Assert.IsTrue(PageAppearing);
+ }
+ }
+ }
+}
Assert.AreEqual(0, shell.Items[1].Items.Count);
Assert.AreEqual(3, shell.Items[0].Items.Count);
}
+
+
+ [Test]
+ public async Task NavigatedFiresAfterContentIsCreatedWhenUsingTemplate()
+ {
+
+ var shell = new Shell();
+ var item1 = CreateShellItem(asImplicit: true, shellContentRoute: "rootlevelcontent1");
+
+ shell.Items.Add(item1);
+ Routing.RegisterRoute("cat", typeof(ContentPage));
+ Routing.RegisterRoute("details", typeof(ContentPage));
+
+ await shell.GoToAsync("cat");
+ await shell.GoToAsync("details");
+
+ Assert.AreEqual("//rootlevelcontent1/cat/details", shell.CurrentState.Location.ToString());
+ await shell.GoToAsync("//rootlevelcontent1/details");
+ Assert.AreEqual("//rootlevelcontent1/details", shell.CurrentState.Location.ToString());
+ }
+
}
}
<Compile Include="DependencyResolutionTests.cs" />
<Compile Include="EffectiveFlowDirectionExtensions.cs" />
<Compile Include="ShellTestBase.cs" />
+ <Compile Include="ShellLifeCycleTests.cs" />
<Compile Include="ShellUriHandlerTests.cs" />
<Compile Include="CheckBoxUnitTests.cs" />
<Compile Include="VisualTests.cs" />
using System.Diagnostics;
using System.Runtime.CompilerServices;
using Xamarin.Forms.Internals;
+using System.ComponentModel;
namespace Xamarin.Forms
{
[DebuggerDisplay("Title = {Title}, Route = {Route}")]
public class BaseShellItem : NavigableElement, IPropertyPropagationController, IVisualController, IFlowDirectionController, ITabStopElement
{
+ public event EventHandler Appearing;
+ public event EventHandler Disappearing;
+
+ bool _hasAppearing;
+
#region PropertyKeys
internal static readonly BindablePropertyKey IsCheckedPropertyKey = BindableProperty.CreateReadOnly(nameof(IsChecked), typeof(bool), typeof(BaseShellItem), false);
set => SetValue(IsTabStopProperty, value);
}
+ internal virtual void SendAppearing()
+ {
+ if (_hasAppearing)
+ return;
+
+ _hasAppearing = true;
+ OnAppearing();
+ Appearing?.Invoke(this, EventArgs.Empty);
+ }
+
+ internal virtual void SendDisappearing()
+ {
+ if (!_hasAppearing)
+ return;
+
+ _hasAppearing = false;
+ OnDisappearing();
+ Disappearing?.Invoke(this, EventArgs.Empty);
+ }
+
+ protected virtual void OnAppearing()
+ {
+ }
+
+ protected virtual void OnDisappearing()
+ {
+ }
+
+ internal void OnAppearing(Action action)
+ {
+ if (_hasAppearing)
+ action();
+ else
+ {
+ EventHandler eventHandler = null;
+ eventHandler = (_, __) =>
+ {
+ this.Appearing -= eventHandler;
+ action();
+ };
+
+ this.Appearing += eventHandler;
+ }
+ }
+
protected virtual void OnTabStopPropertyChanged(bool oldValue, bool newValue) { }
protected virtual bool TabStopDefaultValueCreator() => true;
var uri = navigationRequest.Request.FullUri;
var queryString = navigationRequest.Query;
var queryData = ParseQueryString(queryString);
- var path = uri.AbsolutePath;
-
- path = path.TrimEnd('/');
-
- var parts = path.Substring(1).Split('/').ToList();
-
- if (parts.Count < 2)
- throw new InvalidOperationException("Path must be at least 2 items long in Shell navigation");
-
- var shellRoute = parts[0];
-
- var expectedShellRoute = Routing.GetRoute(this) ?? string.Empty;
- if (expectedShellRoute != shellRoute)
- throw new NotImplementedException();
- else
- parts.RemoveAt(0);
ApplyQueryAttributes(this, queryData, false);
var shellItem = navigationRequest.Request.Item;
+ var shellSection = navigationRequest.Request.Section;
+ ShellContent shellContent = navigationRequest.Request.Content;
+
if (shellItem != null)
{
ApplyQueryAttributes(shellItem, queryData, navigationRequest.Request.Section == null);
if (CurrentItem != shellItem)
+ {
SetValueFromRenderer(CurrentItemProperty, shellItem);
+ }
+
+ if (shellSection != null)
+ {
+ Shell.ApplyQueryAttributes(shellSection, queryData, navigationRequest.Request.Content == null);
+ if (shellItem.CurrentItem != shellSection)
+ {
+ shellItem.SetValueFromRenderer(ShellItem.CurrentItemProperty, shellSection);
+ }
- parts.RemoveAt(0);
+ if (shellContent != null)
+ {
+ Shell.ApplyQueryAttributes(shellContent, queryData, navigationRequest.Request.GlobalRoutes.Count == 0);
+ if (shellSection.CurrentItem != shellContent)
+ {
+ shellSection.SetValueFromRenderer(ShellSection.CurrentItemProperty, shellContent);
+ }
+
+ if (navigationRequest.Request.GlobalRoutes.Count > 0)
+ {
+ // TODO get rid of this hack and fix so if there's a stack the current page doesn't display
+ Device.BeginInvokeOnMainThread(async () =>
+ {
+ await shellSection.GoToAsync(navigationRequest, queryData, false);
+ });
+ }
- if (parts.Count > 0)
- await shellItem.GoToPart(navigationRequest, queryData);
+ }
+ }
}
else
{
await CurrentItem.CurrentItem.GoToAsync(navigationRequest, queryData, animate);
}
- //if (Routing.CompareWithRegisteredRoutes(shellItemRoute))
- //{
- // var shellItem = ShellItem.GetShellItemFromRouteName(shellItemRoute);
-
- // ApplyQueryAttributes(shellItem, queryData, parts.Count == 1);
-
- // if (CurrentItem != shellItem)
- // SetValueFromRenderer(CurrentItemProperty, shellItem);
-
- // if (parts.Count > 0)
- // await ((IShellItemController)shellItem).GoToPart(parts, queryData);
- //}
-
_accumulateNavigatedEvents = false;
// this can be null in the event that no navigation actually took place!
_accumulatedEvent = args;
else
{
- /* Removing this check for now as it doesn't properly cover all implicit scenarios
- * if (args.Current.Location.AbsolutePath.TrimEnd('/') != _lastNavigating.Location.AbsolutePath.TrimEnd('/'))
- throw new InvalidOperationException($"Navigation: Current location doesn't match navigation uri {args.Current.Location.AbsolutePath} != {_lastNavigating.Location.AbsolutePath}");
- */
- Navigated?.Invoke(this, args);
- //System.Diagnostics.Debug.WriteLine("Navigated: " + args.Current.Location);
+ var content = CurrentItem?.CurrentItem?.CurrentItem;
+ if (content != null)
+ {
+ content.OnAppearing(() => Navigated?.Invoke(this, args));
+ }
+ else
+ {
+ Navigated?.Invoke(this, args);
+ }
}
}
static void OnCurrentItemChanged(BindableObject bindable, object oldValue, object newValue)
{
+ if (oldValue is ShellItem oldShellItem)
+ oldShellItem.SendDisappearing();
+
+ if (newValue is ShellItem newShellItem)
+ newShellItem.SendAppearing();
+
var shell = (Shell)bindable;
UpdateChecked(shell);
protected override Task OnPushAsync(Page page, bool animated) => SectionProxy.PushAsync(page, animated);
protected override void OnRemovePage(Page page) => SectionProxy.RemovePage(page);
+
+ protected override Task<Page> OnPopModal(bool animated)
+ {
+ if (ModalStack.Count > 0)
+ ModalStack[ModalStack.Count - 1].SendDisappearing();
+
+ if (ModalStack.Count == 1)
+ _shell.CurrentItem.SendAppearing();
+
+ return base.OnPopModal(animated);
+ }
+ protected override Task OnPushModal(Page modal, bool animated)
+ {
+ if (ModalStack.Count == 0)
+ _shell.CurrentItem.SendDisappearing();
+
+ modal.SendAppearing();
+ return base.OnPushModal(modal, animated);
+ }
}
}
}
}
if (result != null && result.Parent != this)
+ {
OnChildAdded(result);
+ }
if (result == null)
throw new InvalidOperationException($"No Content found for {nameof(ShellContent)}, Title:{Title}, Route {Route}");
public ShellContent() => ((INotifyCollectionChanged)MenuItems).CollectionChanged += MenuItemsCollectionChanged;
-
+ internal bool IsVisibleContent => Parent is ShellSection shellSection && shellSection.IsVisibleSection;
internal override ReadOnlyCollection<Element> LogicalChildrenInternal => _logicalChildrenReadOnly ?? (_logicalChildrenReadOnly = new ReadOnlyCollection<Element>(_logicalChildren));
+ internal override void SendDisappearing()
+ {
+ base.SendDisappearing();
+ ((ContentCache ?? Content) as Page)?.SendDisappearing();
+ }
+
+ internal override void SendAppearing()
+ {
+ // only fire Appearing when the Content Page exists on the ShellContent
+ var content = ContentCache ?? Content;
+ if (content == null)
+ return;
+
+ base.SendAppearing();
+ ((ContentCache ?? Content) as Page)?.SendAppearing();
+ }
+
+ protected override void OnChildAdded(Element child)
+ {
+ base.OnChildAdded(child);
+ if (child is Page page && IsVisibleContent)
+ {
+ SendAppearing();
+ page.SendAppearing();
+ }
+ }
+
Page ContentCache
{
get => _contentCache;
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Xamarin.Forms.Core
+{
+ internal static class ShellExtensions
+ {
+ public static T SearchForRoute<T>(this Shell shell, string route) where T : BaseShellItem =>
+ (T)SearchForRoute(shell, route);
+
+ public static BaseShellItem SearchForRoute(this Shell shell, string route) =>
+ SearchForPart(shell, (p) => p.Route == route);
+
+
+ public static T SearchForRoute<T>(this BaseShellItem shell, string route) where T : BaseShellItem =>
+ (T)SearchForRoute(shell, route);
+
+ public static BaseShellItem SearchForRoute(this BaseShellItem shell, string route) =>
+ SearchForPart(shell, (p) => p.Route == route);
+
+
+ public static BaseShellItem SearchForPart(this Shell shell, Func<BaseShellItem, bool> searchBy)
+ {
+ for (var i = 0; i < shell.Items.Count; i++)
+ {
+ var result = SearchForPart(shell.Items[i], searchBy);
+ if (result != null)
+ return result;
+ }
+
+ return null;
+ }
+
+ public static BaseShellItem SearchForPart(this BaseShellItem part, Func<BaseShellItem, bool> searchBy)
+ {
+ if (searchBy(part))
+ return part;
+
+ BaseShellItem baseShellItem = null;
+ switch (part)
+ {
+ case ShellItem item:
+ foreach (var section in item.Items)
+ {
+ baseShellItem = SearchForPart(section, searchBy);
+ if (baseShellItem != null)
+ return baseShellItem;
+ }
+ break;
+ case ShellSection section:
+ foreach (var content in section.Items)
+ {
+ baseShellItem = SearchForPart(content, searchBy);
+ if (baseShellItem != null)
+ return baseShellItem;
+ }
+ break;
+ }
+
+ return null;
+ }
+ }
+}
#region IShellItemController
- internal Task GoToPart(NavigationRequest request, Dictionary<string, string> queryData)
- {
- var shellSection = request.Request.Section;
-
- if (shellSection == null)
- return Task.FromResult(true);
-
- Shell.ApplyQueryAttributes(shellSection, queryData, request.Request.Content == null);
-
- if (CurrentItem != shellSection)
- SetValueFromRenderer(CurrentItemProperty, shellSection);
-
- return shellSection.GoToPart(request, queryData);
- }
bool IShellItemController.ProposeSection(ShellSection shellSection, bool setValue)
{
static void OnCurrentItemChanged(BindableObject bindable, object oldValue, object newValue)
{
+ if (oldValue is BaseShellItem oldShellItem)
+ oldShellItem.SendDisappearing();
+
var shellItem = (ShellItem)bindable;
+ if (shellItem.Parent is Shell parentShell && parentShell.CurrentItem == shellItem)
+ {
+ if (newValue is BaseShellItem newShellItem)
+ newShellItem.SendAppearing();
+ }
if (shellItem.Parent is IShellController shell)
{
}
SendStructureChanged();
- }
+ }
+
+ internal override void SendAppearing()
+ {
+ base.SendAppearing();
+ if(CurrentItem != null && Parent is Shell shell && shell.CurrentItem == this)
+ {
+ CurrentItem.SendAppearing();
+ }
+ }
+
+ internal override void SendDisappearing()
+ {
+ base.SendDisappearing();
+ if (CurrentItem != null)
+ {
+ CurrentItem.SendDisappearing();
+ }
+ }
}
}
{
if (_navStack.Count > 1)
return _navStack[_navStack.Count - 1];
- return ((IShellContentController)CurrentItem).Page;
+ return ((IShellContentController)CurrentItem)?.Page;
}
}
callback(DisplayedPage);
}
- internal Task GoToPart(NavigationRequest request, Dictionary<string, string> queryData)
- {
- ShellContent shellContent = request.Request.Content;
-
- if (shellContent == null)
- return Task.FromResult(true);
-
- if (request.Request.GlobalRoutes.Count > 0)
- {
- // TODO get rid of this hack and fix so if there's a stack the current page doesn't display
- Device.BeginInvokeOnMainThread(async () =>
- {
- await GoToAsync(request, queryData, false);
- });
- }
-
- Shell.ApplyQueryAttributes(shellContent, queryData, request.Request.GlobalRoutes.Count == 0);
-
- if (CurrentItem != shellContent)
- SetValueFromRenderer(CurrentItemProperty, shellContent);
-
- return Task.FromResult(true);
- }
-
bool IShellSectionController.RemoveContentInsetObserver(IShellContentInsetObserver observer)
{
return _observers.Remove(observer);
DisplayedPage = currentItem.Page;
}
+ PresentedPageAppearing();
}
protected override void OnChildAdded(Element child)
if (!allow)
return null;
-
+
var page = _navStack[_navStack.Count - 1];
var args = new NavigationRequestedEventArgs(page, animated)
{
RequestType = NavigationRequestType.Pop
};
+ PresentedPageDisappearing();
_navStack.Remove(page);
+ PresentedPageAppearing();
+
SendAppearanceChanged();
_navigationRequested?.Invoke(this, args);
if (args.Task != null)
for (int i = 1; i < oldStack.Count; i++)
{
+ oldStack[i].SendDisappearing();
RemovePage(oldStack[i]);
}
+ PresentedPageAppearing();
SendUpdateCurrentState(ShellNavigationSource.PopToRoot);
}
if (!allow)
return Task.FromResult(true);
-
+
var args = new NavigationRequestedEventArgs(page, animated)
{
RequestType = NavigationRequestType.Push
};
+ PresentedPageDisappearing();
_navStack.Add(page);
+ PresentedPageAppearing();
AddPage(page);
SendAppearanceChanged();
_navigationRequested?.Invoke(this, args);
if (!_navStack.Contains(page))
return;
+ bool currentPage = (((IShellSectionController)this).PresentedPage) == page;
var stack = _navStack.ToList();
stack.Remove(page);
var allow = ((IShellController)Shell).ProposeNavigation(
if (!allow)
return;
+ if(currentPage)
+ PresentedPageDisappearing();
_navStack.Remove(page);
+ if(currentPage)
+ PresentedPageAppearing();
+
SendAppearanceChanged();
RemovePage(page);
var args = new NavigationRequestedEventArgs(page, false)
SendUpdateCurrentState(ShellNavigationSource.Remove);
}
+ internal bool IsVisibleSection => Parent?.Parent is Shell shell && shell.CurrentItem?.CurrentItem == this;
+ void PresentedPageDisappearing()
+ {
+ if (this is IShellSectionController sectionController)
+ {
+ CurrentItem?.SendDisappearing();
+ sectionController.PresentedPage?.SendDisappearing();
+ }
+ }
+
+ void PresentedPageAppearing()
+ {
+ if (IsVisibleSection && this is IShellSectionController sectionController)
+ {
+ if(_navStack.Count == 1)
+ CurrentItem?.SendAppearing();
+
+ sectionController.PresentedPage?.SendAppearing();
+ }
+ }
+
static void OnCurrentItemChanged(BindableObject bindable, object oldValue, object newValue)
{
var shellSection = (ShellSection)bindable;
+ if (oldValue is ShellContent oldShellItem)
+ oldShellItem.SendDisappearing();
+
+ shellSection.PresentedPageAppearing();
+
if (shellSection.Parent?.Parent is IShellController shell)
{
shell.UpdateCurrentState(ShellNavigationSource.ShellSectionChanged);
}
}
+ internal override void SendDisappearing()
+ {
+ base.SendDisappearing();
+ PresentedPageDisappearing();
+
+ }
+
+ internal override void SendAppearing()
+ {
+ base.SendAppearing();
+ PresentedPageAppearing();
+ }
+
class NavigationImpl : NavigationProxy
{
readonly ShellSection _owner;
protected override Task OnPushAsync(Page page, bool animated) => _owner.OnPushAsync(page, animated);
protected override void OnRemovePage(Page page) => _owner.OnRemovePage(page);
+
+ protected override Task<Page> OnPopModal(bool animated)
+ {
+ if (ModalStack.Count > 0)
+ ModalStack[ModalStack.Count - 1].SendDisappearing();
+
+ if(ModalStack.Count == 1)
+ {
+ _owner.PresentedPageAppearing();
+ }
+
+ return base.OnPopModal(animated);
+ }
+ protected override Task OnPushModal(Page modal, bool animated)
+ {
+ if (ModalStack.Count == 0)
+ {
+ _owner.PresentedPageDisappearing();
+ }
+
+ modal.SendAppearing();
+ return base.OnPushModal(modal, animated);
+ }
}
}
}
if (result)
{
- var page = ((IShellContentController)shellContent).Page;
- if (page == null)
- throw new ArgumentNullException(nameof(page), "Shell Content Page is Null");
-
- ShellSection.SetValueFromRenderer(ShellSection.CurrentItemProperty, shellContent);
-
- _toolbarTracker.Page = page;
+ UpdateCurrentItem(shellContent);
}
else
{
_selecting = true;
- var index = ShellSection.Items.IndexOf(ShellSection.CurrentItem);
// Android doesn't really appreciate you calling SetCurrentItem inside a OnPageSelected callback.
// It wont crash but the way its programmed doesn't really anticipate re-entrancy around that method
Device.BeginInvokeOnMainThread(() =>
{
- _viewPager.SetCurrentItem(index, false);
+ if (position < _viewPager.ChildCount && _toolbarTracker != null)
+ {
+ _viewPager.SetCurrentItem(position, false);
+ UpdateCurrentItem(shellContent);
+ }
+
_selecting = false;
});
}
}
+ void UpdateCurrentItem(ShellContent content)
+ {
+ if (_toolbarTracker == null)
+ return;
+
+ var page = ((IShellContentController)content).Page;
+ if (page == null)
+ throw new ArgumentNullException(nameof(page), "Shell Content Page is Null");
+
+ ShellSection.SetValueFromRenderer(ShellSection.CurrentItemProperty, content);
+ _toolbarTracker.Page = page;
+ }
+
#endregion IOnPageChangeListener
#region IAppearanceObserver