File: Shell\ShellNavigationState.cs
Web Access
Project: src\src\Controls\src\Core\Controls.Core.csproj (Microsoft.Maui.Controls)
#nullable disable
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
 
namespace Microsoft.Maui.Controls
{
	/// <include file="../../../docs/Microsoft.Maui.Controls/ShellNavigationState.xml" path="Type[@FullName='Microsoft.Maui.Controls.ShellNavigationState']/Docs/*" />
	[DebuggerDisplay("Location = {Location}")]
	[TypeConverter(typeof(ShellNavigationStateTypeConverter))]
	public class ShellNavigationState
	{
		Uri _fullLocation;
 
		internal Uri FullLocation
		{
			get => _fullLocation;
			set
			{
				_fullLocation = value;
			}
		}
 
		/// <include file="../../../docs/Microsoft.Maui.Controls/ShellNavigationState.xml" path="//Member[@MemberName='Location']/Docs/*" />
		public Uri Location
		{
			get;
			private set;
		}
 
		/// <include file="../../../docs/Microsoft.Maui.Controls/ShellNavigationState.xml" path="//Member[@MemberName='.ctor'][1]/Docs/*" />
		public ShellNavigationState() { }
		/// <include file="../../../docs/Microsoft.Maui.Controls/ShellNavigationState.xml" path="//Member[@MemberName='.ctor'][2]/Docs/*" />
		public ShellNavigationState(string location) : this(location, true)
		{
		}
 
		internal ShellNavigationState(string location, bool trimForUser)
		{
			var uri = ShellUriHandler.CreateUri(location);
 
			if (uri.IsAbsoluteUri)
				uri = new Uri($"/{uri.PathAndQuery}", UriKind.Relative);
 
			FullLocation = uri;
 
			if (trimForUser)
				Location = TrimDownImplicitAndDefaultPaths(FullLocation);
			else
				Location = FullLocation;
		}
 
		/// <include file="../../../docs/Microsoft.Maui.Controls/ShellNavigationState.xml" path="//Member[@MemberName='.ctor'][3]/Docs/*" />
		public ShellNavigationState(Uri location)
		{
			FullLocation = location;
			Location = TrimDownImplicitAndDefaultPaths(FullLocation);
		}
 
		public static implicit operator ShellNavigationState(Uri uri) => new ShellNavigationState(uri);
		public static implicit operator ShellNavigationState(string value) => new ShellNavigationState(value);
 
		static Uri TrimDownImplicitAndDefaultPaths(Uri uri)
		{
			uri = ShellUriHandler.FormatUri(uri, null);
 
			// don't trim relative pushes
			if (!uri.OriginalString.StartsWith("//", StringComparison.Ordinal))
				return uri;
 
			string[] parts = uri.OriginalString.TrimEnd(Routing.PathSeparator[0]).Split(Routing.PathSeparator[0]);
 
			List<string> toKeep = new List<string>();
 
			// iterate over the shellitem/section/content
			for (int i = 2; i < 5 && i < parts.Length; i++)
			{
				if (!(Routing.IsDefault(parts[i])) && !(Routing.IsImplicit(parts[i])))
				{
					toKeep.Add(parts[i]);
				}
				else if (i == 4)
				{
					// if all the routes are default then just put the last
					// shell content page as the route
					if (toKeep.Count == 0)
						toKeep.Add(parts[i]);
				}
			}
 
			// Always include pushed pages
			for (int i = 5; i < parts.Length; i++)
			{
				toKeep.Add(parts[i]);
			}
 
			toKeep.Insert(0, "");
			toKeep.Insert(0, "");
			return new Uri(string.Join(Routing.PathSeparator, toKeep), UriKind.Relative);
		}
 
		private sealed class ShellNavigationStateTypeConverter : TypeConverter
		{
			public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) => false;
			public override object ConvertTo(ITypeDescriptorContext context, CultureInfo cultureInfo, object value, Type destinationType) => throw new NotSupportedException();
 
			public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
				=> sourceType == typeof(string) || sourceType == typeof(Uri);
 
			public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
				=> value switch
				{
					string str => (ShellNavigationState)str,
					Uri uri => (ShellNavigationState)uri,
					_ => throw new NotSupportedException(),
				};
		}
	}
}