Fix navigating/navigated events and add ".." navigation (#10048)

This commit is contained in:
Shane Neuville 2020-03-27 14:38:15 -06:00 коммит произвёл GitHub
Родитель 925db31c22
Коммит 9e32dcadb0
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
9 изменённых файлов: 239 добавлений и 33 удалений

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

@ -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

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

@ -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
@ -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: