Fix navigating/navigated events and add ".." navigation (#10048)
This commit is contained in:
Родитель
925db31c22
Коммит
9e32dcadb0
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
|
@ -484,6 +485,59 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
Assert.False(pageNotAppearingFired, "Incorrect Page Appearing Fired");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OnNavigatedCalledOnce()
|
||||
{
|
||||
List<ShellNavigatedEventArgs> args = new List<ShellNavigatedEventArgs>();
|
||||
Action<ShellNavigatedEventArgs> onNavigated = (a) =>
|
||||
{
|
||||
args.Add(a);
|
||||
};
|
||||
|
||||
TestShell testShell = new TestShell()
|
||||
{
|
||||
OnNavigatedHandler = onNavigated
|
||||
};
|
||||
|
||||
testShell.Items.Add(base.CreateShellItem());
|
||||
|
||||
Assert.AreEqual(1, args.Count);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public async Task OnNavigatedFiresWhenPopping()
|
||||
{
|
||||
Routing.RegisterRoute("AlarmPage", typeof(LifeCyclePage));
|
||||
Routing.RegisterRoute("SoundsPage", typeof(LifeCyclePage));
|
||||
TestShell shell = new TestShell();
|
||||
|
||||
var item = CreateShellItem(shellContentRoute: ContentRoute, shellSectionRoute: SectionRoute, shellItemRoute: ItemRoute);
|
||||
shell.Items.Add(item);
|
||||
|
||||
await shell.GoToAsync("AlarmPage/SoundsPage");
|
||||
shell.Reset();
|
||||
|
||||
await shell.Navigation.PopAsync();
|
||||
shell.TestCount(1);
|
||||
}
|
||||
|
||||
public async Task OnNavigatedFiresWhenPopToRoot()
|
||||
{
|
||||
Routing.RegisterRoute("AlarmPage", typeof(LifeCyclePage));
|
||||
Routing.RegisterRoute("SoundsPage", typeof(LifeCyclePage));
|
||||
TestShell shell = new TestShell();
|
||||
|
||||
var item = CreateShellItem(shellContentRoute: ContentRoute, shellSectionRoute: SectionRoute, shellItemRoute: ItemRoute);
|
||||
shell.Items.Add(item);
|
||||
|
||||
await shell.GoToAsync("AlarmPage/SoundsPage");
|
||||
shell.Reset();
|
||||
|
||||
await shell.Navigation.PopToRootAsync();
|
||||
shell.TestCount(1);
|
||||
}
|
||||
|
||||
public class LifeCyclePage : ContentPage
|
||||
{
|
||||
public bool Appearing;
|
||||
|
|
|
@ -297,6 +297,35 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
Assert.AreEqual("1234", testPage.SomeQueryParameter);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task NavigatingAndNavigatedFiresForShellModal()
|
||||
{
|
||||
Shell shell = new Shell();
|
||||
shell.Items.Add(CreateShellItem(shellItemRoute: "NewRoute", shellSectionRoute: "Section", shellContentRoute: "Content"));
|
||||
|
||||
ShellNavigatingEventArgs shellNavigatingEventArgs = null;
|
||||
ShellNavigatedEventArgs shellNavigatedEventArgs = null;
|
||||
|
||||
shell.Navigating += (_, args) =>
|
||||
{
|
||||
shellNavigatingEventArgs = args;
|
||||
};
|
||||
|
||||
shell.Navigated += (_, args) =>
|
||||
{
|
||||
shellNavigatedEventArgs = args;
|
||||
};
|
||||
|
||||
await shell.GoToAsync("ModalTestPage");
|
||||
|
||||
Assert.IsNotNull(shellNavigatingEventArgs, "Shell.Navigating never fired");
|
||||
Assert.IsNotNull(shellNavigatedEventArgs, "Shell.Navigated never fired");
|
||||
|
||||
Assert.AreEqual("//NewRoute/Section/Content/", shellNavigatingEventArgs.Current.FullLocation.ToString());
|
||||
Assert.AreEqual("//NewRoute/Section/Content/ModalTestPage", shellNavigatedEventArgs.Current.FullLocation.ToString());
|
||||
|
||||
}
|
||||
|
||||
|
||||
[QueryProperty("SomeQueryParameter", "SomeQueryParameter")]
|
||||
public class ModalTestPageBase : ShellLifeCycleTests.LifeCyclePage
|
||||
|
|
|
@ -78,7 +78,7 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
string shellSectionRoute = null,
|
||||
string shellItemRoute = null,
|
||||
bool templated = false)
|
||||
{
|
||||
{
|
||||
ShellItem item = null;
|
||||
var section = CreateShellSection(page, asImplicit, shellContentRoute, shellSectionRoute, templated: templated);
|
||||
|
||||
|
@ -169,5 +169,45 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
return (item as IShellController).GetItems();
|
||||
}
|
||||
|
||||
public class TestShell : Shell
|
||||
{
|
||||
public int OnNavigatedCount;
|
||||
public int OnNavigatingCount;
|
||||
public int NavigatedCount;
|
||||
public int NavigatingCount;
|
||||
|
||||
public TestShell()
|
||||
{
|
||||
this.Navigated += (_, __) => NavigatedCount++;
|
||||
this.Navigating += (_, __) => NavigatingCount++;
|
||||
}
|
||||
|
||||
public Action<ShellNavigatedEventArgs> OnNavigatedHandler { get; set; }
|
||||
protected override void OnNavigated(ShellNavigatedEventArgs args)
|
||||
{
|
||||
base.OnNavigated(args);
|
||||
OnNavigatedHandler?.Invoke(args);
|
||||
OnNavigatedCount++;
|
||||
}
|
||||
|
||||
protected override void OnNavigating(ShellNavigatingEventArgs args)
|
||||
{
|
||||
base.OnNavigating(args);
|
||||
OnNavigatingCount++;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
OnNavigatedCount = OnNavigatingCount = NavigatedCount = NavigatingCount = 0;
|
||||
}
|
||||
|
||||
public void TestCount(int count, string message = null)
|
||||
{
|
||||
Assert.AreEqual(count, OnNavigatedCount, $"OnNavigatedCount: {message}");
|
||||
Assert.AreEqual(count, NavigatingCount, $"NavigatingCount: {message}");
|
||||
Assert.AreEqual(count, OnNavigatingCount, $"OnNavigatingCount: {message}");
|
||||
Assert.AreEqual(count, NavigatedCount, $"NavigatedCount: {message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -247,6 +247,9 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
[Test]
|
||||
public async Task RelativeGoTo()
|
||||
{
|
||||
Routing.RegisterRoute("RelativeGoTo_Page1", typeof(ContentPage));
|
||||
Routing.RegisterRoute("RelativeGoTo_Page2", typeof(ContentPage));
|
||||
|
||||
var shell = new Shell
|
||||
{
|
||||
};
|
||||
|
@ -281,6 +284,15 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
await shell.GoToAsync("/tab23", false, true);
|
||||
Assert.That(shell.CurrentState.Location.ToString(), Is.EqualTo("//two/tab23/content"));
|
||||
|
||||
await shell.GoToAsync("RelativeGoTo_Page1", false);
|
||||
Assert.That(shell.CurrentState.Location.ToString(), Is.EqualTo("//two/tab23/content/RelativeGoTo_Page1"));
|
||||
|
||||
await shell.GoToAsync("../RelativeGoTo_Page2", false);
|
||||
Assert.That(shell.CurrentState.Location.ToString(), Is.EqualTo("//two/tab23/content/RelativeGoTo_Page2"));
|
||||
|
||||
await shell.GoToAsync("..", false);
|
||||
Assert.That(shell.CurrentState.Location.ToString(), Is.EqualTo("//two/tab23/content"));
|
||||
|
||||
/*
|
||||
* removing support for .. notation for now
|
||||
await shell.GoToAsync("../one/tab11");
|
||||
|
|
|
@ -394,7 +394,25 @@ namespace Xamarin.Forms
|
|||
}
|
||||
}
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
|
||||
internal void OnAppearing(Action action)
|
||||
{
|
||||
if (_hasAppeared)
|
||||
action();
|
||||
else
|
||||
{
|
||||
EventHandler eventHandler = null;
|
||||
eventHandler = (_, __) =>
|
||||
{
|
||||
this.Appearing -= eventHandler;
|
||||
action();
|
||||
};
|
||||
|
||||
this.Appearing += eventHandler;
|
||||
}
|
||||
}
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public void SendAppearing()
|
||||
{
|
||||
if (_hasAppeared)
|
||||
|
|
|
@ -85,7 +85,7 @@ namespace Xamarin.Forms
|
|||
|
||||
internal static Uri RemoveImplicit(Uri uri)
|
||||
{
|
||||
uri = ShellUriHandler.FormatUri(uri);
|
||||
uri = ShellUriHandler.FormatUri(uri, null);
|
||||
|
||||
string[] parts = uri.OriginalString.TrimEnd(_pathSeparator[0]).Split(_pathSeparator[0]);
|
||||
|
||||
|
|
|
@ -146,6 +146,21 @@ namespace Xamarin.Forms
|
|||
action();
|
||||
else
|
||||
{
|
||||
if(Navigation.ModalStack.Count > 0)
|
||||
{
|
||||
Navigation.ModalStack[Navigation.ModalStack.Count - 1]
|
||||
.OnAppearing(action);
|
||||
|
||||
return;
|
||||
}
|
||||
else if(Navigation.NavigationStack.Count > 1)
|
||||
{
|
||||
Navigation.NavigationStack[Navigation.NavigationStack.Count - 1]
|
||||
.OnAppearing(action);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
EventHandler eventHandler = null;
|
||||
eventHandler = (_, __) =>
|
||||
{
|
||||
|
|
|
@ -376,7 +376,7 @@ namespace Xamarin.Forms
|
|||
|
||||
SetValueFromRenderer(CurrentStatePropertyKey, result);
|
||||
|
||||
OnNavigated(new ShellNavigatedEventArgs(oldState, CurrentState, source));
|
||||
ProcessNavigated(new ShellNavigatedEventArgs(oldState, CurrentState, source));
|
||||
}
|
||||
ReadOnlyCollection<ShellItem> IShellController.GetItems() => ((ShellItemCollection)Items).VisibleItems;
|
||||
|
||||
|
@ -503,9 +503,19 @@ namespace Xamarin.Forms
|
|||
if (navigationRequest.Request.GlobalRoutes.Count > 0 && navigationRequest.StackRequest != NavigationRequest.WhatToDoWithTheStack.ReplaceIt)
|
||||
{
|
||||
// TODO get rid of this hack and fix so if there's a stack the current page doesn't display
|
||||
Device.BeginInvokeOnMainThread(async () =>
|
||||
await Device.InvokeOnMainThreadAsync(() =>
|
||||
{
|
||||
await CurrentItem.CurrentItem.GoToAsync(navigationRequest, queryData, animate);
|
||||
return CurrentItem.CurrentItem.GoToAsync(navigationRequest, queryData, animate);
|
||||
});
|
||||
}
|
||||
else if(navigationRequest.Request.GlobalRoutes.Count == 0 &&
|
||||
navigationRequest.StackRequest == NavigationRequest.WhatToDoWithTheStack.ReplaceIt &&
|
||||
currentShellSection?.Navigation?.NavigationStack?.Count > 1)
|
||||
{
|
||||
// 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -518,7 +528,7 @@ namespace Xamarin.Forms
|
|||
|
||||
// this can be null in the event that no navigation actually took place!
|
||||
if (_accumulatedEvent != null)
|
||||
OnNavigated(_accumulatedEvent);
|
||||
ProcessNavigated(_accumulatedEvent);
|
||||
}
|
||||
|
||||
internal static void ApplyQueryAttributes(Element element, IDictionary<string, string> query, bool isLastItem)
|
||||
|
@ -931,25 +941,39 @@ namespace Xamarin.Forms
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
protected virtual void OnNavigated(ShellNavigatedEventArgs args)
|
||||
internal void ProcessNavigated(ShellNavigatedEventArgs args)
|
||||
{
|
||||
if (_accumulateNavigatedEvents)
|
||||
_accumulatedEvent = args;
|
||||
else
|
||||
{
|
||||
var content = CurrentItem?.CurrentItem?.CurrentItem;
|
||||
if (content != null)
|
||||
BaseShellItem baseShellItem = CurrentItem?.CurrentItem?.CurrentItem;
|
||||
|
||||
if (baseShellItem != null)
|
||||
{
|
||||
content.OnAppearing(() => Navigated?.Invoke(this, args));
|
||||
baseShellItem.OnAppearing(() =>
|
||||
{
|
||||
OnNavigated(args);
|
||||
Navigated?.Invoke(this, args);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
OnNavigated(args);
|
||||
Navigated?.Invoke(this, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void ProcessNavigating(ShellNavigatingEventArgs args)
|
||||
{
|
||||
OnNavigating(args);
|
||||
}
|
||||
|
||||
protected virtual void OnNavigated(ShellNavigatedEventArgs args)
|
||||
{
|
||||
}
|
||||
|
||||
ShellNavigationState _lastNavigating;
|
||||
protected virtual void OnNavigating(ShellNavigatingEventArgs args)
|
||||
{
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Xamarin.Forms
|
||||
|
@ -12,10 +13,19 @@ namespace Xamarin.Forms
|
|||
static readonly char[] _pathSeparators = { '/', '\\' };
|
||||
const string _pathSeparator = "/";
|
||||
|
||||
internal static Uri FormatUri(Uri path)
|
||||
internal static Uri FormatUri(Uri path, Shell shell)
|
||||
{
|
||||
if (path.OriginalString.StartsWith("..") && shell?.CurrentState != null)
|
||||
{
|
||||
var result = Path.Combine(shell.CurrentState.FullLocation.OriginalString, path.OriginalString);
|
||||
var returnValue = ConvertToStandardFormat("scheme", "host", null, new Uri(result, UriKind.Relative));
|
||||
return new Uri(FormatUri(returnValue.AbsolutePath), UriKind.Relative);
|
||||
}
|
||||
|
||||
if (path.IsAbsoluteUri)
|
||||
{
|
||||
return new Uri(FormatUri(path.OriginalString), UriKind.Absolute);
|
||||
}
|
||||
|
||||
return new Uri(FormatUri(path.OriginalString), UriKind.Relative);
|
||||
}
|
||||
|
@ -43,7 +53,12 @@ namespace Xamarin.Forms
|
|||
|
||||
public static Uri ConvertToStandardFormat(Shell shell, Uri request)
|
||||
{
|
||||
request = FormatUri(request);
|
||||
request = FormatUri(request, shell);
|
||||
return ConvertToStandardFormat(shell?.RouteScheme, shell?.RouteHost, shell?.Route, request);
|
||||
}
|
||||
|
||||
public static Uri ConvertToStandardFormat(string routeScheme, string routeHost, string route, Uri request)
|
||||
{
|
||||
string pathAndQuery = null;
|
||||
if (request.IsAbsoluteUri)
|
||||
pathAndQuery = $"{request.Host}/{request.PathAndQuery}";
|
||||
|
@ -52,22 +67,21 @@ namespace Xamarin.Forms
|
|||
|
||||
var segments = new List<string>(pathAndQuery.Split(_pathSeparators, StringSplitOptions.RemoveEmptyEntries));
|
||||
|
||||
if (segments[0] != routeHost)
|
||||
segments.Insert(0, routeHost);
|
||||
|
||||
if (segments[0] != shell.RouteHost)
|
||||
segments.Insert(0, shell.RouteHost);
|
||||
|
||||
if (segments[1] != shell.Route)
|
||||
segments.Insert(1, shell.Route);
|
||||
if (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)
|
||||
{
|
||||
uri = FormatUri(uri);
|
||||
uri = FormatUri(uri, shell);
|
||||
// figure out the intent of the Uri
|
||||
NavigationRequest.WhatToDoWithTheStack whatDoIDo = NavigationRequest.WhatToDoWithTheStack.PushToIt;
|
||||
if (uri.IsAbsoluteUri)
|
||||
|
@ -115,7 +129,7 @@ namespace Xamarin.Forms
|
|||
|
||||
internal static List<RouteRequestBuilder> GenerateRoutePaths(Shell shell, Uri request)
|
||||
{
|
||||
request = FormatUri(request);
|
||||
request = FormatUri(request, shell);
|
||||
return GenerateRoutePaths(shell, request, request, false);
|
||||
}
|
||||
|
||||
|
@ -132,8 +146,8 @@ namespace Xamarin.Forms
|
|||
routeKeys[i] = FormatUri(routeKeys[i]);
|
||||
}
|
||||
|
||||
request = FormatUri(request);
|
||||
originalRequest = FormatUri(originalRequest);
|
||||
request = FormatUri(request, shell);
|
||||
originalRequest = FormatUri(originalRequest, shell);
|
||||
|
||||
List<RouteRequestBuilder> possibleRoutePaths = new List<RouteRequestBuilder>();
|
||||
if (!request.IsAbsoluteUri)
|
||||
|
@ -156,7 +170,7 @@ namespace Xamarin.Forms
|
|||
var uri = ConvertToStandardFormat(shell, 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<RouteRequestBuilder> { builder };
|
||||
}
|
||||
|
@ -175,7 +189,7 @@ namespace Xamarin.Forms
|
|||
depthStart = 0;
|
||||
}
|
||||
|
||||
if(relativeMatch && shell?.CurrentItem != null)
|
||||
if (relativeMatch && shell?.CurrentItem != null)
|
||||
{
|
||||
// retrieve current location
|
||||
var currentLocation = NodeLocation.Create(shell);
|
||||
|
@ -227,7 +241,7 @@ namespace Xamarin.Forms
|
|||
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 +250,7 @@ namespace Xamarin.Forms
|
|||
}
|
||||
}
|
||||
|
||||
if(builder != null && builder.IsFullMatch)
|
||||
if (builder != null && builder.IsFullMatch)
|
||||
return new List<RouteRequestBuilder> { builder };
|
||||
}
|
||||
else
|
||||
|
@ -321,9 +335,9 @@ namespace Xamarin.Forms
|
|||
{
|
||||
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 +551,7 @@ namespace Xamarin.Forms
|
|||
|
||||
if (segments[0] == route)
|
||||
{
|
||||
yield return new GlobalRouteItem(key, key);
|
||||
yield return new GlobalRouteItem(key, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -650,7 +664,7 @@ namespace Xamarin.Forms
|
|||
switch (node)
|
||||
{
|
||||
case ShellUriHandler.GlobalRouteItem globalRoute:
|
||||
if(globalRoute.IsFinished)
|
||||
if (globalRoute.IsFinished)
|
||||
_globalRouteMatches.Add(globalRoute.SourceRoute);
|
||||
break;
|
||||
case Shell shell:
|
||||
|
|
Загрузка…
Ссылка в новой задаче