Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

chore: check if PanelVisibilityNavigator can navigate #2647

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 141 additions & 2 deletions src/Uno.Extensions.Navigation.UI/Navigators/PanelVisiblityNavigator.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace Uno.Extensions.Navigation.Navigators;
using System.Reflection;

namespace Uno.Extensions.Navigation.Navigators;

public class PanelVisiblityNavigator : ControlNavigator<Panel>
{
Expand All @@ -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;
Expand All @@ -34,20 +37,135 @@ private void PanelLoaded(object sender, RoutedEventArgs e) {

protected override async Task<bool> 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;
}

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<FrameworkElement> items)
{
return items.Any(x => x.GetName() == route);
}

return false;
}

private FrameworkElement? CurrentlyVisibleControl { get; set; }
Expand Down Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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" });
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
mc:Ignorable="d">

<Grid>
<TextBlock Text="ChefsFavoriteRecipes" />

<!--<TextBlock Text="ChefsFavoriteRecipes" />-->
<Button uen:Navigation.Request="ChefsRecipeDetails"
Content="Recipe Details" />
<utu:AutoLayout Padding="16"
uen:Region.Attached="True"
Spacing="16">
Expand All @@ -35,7 +36,7 @@
VerticalAlignment="Center"
Text="All my recipes" />
<Button AutomationProperties.AutomationId="RecipeDetailsButton"
uen:Navigation.Request="ChefsFavoriteRecipeDetails"
uen:Navigation.Request="ChefsRecipeDetails"
Content="Recipe Details" />
</Grid>
<Grid uen:Region.Name="MyCookbooks"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,26 +54,17 @@ protected override void RegisterRoutes(IViewRegistry views, IRouteRegistry route
#endregion

#region Recipe Details
new RouteMap("ChefsRecipeDetails", View: views.FindByViewModel<ChefsRecipeDetailsModel>(), DependsOn: "ChefsHome", Nested: new[] {
new RouteMap("ChefsIngredientsTabWide"),
new RouteMap("ChefsStepsTabWide"),
new RouteMap("ChefsReviewsTabWide"),
new RouteMap("ChefsNutritionTabWide"),
new RouteMap("ChefsIngredientsTab"),
new RouteMap("ChefsStepsTab"),
new RouteMap("ChefsReviewsTab"),
new RouteMap("ChefsNutritionTab"),
}),
new RouteMap("ChefsSearchRecipeDetails", View: views.FindByViewModel<ChefsRecipeDetailsModel>(), DependsOn: "ChefsSearch"),
new RouteMap("ChefsFavoriteRecipeDetails", View: views.FindByViewModel<ChefsRecipeDetailsModel>(), DependsOn: "ChefsFavoriteRecipes"),
new RouteMap("ChefsCookbookRecipeDetails", View: views.FindByViewModel<ChefsRecipeDetailsModel>(), DependsOn: "ChefsFavoriteRecipes"),
new RouteMap("ChefsRecipeDetails", View: views.FindByViewModel<ChefsRecipeDetailsModel>()),
//new RouteMap("ChefsSearchRecipeDetails", View: views.FindByViewModel<ChefsRecipeDetailsModel>(), DependsOn: "ChefsSearch"),
//new RouteMap("ChefsFavoriteRecipeDetails", View: views.FindByViewModel<ChefsRecipeDetailsModel>(), DependsOn: "ChefsFavoriteRecipes"),
//new RouteMap("ChefsCookbookRecipeDetails", View: views.FindByViewModel<ChefsRecipeDetailsModel>(), DependsOn: "ChefsFavoriteRecipes"),
#endregion

#region Live Cooking
new RouteMap("ChefsLiveCooking", View: views.FindByViewModel<ChefsLiveCookingModel>(), DependsOn: "ChefsRecipeDetails"),
new RouteMap("ChefsSearchLiveCooking", View: views.FindByViewModel<ChefsLiveCookingModel>(), DependsOn: "ChefsSearchRecipeDetails"),
new RouteMap("ChefsFavoriteLiveCooking", View: views.FindByViewModel<ChefsLiveCookingModel>(), DependsOn: "ChefsFavoriteRecipeDetails"),
new RouteMap("ChefsCookbookLiveCooking", View: views.FindByViewModel<ChefsLiveCookingModel>(), DependsOn: "ChefsCookbookRecipeDetails"),
new RouteMap("ChefsLiveCooking", View: views.FindByViewModel<ChefsLiveCookingModel>()),
//new RouteMap("ChefsSearchLiveCooking", View: views.FindByViewModel<ChefsLiveCookingModel>(), DependsOn: "ChefsSearchRecipeDetails"),
//new RouteMap("ChefsFavoriteLiveCooking", View: views.FindByViewModel<ChefsLiveCookingModel>(), DependsOn: "ChefsFavoriteRecipeDetails"),
//new RouteMap("ChefsCookbookLiveCooking", View: views.FindByViewModel<ChefsLiveCookingModel>(), DependsOn: "ChefsCookbookRecipeDetails"),
#endregion

new RouteMap("ChefsMap", View: views.FindByViewModel<ChefsMapModel>(), DependsOn: "ChefsHome"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ public async ValueTask LiveCooking()
var route = _navigator?.Route?.Base switch
{
"ChefsRecipeDetails" => "ChefsLiveCooking",
"ChefsSearchRecipeDetails" => "ChefsSearchLiveCooking",
"ChefsFavoriteRecipeDetails" => "ChefsFavoriteLiveCooking",
"ChefsCookbookRecipeDetails" => "ChefsCookbookLiveCooking",
"ChefsSearchRecipeDetails" => "ChefsLiveCooking",
"ChefsFavoriteRecipeDetails" => "ChefsLiveCooking",
"ChefsCookbookRecipeDetails" => "ChefsLiveCooking",
_ => throw new InvalidOperationException("Navigating from unknown route")
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ public partial class ChefsSearchModel(INavigator navigator)
{
public async ValueTask NavigateToRecipeDetail()
{
await navigator.NavigateRouteAsync(this, "ChefsSearchRecipeDetails", data: new ChefsRecipe { Name = "Search Page" });
await navigator.NavigateRouteAsync(this, "ChefsRecipeDetails", data: new ChefsRecipe { Name = "Search Page" });
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,7 @@
FontSize="24"
Text="Two" />
</Grid>
<Grid uen:Region.Name="RegionsThree" Visibility="Collapsed">
<Frame uen:Region.Attached="True">
<StackPanel>
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="24"
Text="Three" />
</StackPanel>
</Frame>
</Grid>
<!-- Third tab is defined as a UserControl in RegionsTabBarItem3.xaml -->
</Grid>
<utu:TabBar Grid.Row="2"
VerticalAlignment="Bottom"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ protected override void RegisterRoutes(IViewRegistry views, IRouteRegistry route
new ViewMap(ViewModel: typeof(RegionsShellModel)),
new ViewMap<RegionsHomePage>(),
new ViewMap<RegionsFirstPage>(),
new ViewMap<RegionsTabBarItem3>(),
new ViewMap<RegionsSecondPage, RegionsSecondViewModel>(),
new ViewMap<RegionsThirdPage>(),
new DataViewMap<RegionsFourthPage, RegionsFourthViewModel, FourthData>()
Expand All @@ -20,12 +21,12 @@ protected override void RegisterRoutes(IViewRegistry views, IRouteRegistry route
new ("RegionsHome", View: views.FindByView<RegionsHomePage>(),
Nested:
[
new ("RegionsFirst", View: views.FindByView<RegionsFirstPage>(), IsDefault: true,
Nested:
new ("RegionsFirst", View: views.FindByView<RegionsFirstPage>(), IsDefault: true
,Nested:
[
new ("RegionsOne", IsDefault: true),
new ("RegionsTwo"),
new ("RegionsThree")
new ("RegionsThree", View: views.FindByView<RegionsTabBarItem3>())
]
),
new ("RegionsSecond", View: views.FindByViewModel<RegionsSecondViewModel>()),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<UserControl x:Class="TestHarness.Ext.Navigation.Apps.Regions.RegionsTabBarItem3"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:TestHarness.Ext.Navigation.Apps.Regions"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="300"
d:DesignWidth="400"
mc:Ignorable="d">

<StackPanel>
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="24"
Text="Tab Three (User Control)" />
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Bottom"
FontSize="16"
Text="Content for Tab 3 is a user control." />
</StackPanel>
</UserControl>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace TestHarness.Ext.Navigation.Apps.Regions;

public sealed partial class RegionsTabBarItem3 : UserControl
{
public RegionsTabBarItem3()
{
this.InitializeComponent();
}
}
Loading