diff --git a/src/Uno.Extensions.Navigation.UI/Navigators/PanelVisiblityNavigator.cs b/src/Uno.Extensions.Navigation.UI/Navigators/PanelVisiblityNavigator.cs index caef86e33b..b687590132 100644 --- a/src/Uno.Extensions.Navigation.UI/Navigators/PanelVisiblityNavigator.cs +++ b/src/Uno.Extensions.Navigation.UI/Navigators/PanelVisiblityNavigator.cs @@ -1,4 +1,6 @@ -namespace Uno.Extensions.Navigation.Navigators; +using System.Reflection; + +namespace Uno.Extensions.Navigation.Navigators; public class PanelVisiblityNavigator : ControlNavigator { @@ -20,7 +22,8 @@ public PanelVisiblityNavigator( } } - private void PanelLoaded(object sender, RoutedEventArgs e) { + private void PanelLoaded(object sender, RoutedEventArgs e) + { if (Control is null) { return; @@ -34,11 +37,65 @@ private void PanelLoaded(object sender, RoutedEventArgs e) { protected override async Task RegionCanNavigate(Route route, RouteInfo? routeMap) { + // Check if the SelectorNavigator can navigate to the route + // This is to prevent the PanelVisibilityNavigator from navigating to a route that is not specified + // As a Region.Name in the Selector (TabBar/NavigationView) Items + // Causing a FrameView to be wrongly injected creating a nested navigation + + //var fullRoute = route.FullPath(); + + // NavView usually will be a parent + //if (Region.Parent is { } parentNavigator && + // IsRegionNavigatorSelector(parentNavigator, out var nav)) + //{ + // if (CanSelectorNavigate(nav!, fullRoute)) + // { + // return true; + // } + //} + + // TabBar usually will be a sibling + //var sibling = Region.Parent?.Children.FirstOrDefault(x => x.View != Control); + //if (sibling is { } && IsRegionNavigatorSelector(sibling, out nav)) + //{ + // return CanSelectorNavigate(nav!, fullRoute); + //} + if (!await base.RegionCanNavigate(route, routeMap)) { return false; } + // Get the current route + var currentRoute = Region.Root().GetRoute(); + + if (currentRoute is { Path: not null }) + { + // Get the `RouteInfo` for the current route + var currentRouteInfo = Resolver.FindByPath(currentRoute.Path.Split('/', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault()); + + if (currentRouteInfo is { } && routeMap is { }) + { + // check if any of the nested RouteInfo has the Path equals to `route` + + // COVERS 3 + if (currentRouteInfo.Nested.Length > 0) + { + if (HasMatchingNestedRoute(currentRouteInfo)) + { + return true; + } + } + + // COVERS 4 + if (HasMatchingRoute(currentRouteInfo)) + { + return false; + } + } + } + + // COVERS 2 if (routeMap?.RenderView?.IsSubclassOf(typeof(FrameworkElement)) ?? false) { return true; @@ -46,8 +103,69 @@ protected override async Task RegionCanNavigate(Route route, RouteInfo? ro return await Dispatcher.ExecuteAsync(async cancellation => { + // COVERS 1 return FindByPath(routeMap?.Path ?? route.Base) is not null; }); + + bool HasMatchingNestedRoute(RouteInfo currentRouteInfo, bool ignoreCurrentRoute = false) + { + var nestedRoutes = currentRouteInfo.Nested; + var path = currentRouteInfo.Path; + + foreach (var nestedRoute in nestedRoutes) + { + if (ignoreCurrentRoute && + nestedRoute.Path == path) + { + continue; + } + + if (nestedRoute.Path == routeMap.Path) + { + return true; + } + + if (nestedRoute is { Nested.Length: > 0 } && + HasMatchingNestedRoute(nestedRoute, ignoreCurrentRoute)) + { + return true; + } + } + + return false; + } + + bool HasMatchingRoute(RouteInfo routeInfo) + { + // get the root + var parent = routeInfo.Parent; + while (parent?.Parent != null) + { + if(parent.Parent is { }) + { + parent = parent.Parent; + } + } + + return HasMatchingNestedRoute(parent!, ignoreCurrentRoute: true); + } + } + + private bool IsRegionNavigatorSelector(IRegion region, out INavigator? navigator) + { + navigator = region.Navigator(); + return navigator != null && InheritsFromSelector(navigator.GetType()); + } + + private bool CanSelectorNavigate(INavigator navigator, string route) + { + var itemsProperty = navigator.GetType().GetProperty("Items", BindingFlags.NonPublic | BindingFlags.Instance); + if (itemsProperty?.GetValue(navigator) is IEnumerable items) + { + return items.Any(x => x.GetName() == route); + } + + return false; } private FrameworkElement? CurrentlyVisibleControl { get; set; } @@ -151,4 +269,25 @@ await Dispatcher.ExecuteAsync(async cancellation => Control.FindName(path) as FrameworkElement; return controlToShow; } + + private bool InheritsFromSelector(Type type) + { + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(SelectorNavigator<>)) + { + return true; + } + + var baseType = type.BaseType; + + while (baseType != null && baseType != typeof(object)) + { + if (baseType.IsGenericType && baseType.GetGenericTypeDefinition() == typeof(SelectorNavigator<>)) + { + return true; + } + baseType = baseType.BaseType; + } + + return false; + } } diff --git a/testing/TestHarness/TestHarness/Ext/Navigation/Apps/Chefs/ChefsFavoriteRecipesModel.cs b/testing/TestHarness/TestHarness/Ext/Navigation/Apps/Chefs/ChefsFavoriteRecipesModel.cs index 5a50c50c78..628388b552 100644 --- a/testing/TestHarness/TestHarness/Ext/Navigation/Apps/Chefs/ChefsFavoriteRecipesModel.cs +++ b/testing/TestHarness/TestHarness/Ext/Navigation/Apps/Chefs/ChefsFavoriteRecipesModel.cs @@ -6,6 +6,6 @@ public partial class ChefsFavoriteRecipesModel(INavigator navigator) { public async ValueTask NavigateToRecipeDetail() { - await navigator.NavigateRouteAsync(this, "ChefsFavoriteRecipeDetails", data: new ChefsRecipe { Name = "Favorite Page" }); + await navigator.NavigateRouteAsync(this, "ChefsRecipeDetails", data: new ChefsRecipe { Name = "Favorite Page" }); } } diff --git a/testing/TestHarness/TestHarness/Ext/Navigation/Apps/Chefs/ChefsFavoriteRecipesPage.xaml b/testing/TestHarness/TestHarness/Ext/Navigation/Apps/Chefs/ChefsFavoriteRecipesPage.xaml index a5bae446ec..903121b2fb 100644 --- a/testing/TestHarness/TestHarness/Ext/Navigation/Apps/Chefs/ChefsFavoriteRecipesPage.xaml +++ b/testing/TestHarness/TestHarness/Ext/Navigation/Apps/Chefs/ChefsFavoriteRecipesPage.xaml @@ -10,8 +10,9 @@ mc:Ignorable="d"> - - + +