diff --git a/Xamarin.Forms.Core.UnitTests/ShellNavigationTests.cs b/Xamarin.Forms.Core.UnitTests/ShellNavigationTests.cs
new file mode 100644
index 00000000000..6720f1c2941
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/ShellNavigationTests.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using NUnit.Framework;
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class ShellNavigationTests : ShellTestBase
+ {
+ [Test]
+ public async Task NavigationWithQueryStringWhenPageMatchesBindingContext()
+ {
+ var shell = new Shell();
+
+ var one = new ShellItem { Route = "one" };
+ var two = new ShellItem { Route = "two" };
+
+ var tabone = MakeSimpleShellSection("tabone", "content");
+ var tabfour = MakeSimpleShellSection("tabfour", "content", null);
+
+ one.Items.Add(tabone);
+ two.Items.Add(tabfour);
+
+ shell.Items.Add(one);
+ shell.Items.Add(two);
+
+ ShellTestPage pagetoTest = new ShellTestPage();
+ await shell.GoToAsync(new ShellNavigationState($"//two/tabfour/content?{nameof(ShellTestPage.SomeQueryParameter)}=1234&two.rabbit=monkey"));
+ two.CurrentItem.CurrentItem.ContentTemplate = new DataTemplate(() =>
+ {
+ pagetoTest = new ShellTestPage();
+ pagetoTest.BindingContext = pagetoTest;
+ return pagetoTest;
+ });
+
+
+ var page = (two.CurrentItem.CurrentItem as IShellContentController).GetOrCreateContent();
+ Assert.AreEqual("1234", (page as ShellTestPage).SomeQueryParameter);
+
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/ShellUriHandlerTests.cs b/Xamarin.Forms.Core.UnitTests/ShellUriHandlerTests.cs
index 4129c7124df..3010ae2fc93 100644
--- a/Xamarin.Forms.Core.UnitTests/ShellUriHandlerTests.cs
+++ b/Xamarin.Forms.Core.UnitTests/ShellUriHandlerTests.cs
@@ -87,7 +87,7 @@ public async Task GlobalRegisterAbsoluteMatching()
Routing.RegisterRoute("/seg1/seg2/seg3", typeof(object));
var request = ShellUriHandler.GetNavigationRequest(shell, CreateUri("/seg1/seg2/seg3"));
- Assert.AreEqual("app://shell/IMPL_shell/seg1/seg2/seg3", request.Request.FullUri.ToString());
+ Assert.AreEqual("app://shell/IMPL_shell/seg1/seg2/seg3", request.CurrentRoute.FullUriWithImplicit.ToString());
}
[Test]
@@ -120,8 +120,8 @@ public async Task ShellRelativeGlobalRegistration()
await shell.GoToAsync("//item1/section1/rootlevelcontent1");
var request = ShellUriHandler.GetNavigationRequest(shell, CreateUri("section1/edit"), true);
- Assert.AreEqual(1, request.Request.GlobalRoutes.Count);
- Assert.AreEqual("item1/section1/edit", request.Request.GlobalRoutes.First());
+ Assert.AreEqual(4, request.CurrentRoute.PathParts.Count);
+ Assert.AreEqual("item1/section1/edit", request.CurrentRoute.PathParts[3].Path);
}
[Test]
@@ -137,9 +137,9 @@ public async Task ShellSectionWithRelativeEditUpOneLevelMultiple()
var request = ShellUriHandler.GetNavigationRequest(shell, CreateUri("//rootlevelcontent1/add/edit"));
- Assert.AreEqual(2, request.Request.GlobalRoutes.Count);
- Assert.AreEqual("section1/add", request.Request.GlobalRoutes.First());
- Assert.AreEqual("section1/edit", request.Request.GlobalRoutes.Skip(1).First());
+ Assert.AreEqual(5, request.CurrentRoute.PathParts.Count);
+ Assert.AreEqual("section1/add", request.CurrentRoute.PathParts[3].Path);
+ Assert.AreEqual("section1/edit", request.CurrentRoute.PathParts[4].Path);
}
[Test]
@@ -154,8 +154,8 @@ public async Task ShellSectionWithGlobalRouteAbsolute()
var request = ShellUriHandler.GetNavigationRequest(shell, CreateUri("//rootlevelcontent1/edit"));
- Assert.AreEqual(1, request.Request.GlobalRoutes.Count);
- Assert.AreEqual("edit", request.Request.GlobalRoutes.First());
+ Assert.AreEqual(4, request.CurrentRoute.PathParts.Count);
+ Assert.AreEqual("edit", request.CurrentRoute.PathParts[3].Path);
}
[Test]
@@ -171,8 +171,8 @@ public async Task ShellSectionWithGlobalRouteRelative()
await shell.GoToAsync("//rootlevelcontent1");
var request = ShellUriHandler.GetNavigationRequest(shell, CreateUri("edit"));
- Assert.AreEqual(1, request.Request.GlobalRoutes.Count);
- Assert.AreEqual("edit", request.Request.GlobalRoutes.First());
+ Assert.AreEqual(4, request.CurrentRoute.PathParts.Count);
+ Assert.AreEqual("edit", request.CurrentRoute.PathParts[3].Path);
}
@@ -189,7 +189,7 @@ public async Task ShellSectionWithRelativeEditUpOneLevel()
await shell.GoToAsync("//rootlevelcontent1");
var request = ShellUriHandler.GetNavigationRequest(shell, CreateUri("edit"), true);
- Assert.AreEqual("section1/edit", request.Request.GlobalRoutes.First());
+ Assert.AreEqual("section1/edit", request.CurrentRoute.PathParts[3].Path);
}
[Test]
@@ -205,6 +205,7 @@ public async Task ShellSectionWithRelativeEdit()
await shell.GoToAsync("//rootlevelcontent1");
var location = shell.CurrentState.FullLocation;
+
await shell.GoToAsync("edit", false, true);
Assert.AreEqual(editShellContent, shell.CurrentItem.CurrentItem.CurrentItem);
@@ -275,6 +276,25 @@ public async Task ShellItemAndContentOnly()
Assert.IsTrue(builders.Contains("//item2/rootlevelcontent"));
}
+ [Test]
+ public async Task PoppingRemovesRouteFromStack()
+ {
+ var shell = new Shell();
+ var item1 = CreateShellItem(asImplicit: true, shellSectionRoute: "domestic", shellContentRoute: "cats", shellItemRoute: "animals");
+
+ shell.Items.Add(item1);
+
+ Routing.RegisterRoute("catdetails", typeof(ContentPage));
+ await shell.GoToAsync($"catdetails");
+ await shell.Navigation.PopAsync();
+ await shell.GoToAsync($"catdetails");
+
+ Assert.AreEqual(
+ "//animals/domestic/cats/catdetails",
+ shell.CurrentState.FullLocation.ToString()
+ );
+ }
+
[Test]
public async Task AbsoluteNavigationToRelativeWithGlobal()
@@ -350,12 +370,12 @@ public async Task ConvertToStandardFormat()
foreach(var uri in TestUris)
{
- Assert.AreEqual(new Uri("app://shell/IMPL_shell/path"), ShellUriHandler.ConvertToStandardFormat(shell, uri), $"{uri}");
+ Assert.AreEqual(new Uri("app://shell/IMPL_shell/path"), ShellUriHandler.ConvertToStandardFormat(uri), $"{uri}");
if(!uri.IsAbsoluteUri)
{
var reverse = new Uri(uri.OriginalString.Replace("/", "\\"), UriKind.Relative);
- Assert.AreEqual(new Uri("app://shell/IMPL_shell/path"), ShellUriHandler.ConvertToStandardFormat(shell, reverse));
+ Assert.AreEqual(new Uri("app://shell/IMPL_shell/path"), ShellUriHandler.ConvertToStandardFormat(reverse));
}
}
diff --git a/Xamarin.Forms.Core.UnitTests/Xamarin.Forms.Core.UnitTests.csproj b/Xamarin.Forms.Core.UnitTests/Xamarin.Forms.Core.UnitTests.csproj
index fd38f20a0b8..3bb01acac1d 100644
--- a/Xamarin.Forms.Core.UnitTests/Xamarin.Forms.Core.UnitTests.csproj
+++ b/Xamarin.Forms.Core.UnitTests/Xamarin.Forms.Core.UnitTests.csproj
@@ -74,6 +74,7 @@
+
diff --git a/Xamarin.Forms.Core/Shell/RouteRequestBuilder.cs b/Xamarin.Forms.Core/Shell/RouteRequestBuilder.cs
new file mode 100644
index 00000000000..16f500aa761
--- /dev/null
+++ b/Xamarin.Forms.Core/Shell/RouteRequestBuilder.cs
@@ -0,0 +1,157 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Xamarin.Forms
+{
+ ///
+ /// This attempts to locate the intended route trying to be navigated to
+ ///
+ internal class RouteRequestBuilder
+ {
+ readonly List _globalRouteMatches = new List();
+ readonly List _matchedSegments = new List();
+ readonly List _fullSegments = new List();
+ readonly string[] _allSegments = null;
+ readonly static string _uriSeparator = "/";
+ public Shell Shell { get; private set; }
+ public ShellItem Item { get; private set; }
+ public ShellSection Section { get; private set; }
+ public ShellContent Content { get; private set; }
+ public object LowestChild =>
+ (object)Content ?? (object)Section ?? (object)Item ?? (object)Shell;
+
+ public RouteRequestBuilder(string shellSegment, string userSegment, object node, string[] allSegments)
+ {
+ _allSegments = allSegments;
+ if (node != null)
+ AddMatch(shellSegment, userSegment, node);
+ else
+ AddGlobalRoute(userSegment, shellSegment);
+ }
+
+ public RouteRequestBuilder(RouteRequestBuilder builder)
+ {
+ _allSegments = builder._allSegments;
+ _matchedSegments.AddRange(builder._matchedSegments);
+ _fullSegments.AddRange(builder._fullSegments);
+ _globalRouteMatches.AddRange(builder._globalRouteMatches);
+ Shell = builder.Shell;
+ Item = builder.Item;
+ Section = builder.Section;
+ Content = builder.Content;
+ }
+
+ public void AddGlobalRoute(string routeName, string segment)
+ {
+ _globalRouteMatches.Add(routeName);
+ _fullSegments.Add(segment);
+ _matchedSegments.Add(segment);
+ }
+
+
+ public void AddMatch(string shellSegment, string userSegment, object node)
+ {
+ if (node == null)
+ throw new ArgumentNullException(nameof(node));
+
+ switch (node)
+ {
+ case ShellUriHandler.GlobalRouteItem globalRoute:
+ if (globalRoute.IsFinished)
+ {
+ _globalRouteMatches.Add(globalRoute.SourceRoute);
+ }
+ break;
+ case Shell shell:
+ Shell = shell;
+ break;
+ case ShellItem item:
+ Item = item;
+ break;
+ case ShellSection section:
+ Section = section;
+
+ if (Item == null)
+ {
+ Item = Section.Parent as ShellItem;
+ _fullSegments.Add(Item.Route);
+ }
+
+ break;
+ case ShellContent content:
+ Content = content;
+ if (Section == null)
+ {
+ Section = Content.Parent as ShellSection;
+ _fullSegments.Add(Section.Route);
+ }
+
+ if (Item == null)
+ {
+ Item = Section.Parent as ShellItem;
+ _fullSegments.Insert(0, Item.Route);
+ }
+
+ break;
+
+ }
+
+ // if shellSegment == userSegment it means the implicit route is part of the request
+ if (!Routing.IsImplicit(shellSegment) || shellSegment == userSegment)
+ _matchedSegments.Add(shellSegment);
+
+ _fullSegments.Add(shellSegment);
+ }
+
+ public string NextSegment
+ {
+ get
+ {
+ var nextMatch = _matchedSegments.Count;
+ if (nextMatch >= _allSegments.Length)
+ return null;
+
+ return _allSegments[nextMatch];
+ }
+ }
+
+ public string RemainingPath
+ {
+ get
+ {
+ var nextMatch = _matchedSegments.Count;
+ if (nextMatch >= _allSegments.Length)
+ return null;
+
+ return Routing.FormatRoute(String.Join(_uriSeparator, _allSegments.Skip(nextMatch)));
+ }
+ }
+ public string[] RemainingSegments
+ {
+ get
+ {
+ var nextMatch = _matchedSegments.Count;
+ if (nextMatch >= _allSegments.Length)
+ return null;
+
+ return _allSegments.Skip(nextMatch).ToArray();
+ }
+ }
+
+ string MakeUriString(List segments)
+ {
+ if (segments[0].StartsWith(_uriSeparator, StringComparison.Ordinal) || segments[0].StartsWith("\\", StringComparison.Ordinal))
+ return String.Join(_uriSeparator, segments);
+
+ return $"//{String.Join(_uriSeparator, segments)}";
+ }
+
+ public string PathNoImplicit => MakeUriString(_matchedSegments);
+ public string PathFull => MakeUriString(_fullSegments);
+
+ public bool IsFullMatch => _matchedSegments.Count == _allSegments.Length;
+ public List GlobalRouteMatches => _globalRouteMatches;
+ public List SegmentsMatched => _matchedSegments;
+ }
+}
diff --git a/Xamarin.Forms.Core/Shell/Shell.cs b/Xamarin.Forms.Core/Shell/Shell.cs
index 3cc8fb66384..11437c02b1e 100644
--- a/Xamarin.Forms.Core/Shell/Shell.cs
+++ b/Xamarin.Forms.Core/Shell/Shell.cs
@@ -343,38 +343,17 @@ void IShellController.UpdateCurrentState(ShellNavigationSource source)
public static Shell Current => Application.Current?.MainPage as Shell;
- List BuildAllTheRoutes()
+ public Task GoToAsync(ShellNavigationState state, bool animate = true)
{
- List routes = new List();
- // todo make better maybe
-
- for (var i = 0; i < Items.Count; i++)
- {
- var item = Items[i];
-
- for (var j = 0; j < item.Items.Count; j++)
- {
- var section = item.Items[j];
-
- for (var k = 0; k < section.Items.Count; k++)
- {
- var content = section.Items[k];
-
- string longUri = $"{RouteScheme}://{RouteHost}/{Routing.GetRoute(this)}/{Routing.GetRoute(item)}/{Routing.GetRoute(section)}/{Routing.GetRoute(content)}";
-
- longUri = longUri.TrimEnd('/');
-
- routes.Add(new RequestDefinition(longUri, item, section, content, new List()));
- }
- }
- }
-
- return routes;
+ return GoToAsync(state, animate, false);
}
- public Task GoToAsync(ShellNavigationState state, bool animate = true)
+ internal ShellRouteState RouteState
{
- return GoToAsync(state, animate, false);
+ get
+ {
+ return new ShellRouteState(this);
+ }
}
internal async Task GoToAsync(ShellNavigationState state, bool animate, bool enableRelativeShellRoutes)
@@ -386,70 +365,100 @@ internal async Task GoToAsync(ShellNavigationState state, bool animate, bool ena
_accumulateNavigatedEvents = true;
- var navigationRequest = ShellUriHandler.GetNavigationRequest(this, state.FullLocation, enableRelativeShellRoutes);
- var uri = navigationRequest.Request.FullUri;
- var queryString = navigationRequest.Query;
- var queryData = ParseQueryString(queryString);
- var path = uri.AbsolutePath;
+ try
+ {
- path = path.TrimEnd('/');
+ ShellRouteState navigationRequest = null;
- var parts = path.Substring(1).Split('/').ToList();
+ if (!enableRelativeShellRoutes)
+ navigationRequest = await ShellUriParser.ParseAsync(new ShellUriParserArgs(this, state.FullLocation));
+ else // this path is really for ui testing only
+ navigationRequest = ShellUriHandler.GetNavigationRequest(this, state.FullLocation, enableRelativeShellRoutes);
- if (parts.Count < 2)
- throw new InvalidOperationException("Path must be at least 2 items long in Shell navigation");
+ navigationRequest = await ShellNavigationRequest.NavigatingToAsync(new ShellNavigationArgs(this, navigationRequest));
- var shellRoute = parts[0];
+ if (navigationRequest == null || navigationRequest == this.RouteState)
+ {
+ return;
+ }
- var expectedShellRoute = Routing.GetRoute(this) ?? string.Empty;
- if (expectedShellRoute != shellRoute)
- throw new NotImplementedException();
- else
- parts.RemoveAt(0);
+ var currentRoute = navigationRequest.CurrentRoute;
+ var pathParts = currentRoute.PathParts;
+ ApplyQueryAttributes(this, currentRoute.NavigationParameters, false);
- ApplyQueryAttributes(this, queryData, false);
+ // Right now this is a rigid structure but later down the road this will have more variations
+ // for example it might only be a ShellContent
+ ShellItem shellItem = (ShellItem)pathParts[0].ShellPart;
+ ShellSection shellSection = (ShellSection)pathParts[1].ShellPart;
+ ShellContent shellContent = (ShellContent)pathParts[2].ShellPart;
+ bool shellSectionChanged = false;
+ bool shellItemChanged = false;
+ bool shellContentChanged = false;
- var shellItem = navigationRequest.Request.Item;
- if (shellItem != null)
- {
- ApplyQueryAttributes(shellItem, queryData, navigationRequest.Request.Section == null);
+ ShellApplyParameters.ApplyParameters(new ShellLifecycleArgs(this, null, currentRoute));
+ ShellApplyParameters.ApplyParameters(new ShellLifecycleArgs(shellItem, pathParts[0], currentRoute));
+ ShellApplyParameters.ApplyParameters(new ShellLifecycleArgs(shellSection, pathParts[1], currentRoute));
+ ShellApplyParameters.ApplyParameters(new ShellLifecycleArgs(shellContent, pathParts[2], currentRoute));
if (CurrentItem != shellItem)
+ {
SetValueFromRenderer(CurrentItemProperty, shellItem);
+ shellItemChanged = true;
+ }
- parts.RemoveAt(0);
-
- if (parts.Count > 0)
- await shellItem.GoToPart(navigationRequest, queryData);
- }
- else
- {
- await CurrentItem.CurrentItem.GoToAsync(navigationRequest, queryData, animate);
- }
+ if (shellItem.CurrentItem != shellSection)
+ {
+ shellItem.SetValueFromRenderer(ShellItem.CurrentItemProperty, shellSection);
+ shellSectionChanged = true;
+ }
- //if (Routing.CompareWithRegisteredRoutes(shellItemRoute))
- //{
- // var shellItem = ShellItem.GetShellItemFromRouteName(shellItemRoute);
+ if (shellSection.CurrentItem != shellContent)
+ {
+ shellSection.SetValueFromRenderer(ShellSection.CurrentItemProperty, shellContent);
+ shellContentChanged = true;
+ }
- // ApplyQueryAttributes(shellItem, queryData, parts.Count == 1);
+ if (shellItemChanged)
+ await ShellPartAppearing.AppearingAsync(new ShellLifecycleArgs(shellItem, pathParts[0], currentRoute));
- // if (CurrentItem != shellItem)
- // SetValueFromRenderer(CurrentItemProperty, shellItem);
+ if (shellSectionChanged)
+ await ShellPartAppearing.AppearingAsync(new ShellLifecycleArgs(shellSection, pathParts[1], currentRoute));
- // if (parts.Count > 0)
- // await ((IShellItemController)shellItem).GoToPart(parts, queryData);
- //}
+ if (shellContentChanged)
+ await ShellPartAppearing.AppearingAsync(new ShellLifecycleArgs(shellContent, pathParts[2], currentRoute));
- _accumulateNavigatedEvents = false;
+ if (shellSectionChanged)
+ {
+ if (pathParts.Count > 3)
+ {
+ // 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, false);
+ });
+ }
+ }
+ else
+ {
+ await shellSection.GoToAsync(navigationRequest, animate);
+ }
+ }
+ finally
+ {
+ _accumulateNavigatedEvents = false;
+ }
// this can be null in the event that no navigation actually took place!
if (_accumulatedEvent != null)
OnNavigated(_accumulatedEvent);
+
+ return;
}
- internal static void ApplyQueryAttributes(Element element, IDictionary query, bool isLastItem)
+ // TODO cleanup duplication between here and GetNavigationParameters
+ internal static void ApplyQueryAttributes(Element element, IDictionary navigationParameters, bool isLastItem)
{
- if (query.Count == 0)
+ if (navigationParameters == null || navigationParameters.Count == 0)
return;
string prefix = "";
@@ -462,7 +471,44 @@ internal static void ApplyQueryAttributes(Element element, IDictionary GetNavigationParameters(Element element, string queryString, bool isLastItem)
+ {
+ var query = ParseQueryString(queryString);
+
+ if (query.Count == 0)
+ return new Dictionary();
+
+ string prefix = "";
+ if (!isLastItem)
+ {
+ var route = Routing.GetRoute(element);
+ if (string.IsNullOrEmpty(route) || route.StartsWith(Routing.ImplicitPrefix, StringComparison.Ordinal))
+ return new Dictionary();
+ prefix = route + ".";
+ }
+
+ //if the lastItem is implicitly wrapped, get the actual ShellContent
+ else if (isLastItem)
{
if (element is ShellItem shellitem && shellitem.Items.FirstOrDefault() is ShellSection section)
element = section;
@@ -487,10 +533,12 @@ internal static void ApplyQueryAttributes(Element element, IDictionary();
}
ShellNavigationState GetNavigationState(ShellItem shellItem, ShellSection shellSection, ShellContent shellContent, IReadOnlyList sectionStack)
@@ -578,7 +626,7 @@ public Shell()
{
Navigation = new NavigationImpl(this);
((INotifyCollectionChanged)Items).CollectionChanged += (s, e) => SendStructureChanged();
- Route = Routing.GenerateImplicitRoute("shell");
+ Route = ShellUriHandler.Route;
}
public event EventHandler Navigated;
@@ -668,9 +716,9 @@ internal string Route
set => Routing.SetRoute(this, value);
}
- internal string RouteHost { get; set; } = "shell";
+ internal string RouteHost => ShellUriHandler.RouteHost;
- internal string RouteScheme { get; set; } = "app";
+ internal string RouteScheme => ShellUriHandler.RouteScheme;
View FlyoutHeaderView
{
@@ -743,7 +791,7 @@ void IncrementGroup()
}
else
{
- if(!(shellSection.Parent is TabBar))
+ if (!(shellSection.Parent is TabBar))
currentGroup.Add(shellSection);
// If we have only a single child we will also show the items menu items
@@ -830,6 +878,7 @@ protected virtual void OnNavigated(ShellNavigatedEventArgs args)
}
ShellNavigationState _lastNavigating;
+
protected virtual void OnNavigating(ShellNavigatingEventArgs args)
{
Navigating?.Invoke(this, args);
@@ -1084,6 +1133,16 @@ void IPropertyPropagationController.PropagatePropertyChanged(string propertyName
PropertyPropagationExtensions.PropagatePropertyChanged(propertyName, this, new[] { FlyoutHeaderView });
}
+ #region Navigation Interfaces
+
+ IShellUriParser ShellUriParser => DependencyService.Get();
+ IShellApplyParameters ShellApplyParameters => DependencyService.Get();
+ IShellPartAppearing ShellPartAppearing => DependencyService.Get();
+ IShellPartAppeared ShellPartAppeared => DependencyService.Get();
+ IShellNavigationRequest ShellNavigationRequest => DependencyService.Get();
+
+ #endregion
+
class NavigationImpl : NavigationProxy
{
readonly Shell _shell;
diff --git a/Xamarin.Forms.Core/Shell/ShellContent.cs b/Xamarin.Forms.Core/Shell/ShellContent.cs
index 547767e1f4f..560e8563be5 100644
--- a/Xamarin.Forms.Core/Shell/ShellContent.cs
+++ b/Xamarin.Forms.Core/Shell/ShellContent.cs
@@ -50,17 +50,7 @@ Page IShellContentController.GetOrCreateContent()
var template = ContentTemplate;
var content = Content;
- Page result = null;
- if (template == null)
- {
- if (content is Page page)
- result = page;
- }
- else
- {
- result = ContentCache ?? (Page)template.CreateContent(content, this);
- ContentCache = result;
- }
+ Page result = ContentCache ?? ShellContentCreator.Create(new ShellContentCreateArgs(this));
if (result != null && result.Parent != this)
OnChildAdded(result);
@@ -74,6 +64,10 @@ Page IShellContentController.GetOrCreateContent()
return result;
}
+ #region Navigation Interfaces
+ IShellContentCreator ShellContentCreator => DependencyService.Get();
+ #endregion
+
void IShellContentController.RecyclePage(Page page)
{
}
diff --git a/Xamarin.Forms.Core/Shell/ShellItem.cs b/Xamarin.Forms.Core/Shell/ShellItem.cs
index e8f883b6eb2..36af4802be2 100644
--- a/Xamarin.Forms.Core/Shell/ShellItem.cs
+++ b/Xamarin.Forms.Core/Shell/ShellItem.cs
@@ -41,20 +41,20 @@ public class ShellItem : ShellGroupItem, IShellItemController, IElementConfigura
#region IShellItemController
- internal Task GoToPart(NavigationRequest request, Dictionary queryData)
- {
- var shellSection = request.Request.Section;
+ //internal Task GoToPart(NavigationRequest request, Dictionary queryData)
+ //{
+ // var shellSection = request.Request.Section;
- if (shellSection == null)
- return Task.FromResult(true);
+ // if (shellSection == null)
+ // return Task.FromResult(true);
- Shell.ApplyQueryAttributes(shellSection, queryData, request.Request.Content == null);
+ // Shell.ApplyQueryAttributes(shellSection, queryData, request.Request.Content == null);
- if (CurrentItem != shellSection)
- SetValueFromRenderer(CurrentItemProperty, shellSection);
+ // if (CurrentItem != shellSection)
+ // SetValueFromRenderer(CurrentItemProperty, shellSection);
- return shellSection.GoToPart(request, queryData);
- }
+ // return shellSection.GoToPart(request, queryData);
+ //}
bool IShellItemController.ProposeSection(ShellSection shellSection, bool setValue)
{
diff --git a/Xamarin.Forms.Core/Shell/ShellNavigationService.cs b/Xamarin.Forms.Core/Shell/ShellNavigationService.cs
new file mode 100644
index 00000000000..1c2ea1e60e4
--- /dev/null
+++ b/Xamarin.Forms.Core/Shell/ShellNavigationService.cs
@@ -0,0 +1,348 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Collections.ObjectModel;
+using System.Threading.Tasks;
+using Xamarin.Forms;
+using Xamarin.Forms.Internals;
+
+[assembly: Dependency(typeof(ShellNavigationService))]
+namespace Xamarin.Forms
+{
+ public class ShellNavigationService :
+ IShellUriParser,
+ IShellContentCreator,
+ IShellApplyParameters,
+ IShellPartAppearing,
+ IShellPartAppeared,
+ IShellNavigationRequest
+ {
+ public ShellNavigationService()
+ {
+ }
+
+ public virtual Task AppearedAsync(ShellLifecycleArgs args)
+ {
+ return Task.Delay(0);
+ }
+
+ public virtual Task AppearingAsync(ShellLifecycleArgs args)
+ {
+ return Task.Delay(0);
+ }
+
+ public virtual void ApplyParameters(ShellLifecycleArgs args)
+ {
+ Shell.ApplyQueryAttributes(args.Element, args.PathPart?.NavigationParameters ?? args.RoutePath?.NavigationParameters, args.IsLast);
+ }
+
+ public virtual Page Create(ShellContentCreateArgs args)
+ {
+ var shellContent = args.Content;
+ var template = shellContent.ContentTemplate;
+ var content = shellContent.Content;
+
+ Page result = null;
+ if (template == null)
+ {
+ if (content is Page page)
+ result = page;
+ else
+ result = (Page)Routing.GetOrCreateContent(args.Content.Route);
+ }
+ else
+ {
+ result = (Page)template.CreateContent(content, shellContent);
+ }
+
+ return result;
+
+ }
+ public virtual Task NavigatingToAsync(ShellNavigationArgs args)
+ {
+ return Task.FromResult(args.FutureState);
+ }
+
+ public virtual Task ParseAsync(ShellUriParserArgs args)
+ {
+ var navigationRequest = ShellUriHandler.GetNavigationRequest(args.Shell, args.Uri, false);
+ return Task.FromResult(navigationRequest);
+ }
+ }
+
+ public class PathPart
+ {
+ readonly string _path;
+
+ public PathPart(Element baseShellItem, Dictionary navigationParameters)
+ {
+ ShellPart = baseShellItem;
+
+ if (baseShellItem is BaseShellItem shellPart)
+ _path = shellPart.Route;
+ else
+ _path = Routing.GetRoute(baseShellItem);
+
+ NavigationParameters = navigationParameters;
+ }
+
+ public string Path => _path ?? Routing.GetRoute(ShellPart);
+ public Dictionary NavigationParameters { get; }
+
+ public Element ShellPart { get; }
+
+ //// This describes how you will transition to and away from this Path Part
+ //ITransitionPlan Transition { get; }
+
+ //// how am I presented? Modally? as a page?
+ //PresentationHint Presentation { get; }
+ }
+
+ // Do better at immutable stuff
+ public class RoutePath
+ {
+ public RoutePath(IList pathParts, Dictionary navigationParameters)
+ {
+ PathParts = new ReadOnlyCollection(pathParts);
+ NavigationParameters = navigationParameters;
+
+ StringBuilder builder = new StringBuilder();
+
+ for (var i = 0; i < pathParts.Count; i++)
+ {
+ var path = pathParts[i];
+ builder.Append(path.Path);
+ builder.Append("/");
+ }
+
+ FullUriWithImplicit = ShellUriHandler.ConvertToStandardFormat(new Uri(builder.ToString(), UriKind.Relative));
+ }
+
+ internal Uri FullUriWithImplicit { get; }
+ public Dictionary NavigationParameters { get; }
+ public IReadOnlyList PathParts { get; }
+ }
+
+ public class ShellRouteState
+ {
+ internal ShellRouteState() : this(new RoutePath(new List(), new Dictionary()))
+ {
+
+ }
+
+ internal ShellRouteState(Shell shell)
+ {
+ // TODO Shane wire up navigation parameters property on each base shell item
+ List pathParts = new List();
+ if (shell.CurrentItem != null)
+ pathParts.Add(new PathPart(shell.CurrentItem, null));
+
+ if (shell.CurrentItem?.CurrentItem != null)
+ pathParts.Add(new PathPart(shell.CurrentItem.CurrentItem, null));
+
+ if (shell.CurrentItem?.CurrentItem?.CurrentItem != null)
+ {
+ var shellSection = shell.CurrentItem.CurrentItem.CurrentItem;
+ pathParts.Add(new PathPart(shellSection, null));
+
+ foreach(var item in shellSection.Navigation.NavigationStack)
+ {
+ if (item == null)
+ continue;
+
+ if(item.Parent is ShellContent content)
+ pathParts.Add(new PathPart(content, null));
+ else
+ pathParts.Add(new PathPart(item, null));
+ }
+ }
+
+ CurrentRoute = new RoutePath(pathParts, new Dictionary());
+ Routes = new[] { CurrentRoute };
+ }
+
+
+ public ShellRouteState(RoutePath routePath)
+ {
+ CurrentRoute = routePath;
+ Routes = new[] { CurrentRoute };
+ }
+
+ ShellRouteState(RoutePath[] routePaths, RoutePath currentRoute)
+ {
+ Routes = routePaths;
+ CurrentRoute = currentRoute;
+ }
+
+ public RoutePath[] Routes { get; }
+ public RoutePath CurrentRoute { get; }
+
+ public ShellRouteState Add(PathPart pathPart)
+ {
+ List newPathPArts = new List(CurrentRoute.PathParts);
+ newPathPArts.Add(pathPart);
+
+ RoutePath[] newRoutes = new RoutePath[Routes.Length];
+ Array.Copy(Routes, newRoutes, Routes.Length);
+
+ RoutePath newCurrentRoute = null;
+ for (var i = 0; i < newRoutes.Length; i++)
+ {
+ var route = newRoutes[i];
+ if (route == CurrentRoute)
+ {
+ newCurrentRoute = new RoutePath(newPathPArts, route.NavigationParameters);
+ newRoutes[i] = newCurrentRoute;
+ }
+ }
+
+ return new ShellRouteState(newRoutes, newCurrentRoute);
+ }
+ public ShellRouteState Add(IList pathParts)
+ {
+ List newPathPArts = new List(CurrentRoute.PathParts);
+ newPathPArts.AddRange(pathParts);
+
+ RoutePath[] newRoutes = new RoutePath[Routes.Length];
+ Array.Copy(Routes, newRoutes, Routes.Length);
+
+ RoutePath newCurrentRoute = null;
+ for (var i = 0; i < newRoutes.Length; i++)
+ {
+ var route = newRoutes[i];
+ if (route == CurrentRoute)
+ {
+ newCurrentRoute = new RoutePath(newPathPArts, route.NavigationParameters);
+ newRoutes[i] = newCurrentRoute;
+ }
+ }
+
+ return new ShellRouteState(newRoutes, newCurrentRoute);
+ }
+ }
+
+ public interface IShellUriParser
+ {
+ // based on the current state and this uri what should the new state look like?
+ // the uri could completely demolish the current state and just return a new setup completely
+ Task ParseAsync(ShellUriParserArgs args);
+ }
+
+ public class ShellUriParserArgs : EventArgs
+ {
+ public ShellUriParserArgs(Shell shell, Uri uri)
+ {
+ Shell = shell;
+ Uri = uri;
+ }
+
+ public Shell Shell { get; }
+ public Uri Uri { get; }
+ }
+
+ public interface IShellNavigationRequest
+ {
+ // this will return the state change. If you want to cancel navigation then just return null or current state
+ Task NavigatingToAsync(ShellNavigationArgs args);
+ }
+
+ public interface IShellContentCreator
+ {
+ Page Create(ShellContentCreateArgs content);
+ }
+
+
+ public interface IShellApplyParameters
+ {
+ // this is where we will apply query parameters to the shell content
+ // this may be called multiple times. For example when the bindingcontext changes it will be called again
+ void ApplyParameters(ShellLifecycleArgs args);
+ }
+
+ public interface IShellPartAppearing
+ {
+ // this will get called for each piece that appears shellitem, shellsection, shellcontent
+ Task AppearingAsync(ShellLifecycleArgs args);
+ }
+
+ public interface IShellPartAppeared
+ {
+ // this will get called for each piece that appeared shellitem, shellsection, shellcontent
+ Task AppearedAsync(ShellLifecycleArgs args);
+ }
+
+ public interface IShellPartDisappeared
+ {
+ // this will get called for each piece that disappeared shellitem, shellsection, shellcontent
+ Task DisappearedAsync(ShellLifecycleArgs args);
+ }
+
+ public class ShellNavigationArgs : EventArgs
+ {
+ public ShellNavigationArgs(Shell shell, ShellRouteState futureState)
+ {
+ Shell = shell;
+ FutureState = futureState;
+ }
+
+ public Shell Shell { get; }
+ public ShellRouteState FutureState { get; }
+ }
+
+ public class ShellContentCreateArgs : EventArgs
+ {
+ public ShellContentCreateArgs(ShellContent content)
+ {
+ Content = content;
+ }
+
+ public ShellContent Content { get; }
+ }
+ public class ShellLifecycleArgs : EventArgs
+ {
+ public ShellLifecycleArgs(Element element, PathPart pathPart, RoutePath routePath)
+ {
+ Element = element;
+ PathPart = pathPart;
+ RoutePath = routePath;
+ }
+
+ public Element Element { get; }
+ public PathPart PathPart { get; }
+ public RoutePath RoutePath { get; }
+
+ public bool IsLast
+ {
+ get
+ {
+ if (RoutePath.PathParts[RoutePath.PathParts.Count - 1] == PathPart)
+ return true;
+
+ return false;
+ }
+ }
+ }
+
+
+
+
+ //// possible any interface
+ //public enum PresentationHint
+ //{
+ // Page,
+ // Modal,
+ // Dialog
+ //}
+
+ //interface ITransitionPlan
+ //{
+ // ITransition TransitionTo { get; }
+ // ITransition TransitionFrom { get; }
+ //}
+
+ //interface ITransition
+ //{
+
+ //}
+
+}
diff --git a/Xamarin.Forms.Core/Shell/ShellSection.cs b/Xamarin.Forms.Core/Shell/ShellSection.cs
index 7eed4b94e7f..c87ae54933e 100644
--- a/Xamarin.Forms.Core/Shell/ShellSection.cs
+++ b/Xamarin.Forms.Core/Shell/ShellSection.cs
@@ -67,30 +67,6 @@ void IShellSectionController.AddDisplayedPageObserver(object observer, Action 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);
@@ -227,49 +203,58 @@ public static implicit operator ShellSection(TemplatedPage page)
return (ShellSection)(ShellContent)page;
}
- internal async Task GoToAsync(NavigationRequest request, IDictionary queryData, bool animate)
+ internal async Task GoToAsync(ShellRouteState navigationRequest, bool animate)
{
- List routes = request.Request.GlobalRoutes;
- if (routes == null || routes.Count == 0)
+ var currentRoute = navigationRequest.CurrentRoute;
+ var pathParts = currentRoute.PathParts;
+ if (pathParts == null || pathParts.Count <= 3)
{
await Navigation.PopToRootAsync(animate);
return;
}
- for (int i = 0; i < routes.Count; i++)
+ int pageCount = 0;
+ for (int i = 3; i < pathParts.Count; i++, pageCount++)
{
- bool isLast = i == routes.Count - 1;
- var route = routes[i];
- var navPage = _navStack.Count > i + 1 ? _navStack[i + 1] : null;
+ bool isLast = i == pathParts.Count - 1;
+ var route = pathParts[i];
+ var navPage = _navStack.Count > pageCount + 1 ? _navStack[pageCount + 1] : null;
if (navPage != null)
{
- if (Routing.GetRoute(navPage) == route)
+ if (Routing.GetRoute(navPage) == route.Path)
{
- Shell.ApplyQueryAttributes(navPage, queryData, isLast);
+ ShellApplyParameters.ApplyParameters(new ShellLifecycleArgs(navPage, route, currentRoute));
+ //Shell.ApplyQueryAttributes(navPage, route.NavigationParameters, isLast);
continue;
}
-
- if (request.StackRequest == NavigationRequest.WhatToDoWithTheStack.ReplaceIt)
+ else
{
- while (_navStack.Count > i + 1)
+ while (_navStack.Count > pageCount + 1)
{
await OnPopAsync(false);
}
}
}
- var content = Routing.GetOrCreateContent(route) as Page;
+ Page content = null;
+
+ if (route.ShellPart is IShellContentController shellContent)
+ content = shellContent.GetOrCreateContent();
+ else
+ content = Routing.GetOrCreateContent(route.Path) as Page;
+
if (content == null)
break;
- Shell.ApplyQueryAttributes(content, queryData, isLast);
- await OnPushAsync(content, i == routes.Count - 1 && animate);
+ ShellApplyParameters.ApplyParameters(new ShellLifecycleArgs(content, route, currentRoute));
+ //Shell.ApplyQueryAttributes(content, route.NavigationParameters, isLast);
+ await OnPushAsync(content, i == pathParts.Count - 1 && animate);
}
-
+
SendAppearanceChanged();
}
-
+
internal void SendStructureChanged()
{
if (Parent?.Parent is Shell shell)
@@ -558,6 +543,11 @@ void SendUpdateCurrentState(ShellNavigationSource source)
}
}
+ #region Navigation Interfaces
+ IShellApplyParameters ShellApplyParameters => DependencyService.Get();
+ IShellContentCreator ShellContentCreator => DependencyService.Get();
+ #endregion
+
class NavigationImpl : NavigationProxy
{
readonly ShellSection _owner;
diff --git a/Xamarin.Forms.Core/Shell/ShellState.cs b/Xamarin.Forms.Core/Shell/ShellState.cs
new file mode 100644
index 00000000000..19db8ae18fc
--- /dev/null
+++ b/Xamarin.Forms.Core/Shell/ShellState.cs
@@ -0,0 +1,111 @@
+//using System.Collections.ObjectModel;
+
+//namespace Xamarin.Forms
+//{
+// public class ShellState
+// {
+// internal string Route => _state.Route;
+// readonly Shell _state;
+// public ShellState(Shell state)
+// {
+// _state = state;
+// ObservableCollection shellItems = new ObservableCollection();
+// foreach (var item in state.Items)
+// shellItems.Add(new ShellItemState(item));
+
+// Items = new ReadOnlyCollection(shellItems);
+// }
+// public ReadOnlyCollection Items { get; }
+
+// public ShellRouteState RouteState { get; }
+
+// public ShellItemState CurrentItem
+// {
+// get
+// {
+// foreach (var item in Items)
+// if (item.Item == _state.CurrentItem)
+// return item;
+
+// return null;
+// }
+// }
+
+// // TODO DELETE ME
+// public static implicit operator ShellState(Shell shell)
+// {
+// return new ShellState(shell);
+// }
+// }
+// public class ShellItemState
+// {
+// internal ShellItem Item { get; }
+// public ReadOnlyCollection Items { get; }
+
+// public ShellItemState(ShellItem item)
+// {
+// Item = item;
+// ObservableCollection shellItems = new ObservableCollection();
+// foreach (var section in Item.Items)
+// shellItems.Add(new ShellSectionState(section));
+
+// Items = new ReadOnlyCollection(shellItems);
+// }
+// public ShellSectionState CurrentItem
+// {
+// get
+// {
+// foreach (var item in Items)
+// if (item.Section == Item.CurrentItem)
+// return item;
+
+// return null;
+// }
+// }
+
+// public string Route => Item.Route;
+// }
+
+// public class ShellSectionState
+// {
+// internal ShellSection Section { get; }
+// public ReadOnlyCollection Items { get; }
+
+// public ShellSectionState(ShellSection section)
+// {
+// Section = section;
+// ObservableCollection shellItems = new ObservableCollection();
+// foreach (var content in Section.Items)
+// shellItems.Add(new ShellContentState(content));
+
+// Items = new ReadOnlyCollection(shellItems);
+// }
+// public ShellContentState CurrentItem
+// {
+// get
+// {
+// foreach (var item in Items)
+// if (item.Content == Section.CurrentItem)
+// return item;
+
+// return null;
+// }
+// }
+// public string Route => Section.Route;
+// }
+
+// public class ShellContentState
+// {
+// internal ShellContent Content { get; }
+// public ShellContentState(ShellContent content)
+// {
+// Content = content;
+// }
+// public string Route => Content.Route;
+// }
+
+// public interface IShellStateCreator
+// {
+// ShellState CreateShellState(Shell shell);
+// }
+//}
diff --git a/Xamarin.Forms.Core/Shell/ShellUriHandler.cs b/Xamarin.Forms.Core/Shell/ShellUriHandler.cs
index 285a3894537..b03061a275a 100644
--- a/Xamarin.Forms.Core/Shell/ShellUriHandler.cs
+++ b/Xamarin.Forms.Core/Shell/ShellUriHandler.cs
@@ -1,17 +1,21 @@
using System;
using System.Collections;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
namespace Xamarin.Forms
{
-
internal class ShellUriHandler
{
static readonly char[] _pathSeparators = { '/', '\\' };
const string _pathSeparator = "/";
+ static internal string Route = Routing.GenerateImplicitRoute("shell");
+ static internal string RouteHost => "shell";
+ static internal string RouteScheme => "app";
+
internal static Uri FormatUri(Uri path)
{
if (path.IsAbsoluteUri)
@@ -41,7 +45,7 @@ internal static Uri CreateUri(string path)
return new Uri(path, UriKind.Relative);
}
- public static Uri ConvertToStandardFormat(Shell shell, Uri request)
+ public static Uri ConvertToStandardFormat(Uri request)
{
request = FormatUri(request);
string pathAndQuery = null;
@@ -53,34 +57,23 @@ public static Uri ConvertToStandardFormat(Shell shell, Uri request)
var segments = new List(pathAndQuery.Split(_pathSeparators, StringSplitOptions.RemoveEmptyEntries));
- if (segments[0] != shell.RouteHost)
- segments.Insert(0, shell.RouteHost);
+ if (segments.Count > 0 && segments[0] != RouteHost)
+ segments.Insert(0, RouteHost);
- if (segments[1] != shell.Route)
- segments.Insert(1, shell.Route);
+ if (segments.Count > 1 && segments[1] != Route)
+ segments.Insert(1, Route);
var path = String.Join(_pathSeparator, segments.ToArray());
- string uri = $"{shell.RouteScheme}://{path}";
+ string uri = $"{RouteScheme}://{path}";
return new Uri(uri);
}
- internal static NavigationRequest GetNavigationRequest(Shell shell, Uri uri, bool enableRelativeShellRoutes = false)
+ internal static ShellRouteState GetNavigationRequest(Shell shell, Uri uri, bool enableRelativeShellRoutes = false)
{
uri = FormatUri(uri);
- // figure out the intent of the Uri
- NavigationRequest.WhatToDoWithTheStack whatDoIDo = NavigationRequest.WhatToDoWithTheStack.PushToIt;
- if (uri.IsAbsoluteUri)
- whatDoIDo = NavigationRequest.WhatToDoWithTheStack.ReplaceIt;
- else if (uri.OriginalString.StartsWith("//", StringComparison.Ordinal) || uri.OriginalString.StartsWith("\\\\", StringComparison.Ordinal))
- whatDoIDo = NavigationRequest.WhatToDoWithTheStack.ReplaceIt;
- else
- whatDoIDo = NavigationRequest.WhatToDoWithTheStack.PushToIt;
-
- Uri request = ConvertToStandardFormat(shell, uri);
-
- var possibleRouteMatches = GenerateRoutePaths(shell, request, uri, enableRelativeShellRoutes);
-
+ Uri request = ConvertToStandardFormat(uri);
+ List possibleRouteMatches = GenerateRoutePaths(shell, request, uri, enableRelativeShellRoutes);
if (possibleRouteMatches.Count == 0)
throw new ArgumentException($"unable to figure out route for: {uri}", nameof(uri));
@@ -100,17 +93,50 @@ internal static NavigationRequest GetNavigationRequest(Shell shell, Uri uri, boo
}
var theWinningRoute = possibleRouteMatches[0];
- RequestDefinition definition =
- new RequestDefinition(
- ConvertToStandardFormat(shell, CreateUri(theWinningRoute.PathFull)),
- theWinningRoute.Item,
- theWinningRoute.Section,
- theWinningRoute.Content,
- theWinningRoute.GlobalRouteMatches);
+ List pathParts = new List();
+ ShellRouteState returnValue = shell.RouteState ?? new ShellRouteState();
+
+ if (theWinningRoute.Item == null)
+ {
+ for (var i = 0; i < theWinningRoute.GlobalRouteMatches.Count; i++)
+ {
+ var item = new ShellContent()
+ {
+ Route = theWinningRoute.GlobalRouteMatches[i]
+ };
- NavigationRequest navigationRequest = new NavigationRequest(definition, whatDoIDo, request.Query, request.Fragment);
+ pathParts.Add(new PathPart(item, Shell.GetNavigationParameters(item, request.Query, theWinningRoute.GlobalRouteMatches.Count == (i + 1))));
+ }
- return navigationRequest;
+ returnValue = returnValue.Add(pathParts);
+ }
+ else
+ {
+ var shellItem = theWinningRoute.Item;
+ ShellSection shellSection = theWinningRoute.Section;
+
+ if (shellSection == null)
+ shellSection = shellItem.Items[0];
+
+ ShellContent shellContent = theWinningRoute.Content;
+ if (shellContent == null)
+ shellContent = shellSection.Items[0];
+
+ pathParts.Add(new PathPart(shellItem, Shell.GetNavigationParameters(shellItem, request.Query, false)));
+ pathParts.Add(new PathPart(shellSection, Shell.GetNavigationParameters(shellSection, request.Query, false)));
+
+ if(shellContent != null)
+ pathParts.Add(new PathPart(shellContent, Shell.GetNavigationParameters(shellContent, request.Query, theWinningRoute.GlobalRouteMatches.Count == 0)));
+
+ for (var i = 0; i < theWinningRoute.GlobalRouteMatches.Count; i++)
+ {
+ var item = new ShellContent() { Route = theWinningRoute.GlobalRouteMatches[i] };
+ pathParts.Add(new PathPart(item, Shell.GetNavigationParameters(item, request.Query, theWinningRoute.GlobalRouteMatches.Count == (i + 1))));
+ }
+ returnValue = new ShellRouteState(new RoutePath(pathParts, Shell.GetNavigationParameters(shell, request.Query, false)));
+ }
+
+ return returnValue;
}
internal static List GenerateRoutePaths(Shell shell, Uri request)
@@ -137,7 +163,7 @@ internal static List GenerateRoutePaths(Shell shell, Uri re
List possibleRoutePaths = new List();
if (!request.IsAbsoluteUri)
- request = ConvertToStandardFormat(shell, request);
+ request = ConvertToStandardFormat(request);
string localPath = request.LocalPath;
@@ -153,10 +179,10 @@ internal static List GenerateRoutePaths(Shell shell, Uri re
for (int i = 0; i < routeKeys.Length; i++)
{
var route = routeKeys[i];
- var uri = ConvertToStandardFormat(shell, CreateUri(route));
+ var uri = ConvertToStandardFormat(CreateUri(route));
if (uri.Equals(request))
{
- throw new Exception($"Global routes currently cannot be the only page on the stack, so absolute routing to global routes is not supported. For now, just navigate to: {originalRequest.OriginalString.Replace("//","")}");
+ throw new Exception($"Global routes currently cannot be the only page on the stack, so absolute routing to global routes is not supported. For now, just navigate to: {originalRequest.OriginalString.Replace("//", "")}");
//var builder = new RouteRequestBuilder(route, route, null, segments);
//return new List { builder };
}
@@ -175,7 +201,7 @@ internal static List GenerateRoutePaths(Shell shell, Uri re
depthStart = 0;
}
- if(relativeMatch && shell?.CurrentItem != null)
+ if (relativeMatch && shell?.CurrentItem != null)
{
// retrieve current location
var currentLocation = NodeLocation.Create(shell);
@@ -227,7 +253,7 @@ internal static List GenerateRoutePaths(Shell shell, Uri re
RouteRequestBuilder builder = null;
foreach (var segment in segments)
{
- if(routeKeys.Contains(segment))
+ if (routeKeys.Contains(segment))
{
if (builder == null)
builder = new RouteRequestBuilder(segment, segment, null, segments);
@@ -236,7 +262,7 @@ internal static List GenerateRoutePaths(Shell shell, Uri re
}
}
- if(builder != null && builder.IsFullMatch)
+ if (builder != null && builder.IsFullMatch)
return new List { builder };
}
else
@@ -321,9 +347,9 @@ public static NodeLocation Create(Shell shell)
{
NodeLocation location = new NodeLocation();
location.SetNode(
- (object)shell.CurrentItem?.CurrentItem?.CurrentItem ??
- (object)shell.CurrentItem?.CurrentItem ??
- (object)shell.CurrentItem ??
+ (object)shell.CurrentItem?.CurrentItem?.CurrentItem ??
+ (object)shell.CurrentItem?.CurrentItem ??
+ (object)shell.CurrentItem ??
(object)shell);
return location;
@@ -537,7 +563,7 @@ static IEnumerable GetItems(object node)
if (segments[0] == route)
{
- yield return new GlobalRouteItem(key, key);
+ yield return new GlobalRouteItem(key, key);
}
}
}
@@ -597,204 +623,53 @@ public bool IsFinished
}
}
- ///
- /// This attempts to locate the intended route trying to be navigated to
- ///
- internal class RouteRequestBuilder
- {
- readonly List _globalRouteMatches = new List();
- readonly List _matchedSegments = new List();
- readonly List _fullSegments = new List();
- readonly string[] _allSegments = null;
- readonly static string _uriSeparator = "/";
-
- public Shell Shell { get; private set; }
- public ShellItem Item { get; private set; }
- public ShellSection Section { get; private set; }
- public ShellContent Content { get; private set; }
- public object LowestChild =>
- (object)Content ?? (object)Section ?? (object)Item ?? (object)Shell;
-
- public RouteRequestBuilder(string shellSegment, string userSegment, object node, string[] allSegments)
- {
- _allSegments = allSegments;
- if (node != null)
- AddMatch(shellSegment, userSegment, node);
- else
- AddGlobalRoute(userSegment, shellSegment);
- }
- public RouteRequestBuilder(RouteRequestBuilder builder)
- {
- _allSegments = builder._allSegments;
- _matchedSegments.AddRange(builder._matchedSegments);
- _fullSegments.AddRange(builder._fullSegments);
- _globalRouteMatches.AddRange(builder._globalRouteMatches);
- Shell = builder.Shell;
- Item = builder.Item;
- Section = builder.Section;
- Content = builder.Content;
- }
-
- public void AddGlobalRoute(string routeName, string segment)
- {
- _globalRouteMatches.Add(routeName);
- _fullSegments.Add(segment);
- _matchedSegments.Add(segment);
- }
-
- public void AddMatch(string shellSegment, string userSegment, object node)
- {
- if (node == null)
- throw new ArgumentNullException(nameof(node));
-
- switch (node)
- {
- case ShellUriHandler.GlobalRouteItem globalRoute:
- if(globalRoute.IsFinished)
- _globalRouteMatches.Add(globalRoute.SourceRoute);
- break;
- case Shell shell:
- Shell = shell;
- break;
- case ShellItem item:
- Item = item;
- break;
- case ShellSection section:
- Section = section;
-
- if (Item == null)
- {
- Item = Section.Parent as ShellItem;
- _fullSegments.Add(Item.Route);
- }
-
- break;
- case ShellContent content:
- Content = content;
- if (Section == null)
- {
- Section = Content.Parent as ShellSection;
- _fullSegments.Add(Section.Route);
- }
-
- if (Item == null)
- {
- Item = Section.Parent as ShellItem;
- _fullSegments.Insert(0, Item.Route);
- }
-
- break;
-
- }
-
- // if shellSegment == userSegment it means the implicit route is part of the request
- if (!Routing.IsImplicit(shellSegment) || shellSegment == userSegment)
- _matchedSegments.Add(shellSegment);
-
- _fullSegments.Add(shellSegment);
- }
-
- public string NextSegment
- {
- get
- {
- var nextMatch = _matchedSegments.Count;
- if (nextMatch >= _allSegments.Length)
- return null;
-
- return _allSegments[nextMatch];
- }
- }
-
- public string RemainingPath
- {
- get
- {
- var nextMatch = _matchedSegments.Count;
- if (nextMatch >= _allSegments.Length)
- return null;
-
- return Routing.FormatRoute(String.Join(_uriSeparator, _allSegments.Skip(nextMatch)));
- }
- }
- public string[] RemainingSegments
- {
- get
- {
- var nextMatch = _matchedSegments.Count;
- if (nextMatch >= _allSegments.Length)
- return null;
-
- return _allSegments.Skip(nextMatch).ToArray();
- }
- }
-
- string MakeUriString(List segments)
- {
- if (segments[0].StartsWith(_uriSeparator, StringComparison.Ordinal) || segments[0].StartsWith("\\", StringComparison.Ordinal))
- return String.Join(_uriSeparator, segments);
-
- return $"//{String.Join(_uriSeparator, segments)}";
- }
-
- public string PathNoImplicit => MakeUriString(_matchedSegments);
- public string PathFull => MakeUriString(_fullSegments);
-
- public bool IsFullMatch => _matchedSegments.Count == _allSegments.Length;
- public List GlobalRouteMatches => _globalRouteMatches;
- public List SegmentsMatched => _matchedSegments;
-
- }
-
-
-
- [DebuggerDisplay("RequestDefinition = {Request}, StackRequest = {StackRequest}")]
- internal class NavigationRequest
- {
- public enum WhatToDoWithTheStack
- {
- ReplaceIt,
- PushToIt
- }
-
- public NavigationRequest(RequestDefinition definition, WhatToDoWithTheStack stackRequest, string query, string fragment)
- {
- StackRequest = stackRequest;
- Query = query;
- Fragment = fragment;
- Request = definition;
- }
-
- public WhatToDoWithTheStack StackRequest { get; }
- public string Query { get; }
- public string Fragment { get; }
- public RequestDefinition Request { get; }
- }
-
-
- [DebuggerDisplay("Full = {FullUri}, Short = {ShortUri}")]
- internal class RequestDefinition
- {
- public RequestDefinition(Uri fullUri, ShellItem item, ShellSection section, ShellContent content, List globalRoutes)
- {
- FullUri = fullUri;
- Item = item;
- Section = section;
- Content = content;
- GlobalRoutes = globalRoutes;
- }
-
- public RequestDefinition(string fullUri, ShellItem item, ShellSection section, ShellContent content, List globalRoutes) :
- this(new Uri(fullUri, UriKind.Absolute), item, section, content, globalRoutes)
- {
- }
-
- public Uri FullUri { get; }
- public ShellItem Item { get; }
- public ShellSection Section { get; }
- public ShellContent Content { get; }
- public List GlobalRoutes { get; }
- }
+ //[DebuggerDisplay("RequestDefinition = {Request}, StackRequest = {StackRequest}")]
+ //internal class NavigationRequest
+ //{
+ // public enum WhatToDoWithTheStack
+ // {
+ // ReplaceIt,
+ // PushToIt
+ // }
+
+ // public NavigationRequest(RequestDefinition definition, WhatToDoWithTheStack stackRequest, string query, string fragment)
+ // {
+ // StackRequest = stackRequest;
+ // Query = query;
+ // Fragment = fragment;
+ // Request = definition;
+ // }
+
+ // public WhatToDoWithTheStack StackRequest { get; }
+ // public string Query { get; }
+ // public string Fragment { get; }
+ // public RequestDefinition Request { get; }
+ //}
+
+
+ //[DebuggerDisplay("Full = {FullUri}, Short = {ShortUri}")]
+ //internal class RequestDefinition
+ //{
+ // public RequestDefinition(Uri fullUri, ShellItem item, ShellSection section, ShellContent content, List globalRoutes)
+ // {
+ // FullUri = fullUri;
+ // Item = item;
+ // Section = section;
+ // Content = content;
+ // GlobalRoutes = globalRoutes;
+ // }
+
+ // public RequestDefinition(string fullUri, ShellItem item, ShellSection section, ShellContent content, List globalRoutes) :
+ // this(new Uri(fullUri, UriKind.Absolute), item, section, content, globalRoutes)
+ // {
+ // }
+
+ // public Uri FullUri { get; }
+ // public ShellItem Item { get; }
+ // public ShellSection Section { get; }
+ // public ShellContent Content { get; }
+ // public List GlobalRoutes { get; }
+ //}
}