Skip to content
This repository has been archived by the owner on May 1, 2024. It is now read-only.

Commit

Permalink
[Core] Added RootPage to NavigationPage (#464)
Browse files Browse the repository at this point in the history
* d

* removed whitespace

* Using ArgumentNullException

* changes
  • Loading branch information
adrianknight89 authored and rmarinho committed Feb 2, 2017
1 parent 750b034 commit 070f1dc
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 22 deletions.
103 changes: 97 additions & 6 deletions Xamarin.Forms.Core.UnitTests/NavigationUnitTest.cs
Expand Up @@ -14,14 +14,17 @@ public async Task TestNavigationImplPush ()
{
NavigationPage nav = new NavigationPage ();

Assert.IsNull(nav.RootPage);
Assert.IsNull (nav.CurrentPage);

Label child = new Label {Text = "Label"};
Page childRoot = new ContentPage {Content = child};

await nav.Navigation.PushAsync (childRoot);

Assert.AreSame (childRoot, nav.CurrentPage);
Assert.AreSame(childRoot, nav.RootPage);
Assert.AreSame(childRoot, nav.CurrentPage);
Assert.AreSame(nav.RootPage, nav.CurrentPage);
}

[Test]
Expand All @@ -40,31 +43,44 @@ public async Task TestNavigationImplPop ()

bool fired = false;
nav.Popped += (sender, e) => fired = true;

Assert.AreSame(childRoot, nav.RootPage);
Assert.AreNotSame(childRoot2, nav.RootPage);
Assert.AreNotSame(nav.RootPage, nav.CurrentPage);

var popped = await nav.Navigation.PopAsync ();

Assert.True (fired);
Assert.AreSame (childRoot, nav.CurrentPage);
Assert.AreSame(childRoot, nav.RootPage);
Assert.AreSame(childRoot, nav.CurrentPage);
Assert.AreSame(nav.RootPage, nav.CurrentPage);
Assert.AreEqual (childRoot2, popped);

await nav.PopAsync ();
var last = await nav.Navigation.PopAsync ();

Assert.IsNull (last);
Assert.IsNotNull(nav.RootPage);
Assert.IsNotNull(nav.CurrentPage);
Assert.AreSame(nav.RootPage, nav.CurrentPage);
}

[Test]
public async Task TestPushRoot ()
{
NavigationPage nav = new NavigationPage ();

Assert.IsNull(nav.RootPage);
Assert.IsNull (nav.CurrentPage);

Label child = new Label {Text = "Label"};
Page childRoot = new ContentPage {Content = child};

await nav.PushAsync (childRoot);

Assert.AreSame (childRoot, nav.CurrentPage);
Assert.AreSame (childRoot, nav.RootPage);
Assert.AreSame(childRoot, nav.CurrentPage);
Assert.AreSame(nav.RootPage, nav.CurrentPage);
}

[Test]
Expand Down Expand Up @@ -96,10 +112,15 @@ public async Task TestDoublePush ()
bool fired = false;
nav.Pushed += (sender, e) => fired = true;

Assert.AreSame(childRoot, nav.RootPage);
Assert.AreSame(childRoot, nav.CurrentPage);

await nav.PushAsync (childRoot);

Assert.False (fired);
Assert.AreEqual (childRoot, nav.CurrentPage);
Assert.AreSame(childRoot, nav.RootPage);
Assert.AreSame(childRoot, nav.CurrentPage);
Assert.AreSame(nav.RootPage, nav.CurrentPage);
}

[Test]
Expand Down Expand Up @@ -184,7 +205,9 @@ public async Task TestPopToRoot ()
nav.PopToRootAsync ();

Assert.True (signaled);
Assert.AreEqual (root, nav.CurrentPage);
Assert.AreSame (root, nav.RootPage);
Assert.AreSame(root, nav.CurrentPage);
Assert.AreSame(nav.RootPage, nav.CurrentPage);
}

[Test]
Expand All @@ -209,7 +232,9 @@ public async Task TestPopToRootEventArgs ()
Assert.AreEqual (2, poppedChildren.Count);
Assert.Contains (child1, poppedChildren);
Assert.Contains (child2, poppedChildren);
Assert.AreEqual (root, nav.CurrentPage);
Assert.AreSame(root, nav.RootPage);
Assert.AreSame(root, nav.CurrentPage);
Assert.AreSame(nav.RootPage, nav.CurrentPage);
}

[Test]
Expand Down Expand Up @@ -458,6 +483,72 @@ public void DoesNotHandleBackButtonWhenNoNavStack ()
Assert.False (result);
}

