Calculate Nav Source and fix popping params (#12514)

* Calculate Nav Source and fix popping params

* Update Shell.cs

* - fix length check

* - poptoroot

* Update Shell.cs

* Update Shell.cs

* Update Shell.cs

* - fix inserting middle pages and a few events
This commit is contained in:
Shane Neuville 2020-10-23 10:55:45 -05:00 коммит произвёл GitHub
Родитель 65609dca81
Коммит 3b5b760f41
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
7 изменённых файлов: 479 добавлений и 125 удалений

Просмотреть файл

@ -172,6 +172,25 @@ namespace Xamarin.Forms.Core.UnitTests
Is.EqualTo($"//{itemRoute}/{Routing.GetRoute(page1)}"));
}
[Test]
public async Task InsertTwoPagesAtSeparatePoints()
{
Routing.RegisterRoute("pagefirstmiddle", typeof(ContentPage));
Routing.RegisterRoute("pagesecondmiddle", typeof(ContentPage));
Routing.RegisterRoute("last", typeof(ContentPage));
Routing.RegisterRoute("middle", typeof(ContentPage));
var shell = new TestShell(
CreateShellItem(shellItemRoute: "item")
);
await shell.GoToAsync("//item/middle/last");
await shell.GoToAsync("//item/pagefirstmiddle/middle/pagesecondmiddle/last");
Assert.That(shell.CurrentState.Location.ToString(),
Is.EqualTo("//item/pagefirstmiddle/middle/pagesecondmiddle/last"));
}
[Test]
public async Task NavigationPushAndPopBasic()
{
@ -367,6 +386,161 @@ namespace Xamarin.Forms.Core.UnitTests
Assert.AreEqual(3, tab.NavigationsFired.Count);
}
[Test]
public async Task PoppingSamePageSetsCorrectNavigationSource()
{
Routing.RegisterRoute("detailspage", typeof(ContentPage));
var shell = new TestShell(CreateShellItem(shellItemRoute:"item1"));
await shell.GoToAsync("detailspage/detailspage");
await shell.Navigation.PopAsync();
shell.TestNavigatingArgs(ShellNavigationSource.Pop,
"//item1/detailspage/detailspage", $"..");
shell.TestNavigatedArgs(ShellNavigationSource.Pop,
"//item1/detailspage/detailspage", $"//item1/detailspage");
}
[Test]
public async Task PoppingSetsCorrectNavigationSource()
{
var shell = new TestShell(CreateShellItem(shellContentRoute: "item1"));
shell.RegisterPage("page1");
shell.RegisterPage("page2");
await shell.GoToAsync("page1");
await shell.GoToAsync("page2");
await shell.Navigation.PopAsync();
shell.TestNavigatingArgs(ShellNavigationSource.Pop,
"//item1/page1/page2", $"..");
shell.TestNavigatedArgs(ShellNavigationSource.Pop,
"//item1/page1/page2", $"//item1/page1");
}
[Test]
public async Task PopToRootSetsCorrectNavigationSource()
{
var shell = new TestShell(CreateShellItem());
await shell.Navigation.PushAsync(new ContentPage());
await shell.Navigation.PushAsync(new ContentPage());
await shell.Navigation.PopToRootAsync();
Assert.AreEqual(ShellNavigationSource.PopToRoot, shell.LastShellNavigatingEventArgs.Source);
await shell.Navigation.PushAsync(new ContentPage());
await shell.Navigation.PushAsync(new ContentPage());
await shell.Navigation.PopAsync();
Assert.AreEqual(ShellNavigationSource.Pop, shell.LastShellNavigatingEventArgs.Source);
await shell.Navigation.PopAsync();
Assert.AreEqual(ShellNavigationSource.PopToRoot, shell.LastShellNavigatingEventArgs.Source);
}
[Test]
public async Task PushingSetsCorrectNavigationSource()
{
var shell = new TestShell(CreateShellItem(shellItemRoute: "item1"));
shell.RegisterPage(nameof(PushingSetsCorrectNavigationSource));
await shell.GoToAsync(nameof(PushingSetsCorrectNavigationSource));
shell.TestNavigatingArgs(ShellNavigationSource.Push,
"//item1", $"{nameof(PushingSetsCorrectNavigationSource)}");
shell.TestNavigatedArgs(ShellNavigationSource.Push,
"//item1", $"//item1/{nameof(PushingSetsCorrectNavigationSource)}");
}
[Test]
public async Task ChangingShellItemSetsCorrectNavigationSource()
{
var shell = new TestShell(
CreateShellItem(shellItemRoute: "item1"),
CreateShellItem(shellItemRoute: "item2")
);
await shell.GoToAsync("//item2");
shell.TestNavigationArgs(ShellNavigationSource.ShellItemChanged,
"//item1", "//item2");
}
[Test]
public async Task ChangingShellSectionSetsCorrectNavigationSource()
{
var shell = new TestShell(
CreateShellItem(shellSectionRoute:"item1")
);
shell.Items[0].Items.Add(CreateShellSection(shellContentRoute: "item2"));
await shell.GoToAsync("//item2");
shell.TestNavigationArgs(ShellNavigationSource.ShellSectionChanged,
"//item1", "//item2");
}
[Test]
public async Task ChangingShellContentSetsCorrectNavigationSource()
{
var shell = new TestShell(
CreateShellItem(shellContentRoute:"item1")
);
shell.Items[0].Items[0].Items.Add(CreateShellContent(shellContentRoute: "item2"));
await shell.GoToAsync("//item2");
shell.TestNavigationArgs(ShellNavigationSource.ShellContentChanged,
"//item1", "//item2");
}
[Test]
public async Task InsertPageSetsCorrectNavigationSource()
{
Routing.RegisterRoute("pagemiddle", typeof(ContentPage));
Routing.RegisterRoute("page", typeof(ContentPage));
var shell = new TestShell(
CreateShellItem(shellItemRoute: "item")
);
await shell.GoToAsync("//item/page");
await shell.GoToAsync("//item/pagemiddle/page");
shell.TestNavigationArgs(ShellNavigationSource.Insert,
"//item/page", "//item/pagemiddle/page");
}
[Test]
public async Task RemovePageSetsCorrectNavigationSource()
{
Routing.RegisterRoute("pagemiddle", typeof(ContentPage));
Routing.RegisterRoute("page", typeof(ContentPage));
var shell = new TestShell(
CreateShellItem(shellItemRoute: "item")
);
await shell.GoToAsync("//item/pagemiddle/page");
await shell.GoToAsync("//item/page");
shell.TestNavigationArgs(ShellNavigationSource.Remove,
"//item/pagemiddle/page", "//item/page");
}
[Test]
public async Task InitialNavigatingArgs()
{
var shell = new TestShell(
CreateShellItem(shellItemRoute: "item")
);
shell.TestNavigationArgs(ShellNavigationSource.ShellItemChanged,
null, "//item");
}
public class NavigationMonitoringTab : Tab
{
public List<string> NavigationsFired = new List<string>();

Просмотреть файл

@ -241,5 +241,65 @@ namespace Xamarin.Forms.Core.UnitTests
var testPage = (shell.CurrentItem.CurrentItem as IShellSectionController).PresentedPage as ShellTestPage;
Assert.AreEqual(1234d, testPage.DoubleQueryParameter);
}
[Test]
public async Task NavigatingBackDoesntClearParametersFromPreviousPage()
{
var shell = new TestShell(CreateShellItem());
Routing.RegisterRoute("details", typeof(ShellTestPage));
await shell.GoToAsync($"details?{nameof(ShellTestPage.SomeQueryParameter)}=1");
await shell.GoToAsync("details");
await shell.GoToAsync("..");
var testPage = shell.CurrentPage as ShellTestPage;
Assert.AreEqual("1", testPage.SomeQueryParameter);
}
[Test]
public async Task NavigatingBackAbsolutelyClearsParametersFromPreviousPage()
{
var shell = new TestShell(CreateShellItem(shellItemRoute:"item"));
Routing.RegisterRoute("details", typeof(ShellTestPage));
await shell.GoToAsync($"details?{nameof(ShellTestPage.SomeQueryParameter)}=1");
await shell.GoToAsync("details");
await shell.GoToAsync("//item/details");
var testPage = shell.CurrentPage as ShellTestPage;
Assert.AreEqual(null, testPage.SomeQueryParameter);
}
[Test]
public async Task NavigatingBackToShellContentRetainsQueryParameter()
{
var shell = new Shell();
ShellTestPage pagetoTest = new ShellTestPage();
pagetoTest.BindingContext = pagetoTest;
var one = CreateShellItem(pagetoTest, shellContentRoute: "content");
shell.Items.Add(one);
ShellTestPage page = (ShellTestPage)shell.CurrentPage;
await shell.GoToAsync($"//content?{nameof(ShellTestPage.SomeQueryParameter)}=1234");
await shell.Navigation.PushAsync(new ContentPage());
await shell.Navigation.PopAsync();
Assert.AreEqual("1234", page.SomeQueryParameter);
}
[Test]
public async Task NavigatingBackToShellContentAbsolutelyClearsQueryParameter()
{
var shell = new Shell();
ShellTestPage pagetoTest = new ShellTestPage();
pagetoTest.BindingContext = pagetoTest;
var one = CreateShellItem(pagetoTest, shellContentRoute: "content");
shell.Items.Add(one);
ShellTestPage page = (ShellTestPage)shell.CurrentPage;
await shell.GoToAsync($"//content?{nameof(ShellTestPage.SomeQueryParameter)}=1234");
await shell.Navigation.PushAsync(new ContentPage());
await shell.GoToAsync($"//content");
Assert.AreEqual(null, page.SomeQueryParameter);
}
}
}

