static IEnumerable GetItems(object node) { IEnumerable results = null; switch (node) { case IShellController shell: results = shell.GetItems(); break; case IShellItemController item: results = item.GetItems(); break; case IShellSectionController section: results = section.GetItems(); break; case ShellContent content: results = new object[0]; break; case GlobalRouteItem routeItem: results = routeItem.Items; break; } if (results == null) { throw new ArgumentException($"{node}", nameof(node)); } foreach (var result in results) { yield return(result); } if (node is GlobalRouteItem) { yield break; } var keys = Routing.GetRouteKeys(); string route = GetRoute(node); for (var i = 0; i < keys.Length; i++) { var key = FormatUri(keys[i]); if (key.StartsWith(_pathSeparator, StringComparison.Ordinal) && !(node is Shell)) { continue; } var segments = key.Split(_pathSeparator.ToCharArray(), StringSplitOptions.RemoveEmptyEntries); if (segments[0] == route) { yield return(new GlobalRouteItem(key, key)); } } }
internal static List <RouteRequestBuilder> GenerateRoutePaths(Shell shell, Uri request, Uri originalRequest, bool enableRelativeShellRoutes) { var routeKeys = Routing.GetRouteKeys(); for (int i = 0; i < routeKeys.Length; i++) { if (routeKeys[i] == originalRequest.OriginalString) { var builder = new RouteRequestBuilder(routeKeys[i], routeKeys[i], null, new string[] { routeKeys[i] }); return(new List <RouteRequestBuilder> { builder }); } routeKeys[i] = FormatUri(routeKeys[i]); } request = FormatUri(request, shell); originalRequest = FormatUri(originalRequest, shell); List <RouteRequestBuilder> possibleRoutePaths = new List <RouteRequestBuilder>(); if (!request.IsAbsoluteUri) { request = ConvertToStandardFormat(shell, request); } string localPath = request.LocalPath; bool relativeMatch = false; if (!originalRequest.IsAbsoluteUri && !originalRequest.OriginalString.StartsWith("//", StringComparison.Ordinal)) { relativeMatch = true; } var segments = localPath.Split(_pathSeparator.ToCharArray(), StringSplitOptions.RemoveEmptyEntries); if (!relativeMatch) { for (int i = 0; i < routeKeys.Length; i++) { var route = routeKeys[i]; 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("//", "")}"); //var builder = new RouteRequestBuilder(route, route, null, segments); //return new List<RouteRequestBuilder> { builder }; } } } var depthStart = 0; if (segments[0] == shell?.Route) { segments = segments.Skip(1).ToArray(); depthStart = 1; } else { depthStart = 0; } if (relativeMatch && shell?.CurrentItem != null) { // retrieve current location var currentLocation = NodeLocation.Create(shell); while (currentLocation.Shell != null) { var pureRoutesMatch = new List <RouteRequestBuilder>(); var pureGlobalRoutesMatch = new List <RouteRequestBuilder>(); //currently relative routes to shell routes isn't supported as we aren't creating navigation stacks if (enableRelativeShellRoutes) { SearchPath(currentLocation.LowestChild, null, segments, pureRoutesMatch, 0); ExpandOutGlobalRoutes(pureRoutesMatch, routeKeys); pureRoutesMatch = GetBestMatches(pureRoutesMatch); if (pureRoutesMatch.Count > 0) { return(pureRoutesMatch); } } SearchPath(currentLocation.LowestChild, null, segments, pureGlobalRoutesMatch, 0, ignoreGlobalRoutes: false); ExpandOutGlobalRoutes(pureGlobalRoutesMatch, routeKeys); pureGlobalRoutesMatch = GetBestMatches(pureGlobalRoutesMatch); if (pureGlobalRoutesMatch.Count > 0) { // currently relative routes to shell routes isn't supported as we aren't creating navigation stacks // So right now we will just throw an exception so that once this is implemented // GotoAsync doesn't start acting inconsistently and all of a sudden starts creating routes int shellElementsMatched = pureGlobalRoutesMatch[0].SegmentsMatched.Count - pureGlobalRoutesMatch[0].GlobalRouteMatches.Count; if (!enableRelativeShellRoutes && shellElementsMatched > 0) { throw new Exception($"Relative routing to shell elements is currently not supported. Try prefixing your uri with ///: ///{originalRequest}"); } return(pureGlobalRoutesMatch); } currentLocation.Pop(); } string searchPath = String.Join(_pathSeparator, segments); if (routeKeys.Contains(searchPath)) { return(new List <RouteRequestBuilder> { new RouteRequestBuilder(searchPath, searchPath, null, segments) }); } RouteRequestBuilder builder = null; foreach (var segment in segments) { if (routeKeys.Contains(segment)) { if (builder == null) { builder = new RouteRequestBuilder(segment, segment, null, segments); } else { builder.AddGlobalRoute(segment, segment); } } } if (builder != null && builder.IsFullMatch) { return new List <RouteRequestBuilder> { builder } } ; } else { possibleRoutePaths.Clear(); SearchPath(shell, null, segments, possibleRoutePaths, depthStart); var bestMatches = GetBestMatches(possibleRoutePaths); if (bestMatches.Count > 0) { return(bestMatches); } bestMatches.Clear(); ExpandOutGlobalRoutes(possibleRoutePaths, routeKeys); } possibleRoutePaths = GetBestMatches(possibleRoutePaths); return(possibleRoutePaths); }
static void SearchPath( object node, RouteRequestBuilder currentMatchedPath, string[] segments, List <RouteRequestBuilder> possibleRoutePaths, int depthToStart, int myDepth = -1, NodeLocation currentLocation = null, bool ignoreGlobalRoutes = true) { if (node is GlobalRouteItem && ignoreGlobalRoutes) { return; } ++myDepth; currentLocation = currentLocation ?? new NodeLocation(); currentLocation.SetNode(node); IEnumerable items = null; if (depthToStart > myDepth) { items = GetItems(node); if (items == null) { return; } foreach (var nextNode in items) { SearchPath(nextNode, null, segments, possibleRoutePaths, depthToStart, myDepth, currentLocation, ignoreGlobalRoutes); } return; } string shellSegment = GetRoute(node); string userSegment = null; if (currentMatchedPath == null) { userSegment = segments[0]; } else { userSegment = currentMatchedPath.NextSegment; } if (userSegment == null) { return; } RouteRequestBuilder builder = null; if (shellSegment == userSegment || Routing.IsImplicit(shellSegment)) { if (currentMatchedPath == null) { builder = new RouteRequestBuilder(shellSegment, userSegment, node, segments); } else { builder = new RouteRequestBuilder(currentMatchedPath); builder.AddMatch(shellSegment, userSegment, node); } if (!Routing.IsImplicit(shellSegment) || shellSegment == userSegment) { possibleRoutePaths.Add(builder); } } items = GetItems(node); if (items == null) { return; } foreach (var nextNode in items) { SearchPath(nextNode, builder, segments, possibleRoutePaths, depthToStart, myDepth, currentLocation, ignoreGlobalRoutes); } }
internal static List <RouteRequestBuilder> GenerateRoutePaths(Shell shell, Uri request, Uri originalRequest) { request = FormatUri(request); originalRequest = FormatUri(originalRequest); var routeKeys = Routing.GetRouteKeys(); for (int i = 0; i < routeKeys.Length; i++) { routeKeys[i] = FormatUri(routeKeys[i]); } List <RouteRequestBuilder> possibleRoutePaths = new List <RouteRequestBuilder>(); if (!request.IsAbsoluteUri) { request = ConvertToStandardFormat(shell, request); } string localPath = request.LocalPath; bool relativeMatch = false; if (!originalRequest.IsAbsoluteUri && !originalRequest.OriginalString.StartsWith("/") && !originalRequest.OriginalString.StartsWith("\\")) { relativeMatch = true; } var segments = localPath.Split(_pathSeparator, StringSplitOptions.RemoveEmptyEntries); if (!relativeMatch) { for (int i = 0; i < routeKeys.Length; i++) { var route = routeKeys[i]; var uri = ConvertToStandardFormat(shell, new Uri(route, UriKind.RelativeOrAbsolute)); // Todo is this supported? if (uri.Equals(request)) { var builder = new RouteRequestBuilder(route, route, null, segments); return(new List <RouteRequestBuilder> { builder }); } } } var depthStart = 0; if (segments[0] == shell.Route) { segments = segments.Skip(1).ToArray(); depthStart = 1; } else { depthStart = 0; } if (relativeMatch && shell?.CurrentItem != null) { // retrieve current location var currentLocation = NodeLocation.Create(shell); while (currentLocation.Shell != null) { List <RouteRequestBuilder> pureRoutesMatch = new List <RouteRequestBuilder>(); List <RouteRequestBuilder> pureGlobalRoutesMatch = new List <RouteRequestBuilder>(); SearchPath(currentLocation.LowestChild, null, segments, pureRoutesMatch, 0); SearchPath(currentLocation.LowestChild, null, segments, pureGlobalRoutesMatch, 0, ignoreGlobalRoutes: false); pureRoutesMatch = GetBestMatches(pureRoutesMatch); pureGlobalRoutesMatch = GetBestMatches(pureGlobalRoutesMatch); if (pureRoutesMatch.Count > 0) { return(pureRoutesMatch); } if (pureGlobalRoutesMatch.Count > 0) { return(pureGlobalRoutesMatch); } currentLocation.Pop(); } string searchPath = String.Join("/", segments); if (routeKeys.Contains(searchPath)) { return(new List <RouteRequestBuilder> { new RouteRequestBuilder(searchPath, searchPath, null, segments) }); } RouteRequestBuilder builder = null; foreach (var segment in segments) { if (routeKeys.Contains(segment)) { if (builder == null) { builder = new RouteRequestBuilder(segment, segment, null, segments); } else { builder.AddGlobalRoute(segment, segment); } } } if (builder != null && builder.IsFullMatch) { return new List <RouteRequestBuilder> { builder } } ; } else { possibleRoutePaths.Clear(); SearchPath(shell, null, segments, possibleRoutePaths, depthStart); var bestMatches = GetBestMatches(possibleRoutePaths); if (bestMatches.Count > 0) { return(bestMatches); } bestMatches.Clear(); foreach (var possibleRoutePath in possibleRoutePaths) { while (routeKeys.Contains(possibleRoutePath.NextSegment) || routeKeys.Contains(possibleRoutePath.RemainingPath)) { if (routeKeys.Contains(possibleRoutePath.NextSegment)) { possibleRoutePath.AddGlobalRoute(possibleRoutePath.NextSegment, possibleRoutePath.NextSegment); } else { possibleRoutePath.AddGlobalRoute(possibleRoutePath.RemainingPath, possibleRoutePath.RemainingPath); } } while (!possibleRoutePath.IsFullMatch) { NodeLocation nodeLocation = new NodeLocation(); nodeLocation.SetNode(possibleRoutePath.LowestChild); List <RouteRequestBuilder> pureGlobalRoutesMatch = new List <RouteRequestBuilder>(); while (nodeLocation.Shell != null && pureGlobalRoutesMatch.Count == 0) { SearchPath(nodeLocation.LowestChild, null, possibleRoutePath.RemainingSegments, pureGlobalRoutesMatch, 0, ignoreGlobalRoutes: false); nodeLocation.Pop(); } // nothing found or too many things found if (pureGlobalRoutesMatch.Count != 1) { break; } for (var i = 0; i < pureGlobalRoutesMatch[0].GlobalRouteMatches.Count; i++) { var match = pureGlobalRoutesMatch[0]; possibleRoutePath.AddGlobalRoute(match.GlobalRouteMatches[i], match.SegmentsMatched[i]); } } } } possibleRoutePaths = GetBestMatches(possibleRoutePaths); return(possibleRoutePaths); }
bool UpdateFlyoutGroupings() { // The idea here is to create grouping such that the Flyout would // render correctly if it renderered each item in the groups in order // but put a line between the groups. This is needed because our grouping can // actually go 3 layers deep. // Ideally this lets us control where lines are drawn in the core code // just by changing how we generate these groupings var result = new List <List <Element> >(); var currentGroup = new List <Element>(); foreach (var shellItem in ShellController.GetItems()) { if (!ShowInFlyoutMenu(shellItem)) { continue; } if (Routing.IsImplicit(shellItem) || shellItem.FlyoutDisplayOptions == FlyoutDisplayOptions.AsMultipleItems) { if (shellItem.FlyoutDisplayOptions == FlyoutDisplayOptions.AsMultipleItems) { IncrementGroup(); } foreach (var shellSection in (shellItem as IShellItemController).GetItems()) { if (!ShowInFlyoutMenu(shellSection)) { continue; } var shellContents = ((IShellSectionController)shellSection).GetItems(); if (Routing.IsImplicit(shellSection) || shellSection.FlyoutDisplayOptions == FlyoutDisplayOptions.AsMultipleItems) { foreach (var shellContent in shellContents) { if (!ShowInFlyoutMenu(shellContent)) { continue; } currentGroup.Add(shellContent); if (shellContent == shellSection.CurrentItem) { AddMenuItems(shellContent.MenuItems); } } if (shellSection.FlyoutDisplayOptions == FlyoutDisplayOptions.AsMultipleItems) { IncrementGroup(); } } else { if (!(shellSection.Parent is TabBar)) { if (Routing.IsImplicit(shellSection) && shellContents.Count == 1) { if (!ShowInFlyoutMenu(shellContents[0])) { continue; } currentGroup.Add(shellContents[0]); } else { currentGroup.Add(shellSection); } } // If we have only a single child we will also show the items menu items if (shellContents.Count == 1 && shellSection == shellItem.CurrentItem && shellSection.CurrentItem.MenuItems.Count > 0) { AddMenuItems(shellSection.CurrentItem.MenuItems); } } } if (shellItem.FlyoutDisplayOptions == FlyoutDisplayOptions.AsMultipleItems) { IncrementGroup(); } } else { if (!(shellItem is TabBar)) { currentGroup.Add(shellItem); } } } IncrementGroup(); // If the flyout groupings haven't changed just return // the same instance so the caller knows it hasn't changed // at a later point this will all get converted to an observable collection if (_lastGeneratedFlyoutItems?.Count == result.Count) { bool hasChanged = false; for (var i = 0; i < result.Count && !hasChanged; i++) { var topLevelNew = result[i]; var topLevelPrevious = _lastGeneratedFlyoutItems[i]; if (topLevelNew.Count != topLevelPrevious.Count) { hasChanged = true; break; } for (var j = 0; j > topLevelNew.Count; j++) { if (topLevelNew[j] != topLevelPrevious[j]) { hasChanged = true; break; } } } if (!hasChanged) { return(false); } } _lastGeneratedFlyoutItems = result; return(true); bool ShowInFlyoutMenu(BindableObject bo) { if (bo is MenuShellItem msi) { return(Shell.GetFlyoutItemIsVisible(msi.MenuItem)); } return(Shell.GetFlyoutItemIsVisible(bo)); } void AddMenuItems(MenuItemCollection menuItems) { foreach (var item in menuItems) { if (ShowInFlyoutMenu(item)) { currentGroup.Add(item); } } } void IncrementGroup() { if (currentGroup.Count > 0) { result.Add(currentGroup); currentGroup = new List <Element>(); } } }
public void AddMatch(string shellSegment, string userSegment, object node) { if (node == null) { throw new ArgumentNullException(nameof(node)); } switch (node) { case ShellUriHandler.GlobalRouteItem globalRoute: if (globalRoute.IsFinished) { _globalRouteMatches.Add(globalRoute.SourceRoute); } break; case Shell shell: if (shell == Shell) { return; } Shell = shell; break; case ShellItem item: if (Item == item) { return; } Item = item; break; case ShellSection section: if (Section == section) { return; } Section = section; if (Item == null) { Item = Section.Parent as ShellItem; _fullSegments.Add(Item.Route); } break; case ShellContent content: if (Content == content) { return; } Content = content; if (Section == null) { Section = Content.Parent as ShellSection; _fullSegments.Add(Section.Route); } if (Item == null) { Item = Section.Parent as ShellItem; _fullSegments.Insert(0, Item.Route); } break; } if (Item?.Parent is Shell s) { Shell = s; } // if shellSegment == userSegment it means the implicit route is part of the request if (Routing.IsUserDefined(shellSegment) || shellSegment == userSegment || shellSegment == NextSegment) { _matchedSegments.Add(shellSegment); } _fullSegments.Add(shellSegment); }
public async Task GoToAsync(ShellNavigationParameters shellNavigationParameters) { if (shellNavigationParameters.PagePushing != null) { Routing.RegisterImplicitPageRoute(shellNavigationParameters.PagePushing); } ShellNavigationState state = shellNavigationParameters.TargetState ?? Routing.GetRoute(shellNavigationParameters.PagePushing); bool?animate = shellNavigationParameters.Animated; bool enableRelativeShellRoutes = shellNavigationParameters.EnableRelativeShellRoutes; ShellNavigatingEventArgs deferredArgs = shellNavigationParameters.DeferredArgs; var navigationRequest = ShellUriHandler.GetNavigationRequest(_shell, state.FullLocation, enableRelativeShellRoutes, shellNavigationParameters: shellNavigationParameters); bool isRelativePopping = ShellUriHandler.IsTargetRelativePop(shellNavigationParameters); ShellNavigationSource source = CalculateNavigationSource(_shell, _shell.CurrentState, navigationRequest); // If the deferredArgs are non null that means we are processing a delayed navigation // so the user has indicated they want to go forward with navigation // This scenario only comes up from UI iniated navigation (i.e. switching tabs) if (deferredArgs == null) { var navigatingArgs = ProposeNavigation(source, state, _shell.CurrentState != null, animate ?? true); if (navigatingArgs != null) { bool accept = !navigatingArgs.NavigationDelayedOrCancelled; if (navigatingArgs.DeferredTask != null) { accept = await navigatingArgs.DeferredTask; } if (!accept) { return; } } } Routing.RegisterImplicitPageRoutes(_shell); _accumulateNavigatedEvents = true; var uri = navigationRequest.Request.FullUri; var queryString = navigationRequest.Query; var queryData = ParseQueryString(queryString); ApplyQueryAttributes(_shell, queryData, false, false); var shellItem = navigationRequest.Request.Item; var shellSection = navigationRequest.Request.Section; var currentShellSection = _shell.CurrentItem?.CurrentItem; var nextActiveSection = shellSection ?? shellItem?.CurrentItem; 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 && navigationRequest.StackRequest == NavigationRequest.WhatToDoWithTheStack.ReplaceIt) { modalStackPreBuilt = true; bool?isAnimated = (nextActiveSection != currentShellSection) ? false : animate; await nextActiveSection.GoToAsync(navigationRequest, queryData, isAnimated, isRelativePopping); } if (shellItem != null) { ApplyQueryAttributes(shellItem, queryData, navigationRequest.Request.Section == null, false); bool navigatedToNewShellElement = false; if (shellSection != null && shellContent != null) { ApplyQueryAttributes(shellContent, queryData, navigationRequest.Request.GlobalRoutes.Count == 0, isRelativePopping); if (shellSection.CurrentItem != shellContent) { shellSection.SetValueFromRenderer(ShellSection.CurrentItemProperty, shellContent); navigatedToNewShellElement = true; } } if (shellSection != null) { ApplyQueryAttributes(shellSection, queryData, navigationRequest.Request.Content == null, false); if (shellItem.CurrentItem != shellSection) { shellItem.SetValueFromRenderer(ShellItem.CurrentItemProperty, shellSection); navigatedToNewShellElement = true; } } if (_shell.CurrentItem != shellItem) { _shell.SetValueFromRenderer(Shell.CurrentItemProperty, shellItem); navigatedToNewShellElement = true; } if (!modalStackPreBuilt && currentShellSection?.Navigation.ModalStack.Count > 0) { // - navigating to new shell element so just pop everything // - or route contains no global route requests if (navigatedToNewShellElement || navigationRequest.Request.GlobalRoutes.Count == 0) { // remove all non visible pages first so the transition just smoothly goes from // currently visible modal to base page if (navigationRequest.Request.GlobalRoutes.Count == 0) { for (int i = currentShellSection.Stack.Count - 1; i >= 1; i--) { currentShellSection.Navigation.RemovePage(currentShellSection.Stack[i]); } } await currentShellSection.PopModalStackToPage(null, animate); } } 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 await Device.InvokeOnMainThreadAsync(() => { return(_shell.CurrentItem.CurrentItem.GoToAsync(navigationRequest, queryData, animate, isRelativePopping)); }); } 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(_shell.CurrentItem.CurrentItem.GoToAsync(navigationRequest, queryData, animate, isRelativePopping)); }); } } else { await _shell.CurrentItem.CurrentItem.GoToAsync(navigationRequest, queryData, animate, isRelativePopping); } _accumulateNavigatedEvents = false; // this can be null in the event that no navigation actually took place! if (_accumulatedEvent != null) { HandleNavigated(_accumulatedEvent); } }
public static void ApplyQueryAttributes(Element element, IDictionary <string, string> query, bool isLastItem, bool isPopping) { string prefix = ""; if (!isLastItem) { var route = Routing.GetRoute(element); if (string.IsNullOrEmpty(route) || Routing.IsImplicit(route)) { return; } prefix = route + "."; } //if the lastItem is implicitly wrapped, get the actual ShellContent if (isLastItem) { if (element is IShellItemController shellitem && shellitem.GetItems().FirstOrDefault() is ShellSection section) { element = section; } if (element is IShellSectionController shellsection && shellsection.GetItems().FirstOrDefault() is ShellContent content) { element = content; } if (element is ShellContent shellcontent && shellcontent.Content is Element e) { element = e; } } if (!(element is BaseShellItem baseShellItem)) { baseShellItem = element?.Parent as BaseShellItem; } //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)) { continue; } var key = q.Key.Substring(prefix.Length); if (key.Contains(".")) { continue; } filteredQuery.Add(key, q.Value); } if (baseShellItem is ShellContent) { baseShellItem.ApplyQueryAttributes(MergeData(element, filteredQuery, isPopping)); } else if (isLastItem) { 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); } }