[Test]
public void TestInsertPage()
{
var root = new ContentPage { Title = "Root" };
var newPage = new ContentPage();
var navPage = new NavigationPage(root);

navPage.Navigation.InsertPageBefore(newPage, navPage.RootPage);

Assert.AreSame(newPage, navPage.RootPage);
Assert.AreNotSame(newPage, navPage.CurrentPage);
Assert.AreNotSame(navPage.RootPage, navPage.CurrentPage);
Assert.AreSame(root, navPage.CurrentPage);

Assert.Throws<ArgumentException>(() =>
{
navPage.Navigation.InsertPageBefore(new ContentPage(), new ContentPage());
});

Assert.Throws<ArgumentException>(() =>
{
navPage.Navigation.InsertPageBefore(navPage.RootPage, navPage.CurrentPage);
});

Assert.Throws<ArgumentNullException>(() =>
{
navPage.Navigation.InsertPageBefore(null, navPage.CurrentPage);
});

Assert.Throws<ArgumentNullException>(() =>
{
navPage.Navigation.InsertPageBefore(new ContentPage(), null);
});
}

[Test]
public async void TestRemovePage()
{
var root = new ContentPage { Title = "Root" };
var newPage = new ContentPage();
var navPage = new NavigationPage(root);
await navPage.PushAsync(newPage);

navPage.Navigation.RemovePage(root);

Assert.AreSame(newPage, navPage.RootPage);
Assert.AreSame(newPage, navPage.CurrentPage);
Assert.AreSame(navPage.RootPage, navPage.CurrentPage);
Assert.AreNotSame(root, navPage.CurrentPage);

Assert.Throws<ArgumentException>(() =>
{
navPage.Navigation.RemovePage(root);
});

Assert.Throws<InvalidOperationException>(() =>
{
navPage.Navigation.RemovePage(newPage);
});

Assert.Throws<ArgumentNullException>(() =>
{
navPage.Navigation.RemovePage(null);
});
}

[Test (Description = "CurrentPage should not be set to null when you attempt to pop the last page")]
[Property ("Bugzilla", 28335)]
public async Task CurrentPageNotNullPoppingRoot()
Expand Down
53 changes: 37 additions & 16 deletions Xamarin.Forms.Core/NavigationPage.cs
Expand Up @@ -28,7 +28,10 @@ public class NavigationPage : Page, IPageContainer<Page>, INavigationPageControl

static readonly BindablePropertyKey CurrentPagePropertyKey = BindableProperty.CreateReadOnly("CurrentPage", typeof(Page), typeof(NavigationPage), null);
public static readonly BindableProperty CurrentPageProperty = CurrentPagePropertyKey.BindableProperty;


static readonly BindablePropertyKey RootPagePropertyKey = BindableProperty.CreateReadOnly(nameof(RootPage), typeof(Page), typeof(NavigationPage), null);
public static readonly BindableProperty RootPageProperty = RootPagePropertyKey.BindableProperty;

public NavigationPage()
{
_platformConfigurationRegistry = new Lazy<PlatformConfigurationRegistry<NavigationPage>>(() => new PlatformConfigurationRegistry<NavigationPage>(this));
Expand Down Expand Up @@ -92,6 +95,12 @@ public Page CurrentPage
private set { SetValue(CurrentPagePropertyKey, value); }
}

public Page RootPage
{
get { return (Page)GetValue(RootPageProperty); }
private set { SetValue(RootPagePropertyKey, value); }
}

public static string GetBackButtonTitle(BindableObject page)
{
return (string)page.GetValue(BackButtonTitleProperty);
Expand Down Expand Up @@ -304,8 +313,14 @@ async Task<Page> INavigationPageController.PopAsyncInner(bool animated, bool fas

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 (!PageController.InternalChildren.Contains(before))
throw new ArgumentException("before must be a child of the NavigationPage", "before");
throw new ArgumentException($"{nameof(before)} must be a child of the NavigationPage", nameof(before));

if (PageController.InternalChildren.Contains(page))
throw new ArgumentException("Cannot insert page which is already in the navigation stack");
Expand All @@ -316,6 +331,9 @@ void InsertPageBefore(Page page, Page before)
int index = PageController.InternalChildren.IndexOf(before);
PageController.InternalChildren.Insert(index, page);

if (index == 0)
RootPage = page;

// Shouldn't be required?
if (Width > 0 && Height > 0)
ForceLayout();
Expand All @@ -326,15 +344,13 @@ async Task PopToRootAsyncInner(bool animated)
if (((INavigationPageController)this).StackDepth == 1)
return;

var root = (Page)PageController.InternalChildren.First();

var childrenToRemove = PageController.InternalChildren.ToArray().Where(c => c != root);
foreach (var child in childrenToRemove)
Element[] childrenToRemove = PageController.InternalChildren.Skip(1).ToArray();
foreach (Element child in childrenToRemove)
PageController.InternalChildren.Remove(child);

CurrentPage = root;
CurrentPage = RootPage;

var args = new NavigationRequestedEventArgs(root, animated);
var args = new NavigationRequestedEventArgs(RootPage, animated);

EventHandler<NavigationRequestedEventArgs> requestPopToRoot = PopToRootRequestedInternal;
if (requestPopToRoot != null)
Expand All @@ -345,8 +361,7 @@ async Task PopToRootAsyncInner(bool animated)
await args.Task;
}

if (PoppedToRoot != null)
PoppedToRoot(this, new PoppedToRootEventArgs(root, childrenToRemove.OfType<Page>().ToList()));
PoppedToRoot?.Invoke(this, new PoppedToRootEventArgs(RootPage, childrenToRemove.OfType<Page>().ToList()));
}