Просмотреть файл

@ -352,6 +352,38 @@ namespace Xamarin.Forms.Core.UnitTests
OnNavigatingCount++;
}
public void TestNavigationArgs(ShellNavigationSource source, string from, string to)
{
TestNavigatingArgs(source, from, to);
TestNavigatedArgs(source, from, to);
}
public void TestNavigatedArgs(ShellNavigationSource source, string from, string to)
{
Assert.AreEqual(source, this.LastShellNavigatedEventArgs.Source);
if (from == null)
Assert.AreEqual(LastShellNavigatedEventArgs.Previous, null);
else
Assert.AreEqual(from, this.LastShellNavigatedEventArgs.Previous.Location.ToString());
Assert.AreEqual(to, this.LastShellNavigatedEventArgs.Current.Location.ToString());
}
public void TestNavigatingArgs(ShellNavigationSource source, string from, string to)
{
Assert.AreEqual(source, this.LastShellNavigatingEventArgs.Source);
if (from == null)
Assert.AreEqual(LastShellNavigatingEventArgs.Current, null);
else
Assert.AreEqual(from, this.LastShellNavigatingEventArgs.Current.Location.ToString());
Assert.AreEqual(to, this.LastShellNavigatingEventArgs.Target.Location.ToString());
}
public Func<bool> OnBackButtonPressedFunc;
protected override bool OnBackButtonPressed()
{

Просмотреть файл

@ -449,36 +449,6 @@ namespace Xamarin.Forms
public static Shell Current => Application.Current?.MainPage as Shell;
List<RequestDefinition> BuildAllTheRoutes()
{
List<RequestDefinition> routes = new List<RequestDefinition>();
// 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<string>()));
}
}
}
return routes;
}
internal Task CompleteDeferredNavigating(ShellNavigatingEventArgs deferredArgs)
{
return GoToAsync(deferredArgs.Target, deferredArgs.Animate, false, deferredArgs);
@ -520,8 +490,12 @@ namespace Xamarin.Forms
throw new InvalidOperationException("Not all ShellNavigatingDeferrals have been completed from the previous operation");
}
// FIXME: This should not be none, we need to compute the delta and set flags correctly
var accept = ProposeNavigation(ShellNavigationSource.Unknown, state, this.CurrentState != null, deferredArgs);
var navigationRequest = ShellUriHandler.GetNavigationRequest(this, state.FullLocation, enableRelativeShellRoutes, shellNavigationParameters: shellNavigationParameters);
bool isRelativePopping = ShellUriHandler.IsTargetRelativePop(shellNavigationParameters);
ShellNavigationSource source = CalculateNavigationSource(CurrentState, navigationRequest);
var accept = ProposeNavigation(source, state, this.CurrentState != null, deferredArgs);
if (deferredArgs == null && _deferredEventArgs != null)
{
@ -539,12 +513,11 @@ namespace Xamarin.Forms
_accumulateNavigatedEvents = true;
var navigationRequest = ShellUriHandler.GetNavigationRequest(this, state.FullLocation, enableRelativeShellRoutes, shellNavigationParameters: shellNavigationParameters);
var uri = navigationRequest.Request.FullUri;
var queryString = navigationRequest.Query;
var queryData = ParseQueryString(queryString);
ApplyQueryAttributes(this, queryData, false);
ApplyQueryAttributes(this, queryData, false, false);
var shellItem = navigationRequest.Request.Item;
var shellSection = navigationRequest.Request.Section;
@ -555,6 +528,8 @@ namespace Xamarin.Forms
ShellContent shellContent = navigationRequest.Request.Content;
bool modalStackPreBuilt = false;
// If we're replacing the whole stack and there are global routes then build the navigation stack before setting the shell section visible
if (navigationRequest.Request.GlobalRoutes.Count > 0 &&
nextActiveSection != null &&
@ -563,17 +538,17 @@ namespace Xamarin.Forms
modalStackPreBuilt = true;
bool? isAnimated = (nextActiveSection != currentShellSection) ? false : animate;
await nextActiveSection.GoToAsync(navigationRequest, queryData, isAnimated);
await nextActiveSection.GoToAsync(navigationRequest, queryData, isAnimated, isRelativePopping);
}
if (shellItem != null)
{
ApplyQueryAttributes(shellItem, queryData, navigationRequest.Request.Section == null);
ApplyQueryAttributes(shellItem, queryData, navigationRequest.Request.Section == null, false);
bool navigatedToNewShellElement = false;
if (shellSection != null && shellContent != null)
{
Shell.ApplyQueryAttributes(shellContent, queryData, navigationRequest.Request.GlobalRoutes.Count == 0);
ApplyQueryAttributes(shellContent, queryData, navigationRequest.Request.GlobalRoutes.Count == 0, isRelativePopping);
if (shellSection.CurrentItem != shellContent)
{
shellSection.SetValueFromRenderer(ShellSection.CurrentItemProperty, shellContent);
@ -583,7 +558,7 @@ namespace Xamarin.Forms
if (shellSection != null)
{
Shell.ApplyQueryAttributes(shellSection, queryData, navigationRequest.Request.Content == null);
Shell.ApplyQueryAttributes(shellSection, queryData, navigationRequest.Request.Content == null, false);
if (shellItem.CurrentItem != shellSection)
{
shellItem.SetValueFromRenderer(ShellItem.CurrentItemProperty, shellSection);
@ -620,7 +595,7 @@ namespace Xamarin.Forms
// TODO get rid of this hack and fix so if there's a stack the current page doesn't display
await Device.InvokeOnMainThreadAsync(() =>
{
return CurrentItem.CurrentItem.GoToAsync(navigationRequest, queryData, animate);
return CurrentItem.CurrentItem.GoToAsync(navigationRequest, queryData, animate, isRelativePopping);
});
}
else if (navigationRequest.Request.GlobalRoutes.Count == 0 &&
@ -630,13 +605,13 @@ namespace Xamarin.Forms
// TODO get rid of this hack and fix so if there's a stack the current page doesn't display
await Device.InvokeOnMainThreadAsync(() =>
{
return CurrentItem.CurrentItem.GoToAsync(navigationRequest, queryData, animate);
return CurrentItem.CurrentItem.GoToAsync(navigationRequest, queryData, animate, isRelativePopping);
});
}
}
else
{
await CurrentItem.CurrentItem.GoToAsync(navigationRequest, queryData, animate);
await CurrentItem.CurrentItem.GoToAsync(navigationRequest, queryData, animate, isRelativePopping);
}
_accumulateNavigatedEvents = false;
@ -646,7 +621,7 @@ namespace Xamarin.Forms
HandleNavigated(_accumulatedEvent);
}
internal static void ApplyQueryAttributes(Element element, IDictionary<string, string> query, bool isLastItem)
internal static void ApplyQueryAttributes(Element element, IDictionary<string, string> query, bool isLastItem, bool isPopping)
{
string prefix = "";
if (!isLastItem)
@ -673,6 +648,7 @@ namespace Xamarin.Forms
//filter the query to only apply the keys with matching prefix
var filteredQuery = new Dictionary<string, string>(query.Count);
foreach (var q in query)
{
if (!q.Key.StartsWith(prefix, StringComparison.Ordinal))
@ -683,10 +659,32 @@ namespace Xamarin.Forms
filteredQuery.Add(key, q.Value);
}
if (baseShellItem is ShellContent)
baseShellItem.ApplyQueryAttributes(filteredQuery);
baseShellItem.ApplyQueryAttributes(MergeData(element, filteredQuery, isPopping));
else if (isLastItem)
element.SetValue(ShellContent.QueryAttributesProperty, query);
element.SetValue(ShellContent.QueryAttributesProperty, MergeData(element, query, isPopping));
IDictionary<string,string> MergeData(Element shellElement, IDictionary<string, string> data, bool isPopping)
{
if (!isPopping)
return data;
var returnValue = new Dictionary<string, string>(data);
var existing = (IDictionary<string, string>)shellElement.GetValue(ShellContent.QueryAttributesProperty);
if (existing == null)
return data;
foreach(var datum in existing)
{
if (!returnValue.ContainsKey(datum.Key))
returnValue[datum.Key] = datum.Value;
}
return returnValue;
}
}
internal static ShellNavigationState GetNavigationState(ShellItem shellItem, ShellSection shellSection, ShellContent shellContent, IReadOnlyList<Page> sectionStack, IReadOnlyList<Page> modalStack)
@ -1286,9 +1284,13 @@ namespace Xamarin.Forms
internal void HandleNavigated(ShellNavigatedEventArgs args)
{
if (_accumulateNavigatedEvents)
_accumulatedEvent = args;
{
if(_accumulatedEvent == null)
_accumulatedEvent = args;
}
else
{
_accumulatedEvent = null;
BaseShellItem baseShellItem = CurrentItem?.CurrentItem?.CurrentItem;
if (baseShellItem != null)
@ -1653,6 +1655,79 @@ namespace Xamarin.Forms
return !navArgs.Cancelled && navArgs.DeferralCount == 0;
}
ShellNavigationSource CalculateNavigationSource(ShellNavigationState current, NavigationRequest request)
{
if (request.StackRequest == NavigationRequest.WhatToDoWithTheStack.PushToIt)
return ShellNavigationSource.Push;
if (current == null)
return ShellNavigationSource.ShellItemChanged;
var targetUri = ShellUriHandler.ConvertToStandardFormat(this, request.Request.FullUri);
var currentUri = ShellUriHandler.ConvertToStandardFormat(this, current.FullLocation);
var targetPaths = ShellUriHandler.RetrievePaths(targetUri.PathAndQuery);
var currentPaths = ShellUriHandler.RetrievePaths(currentUri.PathAndQuery);
var targetPathsLength = targetPaths.Length;
var currentPathsLength = currentPaths.Length;
if (targetPathsLength < 4 || currentPathsLength < 4)
return ShellNavigationSource.Unknown;
if (targetPaths[1] != currentPaths[1])
return ShellNavigationSource.ShellItemChanged;
if (targetPaths[2] != currentPaths[2])
return ShellNavigationSource.ShellSectionChanged;
if (targetPaths[3] != currentPaths[3])
return ShellNavigationSource.ShellContentChanged;
if (targetPathsLength == currentPathsLength)
return ShellNavigationSource.Unknown;
if(targetPathsLength < currentPathsLength)
{
for (var i = 0; i < targetPathsLength; i++)
{
var targetPath = targetPaths[i];
if (targetPath != currentPaths[i])
break;
if (i == targetPathsLength - 1)
{
if (targetPathsLength == 4)
return ShellNavigationSource.PopToRoot;
return ShellNavigationSource.Pop;
}
}
if (targetPaths[targetPathsLength - 1] == currentPaths[currentPathsLength - 1])
return ShellNavigationSource.Remove;
if (targetPathsLength == 4)
return ShellNavigationSource.PopToRoot;
return ShellNavigationSource.Pop;
}
else if(targetPathsLength > currentPathsLength)
{
for (var i = 0; i < currentPathsLength; i++)
{
if (targetPaths[i] != currentPaths[i])
break;
if (i == targetPathsLength - 1)
return ShellNavigationSource.Push;
}
}
if (targetPaths[targetPathsLength - 1] == currentPaths[currentPathsLength - 1])
return ShellNavigationSource.Insert;
return ShellNavigationSource.Push;
}
internal Element GetVisiblePage()
{
if (CurrentItem?.CurrentItem is IShellSectionController scc)

Просмотреть файл

@ -61,21 +61,6 @@ namespace Xamarin.Forms
IShellItemController ShellItemController => this;
internal Task GoToPart(NavigationRequest request, Dictionary<string, string> queryData)
{
var shellSection = request.Request.Section;
if (shellSection == null)
shellSection = ShellItemController.GetItems()[0];
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)
{
var controller = (IShellController)Parent;

Просмотреть файл

@ -76,30 +76,6 @@ namespace Xamarin.Forms
callback(DisplayedPage);
}
internal Task GoToPart(NavigationRequest request, Dictionary<string, string> queryData)
{
ShellContent shellContent = request.Request.Content;
if (shellContent == null)
shellContent = ShellSectionController.GetItems()[0];
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);
@ -332,7 +308,7 @@ namespace Xamarin.Forms
return (ShellSection)(ShellContent)page;
}
async Task PrepareCurrentStackForBeingReplaced(NavigationRequest request, IDictionary<string, string> queryData, bool? animate, List<string> globalRoutes)
async Task PrepareCurrentStackForBeingReplaced(NavigationRequest request, IDictionary<string, string> queryData, bool? animate, List<string> globalRoutes, bool isRelativePopping)
{
string route = "";
List<Page> navStack = null;
@ -368,6 +344,7 @@ namespace Xamarin.Forms
List<Page> pagesToInsert = new List<Page>();
for (int i = 0; i < globalRoutes.Count; i++)
{
bool isLast = i == globalRoutes.Count - 1;
int navIndex = i + 1;
// Routes match so don't do anything
if (navIndex < _navStack.Count && Routing.GetRoute(_navStack[navIndex]) == globalRoutes[i])
@ -375,12 +352,16 @@ namespace Xamarin.Forms
continue;
}
var page = GetOrCreateFromRoute(globalRoutes[i], queryData, i == globalRoutes.Count - 1);
var page = GetOrCreateFromRoute(globalRoutes[i], queryData, i == globalRoutes.Count - 1, false);
if (IsModal(page))
{
await Navigation.PushModalAsync(page, IsNavigationAnimated(page));
break;
}
else if(!isLast && navIndex < _navStack.Count)
{
Navigation.InsertPageBefore(page, _navStack[navIndex]);
}
else
{
pagesToInsert.Add(page);
@ -414,7 +395,7 @@ namespace Xamarin.Forms
// if the routes do match and this is the last in the loop
// pop everything after this route
popCount = i + 2;
Shell.ApplyQueryAttributes(navPage, queryData, isLast);
Shell.ApplyQueryAttributes(navPage, queryData, isLast, isRelativePopping);
// If we're not on the last loop of the stack then continue
// otherwise pop the rest of the stack
@ -507,7 +488,7 @@ namespace Xamarin.Forms
return startingList;
}
Page GetOrCreateFromRoute(string route, IDictionary<string, string> queryData, bool isLast)
Page GetOrCreateFromRoute(string route, IDictionary<string, string> queryData, bool isLast, bool isPopping)
{
var content = Routing.GetOrCreateContent(route) as Page;
if (content == null)
@ -515,15 +496,13 @@ namespace Xamarin.Forms
Internals.Log.Warning(nameof(Shell), $"Failed to Create Content For: {route}");
}
Shell.ApplyQueryAttributes(content, queryData, isLast);
Shell.ApplyQueryAttributes(content, queryData, isLast, isPopping);
return content;
}
internal async Task GoToAsync(NavigationRequest request, IDictionary<string, string> queryData, bool? animate)
internal async Task GoToAsync(NavigationRequest request, IDictionary<string, string> queryData, bool? animate, bool isRelativePopping)
{
List<string> globalRoutes = request.Request.GlobalRoutes;
string route = String.Empty;
if (globalRoutes == null || globalRoutes.Count == 0)
{
if (_navStack.Count == 2)
@ -534,7 +513,7 @@ namespace Xamarin.Forms
return;
}
await PrepareCurrentStackForBeingReplaced(request, queryData, animate, globalRoutes);
await PrepareCurrentStackForBeingReplaced(request, queryData, animate, globalRoutes, isRelativePopping);
List<Page> modalPageStacks = new List<Page>();
List<Page> nonModalPageStacks = new List<Page>();
@ -552,8 +531,7 @@ namespace Xamarin.Forms
for (int i = whereToStartNavigation; i < globalRoutes.Count; i++)
{
bool isLast = i == globalRoutes.Count - 1;
route = globalRoutes[i];
var content = GetOrCreateFromRoute(route, queryData, isLast);
var content = GetOrCreateFromRoute(globalRoutes[i], queryData, isLast, false);
if (content == null)
{
break;
@ -999,7 +977,7 @@ namespace Xamarin.Forms
if (shellSection.Parent?.Parent is IShellController shell && shellSection.IsVisibleSection)
{
shell.UpdateCurrentState(ShellNavigationSource.ShellSectionChanged);
shell.UpdateCurrentState(ShellNavigationSource.ShellContentChanged);
}
shellSection.SendStructureChanged();

Просмотреть файл

@ -3,6 +3,7 @@ using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using IOPath = System.IO.Path;
namespace Xamarin.Forms
@ -51,6 +52,33 @@ namespace Xamarin.Forms
return new Uri(path, UriKind.Relative);
}
public static bool IsTargetRelativePop(ShellNavigationParameters request)
{
if (request?.TargetState?.Location?.OriginalString == null)
return false;
bool isRelativePopping = false;
// If the user is popping with PopAsync or ".."
// we need to know this so we don't clear the query parameters off
// the destination location
var dest = request.TargetState?.Location?.OriginalString;
foreach (var path in RetrievePaths(dest))
{
if (path != "..")
{
isRelativePopping = false;
break;
}
else
isRelativePopping = true;
}
return isRelativePopping;
}
public static Uri ConvertToStandardFormat(Shell shell, Uri request)
{
request = FormatUri(request, shell);
@ -69,7 +97,7 @@ namespace Xamarin.Forms
if (pathAndQuery.Length > 1)
query = $"?{pathAndQuery[1]}";
var segments = new List<string>(pathAndQuery[0].Split(_pathSeparators, StringSplitOptions.RemoveEmptyEntries));
var segments = new List<string>(RetrievePaths(pathAndQuery[0]));
if (segments[0] != routeHost)
segments.Insert(0, routeHost);
@ -83,18 +111,25 @@ namespace Xamarin.Forms
return new Uri(uri);
}
static internal string[] RetrievePaths(string uri) => uri.Split(_pathSeparators, StringSplitOptions.RemoveEmptyEntries);
static internal NavigationRequest.WhatToDoWithTheStack CalculateStackRequest(Uri uri)
{
if (uri.IsAbsoluteUri)
return NavigationRequest.WhatToDoWithTheStack.ReplaceIt;
else if (uri.OriginalString.StartsWith("//", StringComparison.Ordinal) || uri.OriginalString.StartsWith("\\\\", StringComparison.Ordinal))
return NavigationRequest.WhatToDoWithTheStack.ReplaceIt;
return NavigationRequest.WhatToDoWithTheStack.PushToIt;
}
internal static NavigationRequest GetNavigationRequest(Shell shell, Uri uri, bool enableRelativeShellRoutes = false, bool throwNavigationErrorAsException = true, ShellNavigationParameters shellNavigationParameters = null)
{
uri = FormatUri(uri, shell);
// 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;
NavigationRequest.WhatToDoWithTheStack whatDoIDo = CalculateStackRequest(uri);
Uri request = ConvertToStandardFormat(shell, uri);
@ -128,13 +163,9 @@ namespace Xamarin.Forms
}
var theWinningRoute = possibleRouteMatches[0];
RequestDefinition definition =
new RequestDefinition(
ConvertToStandardFormat(shell, CreateUri(theWinningRoute.PathFull)),
theWinningRoute.Item,
theWinningRoute.Section,
theWinningRoute.Content,
theWinningRoute.GlobalRouteMatches);
new RequestDefinition(theWinningRoute, shell);
NavigationRequest navigationRequest = new NavigationRequest(definition, whatDoIDo, request.Query, request.Fragment);
@ -174,7 +205,7 @@ namespace Xamarin.Forms
!originalRequest.OriginalString.StartsWith("//", StringComparison.Ordinal))
relativeMatch = true;
var segments = localPath.Split(_pathSeparator.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
var segments = RetrievePaths(localPath);
if (!relativeMatch)
{
@ -566,7 +597,7 @@ namespace Xamarin.Forms
if (key.StartsWith(_pathSeparator, StringComparison.Ordinal) && !(node is Shell))
continue;
var segments = key.Split(_pathSeparator.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
var segments = RetrievePaths(key);
if (segments[0] == route)
{
@ -589,7 +620,7 @@ namespace Xamarin.Forms
{
get
{
var segments = _path.Split(_pathSeparator.ToCharArray(), StringSplitOptions.RemoveEmptyEntries).ToList().Skip(1).ToList();
var segments = RetrievePaths(_path).ToList().Skip(1).ToList();
if (segments.Count == 0)
return new object[0];
@ -604,7 +635,7 @@ namespace Xamarin.Forms
{
get
{
var segments = _path.Split(_pathSeparator.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
var segments = RetrievePaths(_path);
if (segments.Length == 0)
return string.Empty;
@ -617,7 +648,7 @@ namespace Xamarin.Forms
{
get
{
var segments = _path.Split(_pathSeparator.ToCharArray(), StringSplitOptions.RemoveEmptyEntries).ToList().Skip(1).ToList();
var segments = RetrievePaths(_path).ToList().Skip(1).ToList();
if (segments.Count == 0)
return true;
@ -717,7 +748,6 @@ namespace Xamarin.Forms
}
break;
}
// if shellSegment == userSegment it means the implicit route is part of the request
@ -750,6 +780,7 @@ namespace Xamarin.Forms
return Routing.FormatRoute(String.Join(_uriSeparator, _allSegments.Skip(nextMatch)));
}
}
public string[] RemainingSegments
{
get
@ -776,7 +807,6 @@ namespace Xamarin.Forms
public bool IsFullMatch => _matchedSegments.Count == _allSegments.Length;
public List<string> GlobalRouteMatches => _globalRouteMatches;
public List<string> SegmentsMatched => _matchedSegments;
}
@ -808,18 +838,38 @@ namespace Xamarin.Forms
[DebuggerDisplay("Full = {FullUri}, Short = {ShortUri}")]
internal class RequestDefinition
{
public RequestDefinition(Uri fullUri, ShellItem item, ShellSection section, ShellContent content, List<string> globalRoutes)
public RequestDefinition(RouteRequestBuilder theWinningRoute, Shell shell)
{
FullUri = fullUri;
Item = item;
Section = section;
Content = content;
GlobalRoutes = globalRoutes;
Item = theWinningRoute.Item;
Section = theWinningRoute.Section ?? Item?.CurrentItem;
Content = theWinningRoute.Content ?? Section?.CurrentItem;
GlobalRoutes = theWinningRoute.GlobalRouteMatches;
List<String> builder = new List<string>();
if (Item?.Route != null)
builder.Add(Item.Route);
if (Section?.Route != null)
builder.Add(Section?.Route);
if (Content?.Route != null)
builder.Add(Content?.Route);
if (GlobalRoutes != null)
builder.AddRange(GlobalRoutes);
var uriPath = MakeUriString(builder);
var uri = ShellUriHandler.CreateUri(uriPath);
FullUri = ShellUriHandler.ConvertToStandardFormat(shell, uri);
}
public RequestDefinition(string fullUri, ShellItem item, ShellSection section, ShellContent content, List<string> globalRoutes) :
this(new Uri(fullUri, UriKind.Absolute), item, section, content, globalRoutes)
string MakeUriString(List<string> segments)
{
if (segments[0].StartsWith("/", StringComparison.Ordinal) || segments[0].StartsWith("\\", StringComparison.Ordinal))
return String.Join("/", segments);
return $"//{String.Join("/", segments)}";
}
public Uri FullUri { get; }