async Task PushAsyncInner(Page page, bool animated)
Expand All @@ -367,24 +382,29 @@ async Task PushAsyncInner(Page page, bool animated)
await args.Task;
}

if (Pushed != null)
Pushed(this, args);
Pushed?.Invoke(this, args);
}

void PushPage(Page page)
{
PageController.InternalChildren.Add(page);

if (PageController.InternalChildren.Count == 1)
RootPage = page;

CurrentPage = page;
}

void RemovePage(Page page)
{
if (page == CurrentPage && ((INavigationPageController)this).StackDepth <= 1)
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)
{
Log.Warning("NavigationPage", "RemovePage called for CurrentPage object. This can result in undesired behavior, consider called PopAsync instead.");
Log.Warning("NavigationPage", "RemovePage called for CurrentPage object. This can result in undesired behavior, consider calling PopAsync instead.");
PopAsync();
return;
}
Expand All @@ -393,10 +413,11 @@ void RemovePage(Page page)
throw new ArgumentException("Page to remove must be contained on this Navigation Page");

EventHandler<NavigationRequestedEventArgs> handler = RemovePageRequestedInternal;
if (handler != null)
handler(this, new NavigationRequestedEventArgs(page, true));
handler?.Invoke(this, new NavigationRequestedEventArgs(page, true));

PageController.InternalChildren.Remove(page);
if (RootPage == page)
RootPage = (Page)PageController.InternalChildren.First();
}

void SafePop()
Expand Down
36 changes: 36 additions & 0 deletions docs/Xamarin.Forms.Core/Xamarin.Forms/NavigationPage.xml
Expand Up @@ -226,6 +226,42 @@
</remarks>
</Docs>
</Member>
<Member MemberName="RootPage">
<MemberSignature Language="C#" Value="public Xamarin.Forms.Page RootPage { get; }" />
<MemberSignature Language="ILAsm" Value=".property instance class Xamarin.Forms.Page RootPage" />
<MemberType>Property</MemberType>
<AssemblyInfo>
<AssemblyVersion>2.0.0.0</AssemblyVersion>
</AssemblyInfo>
<ReturnValue>
<ReturnType>Xamarin.Forms.Page</ReturnType>
</ReturnValue>
<Docs>
<summary>
The <see cref="T:Xamarin.Forms.Page" /> that is the root of the navigation stack.
</summary>
<value>To be added.</value>
<remarks>To be added.</remarks>
</Docs>
</Member>
<Member MemberName="RootPageProperty">
<MemberSignature Language="C#" Value="public static readonly Xamarin.Forms.BindableProperty RootPageProperty;" />
<MemberSignature Language="ILAsm" Value=".field public static initonly class Xamarin.Forms.BindableProperty RootPageProperty" />
<MemberType>Field</MemberType>
<AssemblyInfo>
<AssemblyVersion>2.0.0.0</AssemblyVersion>
</AssemblyInfo>
<ReturnValue>
<ReturnType>Xamarin.Forms.BindableProperty</ReturnType>
</ReturnValue>
<Docs>
<summary>
Identifies the <see cref="P:Xamarin.Forms.NavigationPage.RootPage" /> property.
</summary>
<remarks>
</remarks>
</Docs>
</Member>
<Member MemberName="GetBackButtonTitle">
<MemberSignature Language="C#" Value="public static string GetBackButtonTitle (Xamarin.Forms.BindableObject page);" />
<MemberSignature Language="ILAsm" Value=".method public static hidebysig string GetBackButtonTitle(class Xamarin.Forms.BindableObject page) cil managed" />
Expand Down

0 comments on commit 070f1dc

Please sign in to comment.