File: Shell\Shell.cs
Web Access
Project: src\src\Controls\src\Core\Controls.Core.csproj (Microsoft.Maui.Controls)
#nullable disable
 
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using System.Windows.Input;
using Microsoft.Extensions.Logging;
using Microsoft.Maui.ApplicationModel;
using Microsoft.Maui.Controls.Internals;
using Microsoft.Maui.Controls.Xaml.Diagnostics;
using Microsoft.Maui.Devices;
using Microsoft.Maui.Graphics;
 
namespace Microsoft.Maui.Controls
{
	/// <include file="../../../docs/Microsoft.Maui.Controls/Shell.xml" path="Type[@FullName='Microsoft.Maui.Controls.Shell']/Docs/*" />
	[ContentProperty(nameof(Items))]
	public partial class Shell : Page, IShellController, IPropertyPropagationController, IPageContainer<Page>, IFlyoutView
	{
		/// <summary>
		/// The currently presented page.
		/// </summary>
		public Page CurrentPage => GetVisiblePage() as Page;
 
		/// <summary>
		/// Controls the behavior of the page's back button.
		/// </summary>
		public static readonly BindableProperty BackButtonBehaviorProperty =
			BindableProperty.CreateAttached("BackButtonBehavior", typeof(BackButtonBehavior), typeof(Shell), null, BindingMode.OneTime,
				propertyChanged: OnBackButonBehaviorPropertyChanged);
 
		static void OnBackButonBehaviorPropertyChanged(BindableObject bindable, object oldValue, object newValue)
		{
			if (oldValue is BackButtonBehavior oldHandlerProperties)
				SetInheritedBindingContext(oldHandlerProperties, null);
			if (newValue is BackButtonBehavior newHandlerProperties)
				SetInheritedBindingContext(newHandlerProperties, bindable.BindingContext);
		}
 
		/// <summary>
		/// Defines the navigation animation that occurs when a page is navigated to with the <see cref="GoToAsync(ShellNavigationState, bool)"/> method.
		/// Also controls if the content is presented in a modal way or not.
		/// </summary>
		public static readonly BindableProperty PresentationModeProperty = BindableProperty.CreateAttached("PresentationMode", typeof(PresentationMode), typeof(Shell), PresentationMode.Animated);
 
		/// <summary>
		/// Manages the behavior used to open the flyout.
		/// </summary>
		/// <remarks>
		/// The flyout can be accessed through the hamburger icon or by swiping from the side of the screen. 
		/// This behavior can be changed by setting the <see cref = "FlyoutBehavior" /> property.
		/// </remarks>
		public static readonly BindableProperty FlyoutBehaviorProperty =
			BindableProperty.CreateAttached(nameof(FlyoutBehavior), typeof(FlyoutBehavior), typeof(Shell), FlyoutBehavior.Flyout,
				propertyChanged: OnFlyoutBehaviorChanged);
 
		/// <summary>
		/// Manages if the navigation bar is visible when a page is presented. 
		/// </summary>
		public static readonly BindableProperty NavBarIsVisibleProperty =
			BindableProperty.CreateAttached("NavBarIsVisible", typeof(bool), typeof(Shell), true, propertyChanged: OnNavBarIsVisibleChanged);
 
		private static void OnNavBarIsVisibleChanged(BindableObject bindable, object oldValue, object newValue)
		{
			// Nav bar visibility change is only interesting from the Shell down to the current Page.
			// Make sure the ShellToolbar knows about any possible change.
			Shell shell = bindable as Shell
				?? (bindable as BaseShellItem)?.FindParentOfType<Shell>()
				?? (bindable as Page)?.FindParentOfType<Shell>();
 
			shell?.OnPropertyChanged(NavBarIsVisibleProperty.PropertyName);
		}
 
		/// <summary>
		/// Controls whether the navigation bar has a shadow.
		/// </summary>
		public static readonly BindableProperty NavBarHasShadowProperty =
			BindableProperty.CreateAttached("NavBarHasShadow", typeof(bool), typeof(Shell), default(bool),
				defaultValueCreator: (b) => DeviceInfo.Platform == DevicePlatform.Android);
 
		/// <summary>
		/// Controls the <see cref = "Shell" /> search functionality.
		/// </summary>
		public static readonly BindableProperty SearchHandlerProperty =
			BindableProperty.CreateAttached("SearchHandler", typeof(SearchHandler), typeof(Shell), null, BindingMode.OneTime,
				propertyChanged: OnSearchHandlerPropertyChanged);
 
		static void OnSearchHandlerPropertyChanged(BindableObject bindable, object oldValue, object newValue)
		{
			if (oldValue is SearchHandler oldHandler)
				SetInheritedBindingContext(oldHandler, null);
			if (newValue is SearchHandler newHandler)
				SetInheritedBindingContext(newHandler, bindable.BindingContext);
		}
 
		/// <summary>
		/// The <see cref = "FlyoutItem" /> visibility.
		/// Flyout items are visible in the flyout by default.
		/// </summary>
		public static readonly BindableProperty FlyoutItemIsVisibleProperty =
			BindableProperty.CreateAttached("FlyoutItemIsVisible", typeof(bool), typeof(Shell), true, propertyChanged: OnFlyoutItemIsVisibleChanged);
		public static bool GetFlyoutItemIsVisible(BindableObject obj) => (bool)obj.GetValue(FlyoutItemIsVisibleProperty);
 
		/// <summary>
		/// Sets a value that determines if an object has a visible <see cref = "FlyoutItem" /> in the flyout menu.
		/// Flyout items are visible in the flyout by default. However, an item can be hidden in the flyout with the <see cref = "FlyoutItemIsVisibleProperty" />.
		/// </summary>
		/// <param name="obj">The object that sets the visibility of flyout items.</param>
		/// <param name="isVisible"><see langword="true"/> to set the flyout item as visible; otherwise, <see langword="false"/>.</param>
		public static void SetFlyoutItemIsVisible(BindableObject obj, bool isVisible) => obj.SetValue(FlyoutItemIsVisibleProperty, isVisible);
 
		static void OnFlyoutItemIsVisibleChanged(BindableObject bindable, object oldValue, object newValue)
		{
			if (bindable is Element element)
				element
					.FindParentOfType<Shell>()
					?.SendFlyoutItemsChanged();
 
			if (bindable is BaseShellItem baseShellItem && baseShellItem.FlyoutItemIsVisible != (bool)newValue)
				baseShellItem.FlyoutItemIsVisible = (bool)newValue;
		}
 
		/// <summary>
		/// Manages the bottom tab bar visibility.
		/// </summary>
		/// <remarks>
		/// The tab bar and tabs are visible in <see cref = "Shell" /> applications by default. 
		/// </remarks>
		public static readonly BindableProperty TabBarIsVisibleProperty =
			BindableProperty.CreateAttached("TabBarIsVisible", typeof(bool), typeof(Shell), true);
 
		/// <summary>
		/// Enables any <see cref = "View" /> to be displayed in the navigation bar.
		/// </summary>
		public static readonly BindableProperty TitleViewProperty =
			BindableProperty.CreateAttached("TitleView", typeof(View), typeof(Shell), null, propertyChanged: OnTitleViewChanged);
 
		/// <summary>
		/// Customizes the appearance of each <see cref = "MenuItem" />.
		/// </summary>
		public static readonly BindableProperty MenuItemTemplateProperty =
			BindableProperty.CreateAttached(nameof(MenuItemTemplate), typeof(DataTemplate), typeof(Shell), null, BindingMode.OneTime);
 
		/// <summary>
		/// Gets the <see cref = "DataTemplate" /> applied to <see cref = "MenuItem" /> objects in the MenuItems collection.
		/// </summary>
		/// <param name="obj">The object to get the <see cref="DataTemplate"/> from.</param>
		/// <returns>The <see cref = "DataTemplate" /> applied to <paramref name="obj"/>.</returns>
		public static DataTemplate GetMenuItemTemplate(BindableObject obj) => (DataTemplate)obj.GetValue(MenuItemTemplateProperty);
 
		/// <summary>
		/// Sets the <see cref = "DataTemplate" /> applied to <see cref = "MenuItem" /> objects in the MenuItems collection.
		/// Shell provides the Text and IconImageSource properties to the BindingContext of the <see cref = "MenuItemTemplate" />. 
		/// </summary>
		/// <remarks>
		/// Title can be used instead of Text, and Icon instead of IconImageSource. This allows reuse of the same template for menu items and flyout items.
		/// </remarks>
		/// <param name="obj">The object that sets the <see cref = "DataTemplate" /> applied to <see cref = "MenuItem" /> objects.</param>
		/// <param name="menuItemTemplate">The <see cref = "DataTemplate" /> applied to <see cref = "MenuItem" /> objects.</param>
		public static void SetMenuItemTemplate(BindableObject obj, DataTemplate menuItemTemplate) => obj.SetValue(MenuItemTemplateProperty, menuItemTemplate);
 
		/// <summary>
		///  The <see cref = "DataTemplate" /> applied to each <see cref = "FlyoutItem" /> object managed by Shell.
		/// </summary>
		public static readonly BindableProperty ItemTemplateProperty =
			BindableProperty.CreateAttached(nameof(ItemTemplate), typeof(DataTemplate), typeof(Shell), null, BindingMode.OneTime);
 
		/// <summary>
		/// Gets the <see cref = "DataTemplate" /> applied to each <see cref = "FlyoutItem" /> object managed by Shell.
		/// </summary>
		/// <param name="obj">The object that sets the <see cref = "DataTemplate" /> applied to Item objects.</param>
		/// <returns>The <see cref = "DataTemplate" /> applied to Item objects.</returns>
		public static DataTemplate GetItemTemplate(BindableObject obj) => (DataTemplate)obj.GetValue(ItemTemplateProperty);
 
		/// <summary>
		/// Sets the <see cref = "DataTemplate" /> applied to each <see cref = "FlyoutItem" /> object managed by Shell.
		/// </summary>
		/// <param name="obj">The object that sets the <see cref = "DataTemplate" /> applied to Item objects.</param>
		/// <param name="itemTemplate">The <see cref = "DataTemplate" /> applied to Item objects.</param>
		public static void SetItemTemplate(BindableObject obj, DataTemplate itemTemplate) => obj.SetValue(ItemTemplateProperty, itemTemplate);
 
		/// <include file="../../../docs/Microsoft.Maui.Controls/Shell.xml" path="//Member[@MemberName='GetBackButtonBehavior']/Docs/*" />
		public static BackButtonBehavior GetBackButtonBehavior(BindableObject obj) => (BackButtonBehavior)obj.GetValue(BackButtonBehaviorProperty);
 
		/// <summary>
		/// Sets the back button behavior when the given <paramref name="obj"/> is presented.
		/// </summary>
		/// <remarks>
		/// If the <paramref name="obj"/> is not a page, this property won't do anything.
		/// </remarks>
		/// <param name="obj">The page that dictates the Shell's back button behavior when active.</param>
		/// <param name="behavior">The back button behavior.</param>
		public static void SetBackButtonBehavior(BindableObject obj, BackButtonBehavior behavior) => obj.SetValue(BackButtonBehaviorProperty, behavior);
 
		/// <summary>
		/// Gets the navigation animation that occurs when a page is navigated to with the <see cref = "GoToAsync(ShellNavigationState, bool)" /> method.
		/// </summary>
		/// <param name="obj">The object that modifies the tabs visibility.</param>
		/// <returns>The navigation animation that occurs when a page is navigated to.</returns>
		public static PresentationMode GetPresentationMode(BindableObject obj) => (PresentationMode)obj.GetValue(PresentationModeProperty);
 
		/// <summary>
		/// Sets the navigation animation that plays when a <see cref="Page"/> is navigated to with the <see cref = "GoToAsync(ShellNavigationState, bool)" /> method.
		/// </summary>
		/// <param name="obj">The object that modifies the tabs visibility.</param>
		/// <param name="presentationMode">Defines the navigation animation that occurs when a page is navigated.</param>
		public static void SetPresentationMode(BindableObject obj, PresentationMode presentationMode) => obj.SetValue(PresentationModeProperty, presentationMode);
 
		/// <summary>
		/// Gets the behavior used to open the flyout when the given <paramref name="obj"/> is presented.
		/// </summary>
		/// <param name="obj">The object that modifies the Shell behavior used to open the flyout.</param>
		/// <returns>The behavior used to open the flyout.</returns>
		public static FlyoutBehavior GetFlyoutBehavior(BindableObject obj) => (FlyoutBehavior)obj.GetValue(FlyoutBehaviorProperty);
 
		/// <summary>
		/// Sets the behavior used to open the flyout when the given <paramref name="obj"/> is presented.
		/// </summary>
		/// <remarks>
		/// The flyout can be accessed through the hamburger icon or by swiping from the side of the screen.
		/// However, this behavior can be changed by setting the <see cref = "FlyoutBehavior" /> attached property.
		/// </remarks>
		/// <param name="obj">The object that modifies the Shell behavior used to open the flyout.</param>
		/// <param name="value">The behavior used to open the flyout.</param>
		public static void SetFlyoutBehavior(BindableObject obj, FlyoutBehavior value) => obj.SetValue(FlyoutBehaviorProperty, value);
 
		/// <summary>
		/// Gets the width of the flyout.
		/// </summary>
		/// <param name="obj">The object that modifies the width of the flyout.</param>
		/// <returns>The width of the flyout.</returns>
		public static double GetFlyoutWidth(BindableObject obj) => (double)obj.GetValue(FlyoutWidthProperty);
 
		/// <summary>
		/// Sets the width of the flyout when the given <paramref name="obj"/> is active.
		/// This enables scenarios such as expanding the flyout across the entire screen.
		/// </summary>
		/// <param name="obj">The object that modifies the width of the flyout.</param>
		/// <param name="value">Defines the width of the flyout.</param>
		public static void SetFlyoutWidth(BindableObject obj, double value) => obj.SetValue(FlyoutWidthProperty, value);
 
		/// <summary>
		/// Gets the height of the flyout when the given <paramref name="obj"/> is active.
		/// </summary>
		/// <param name="obj">The object that modifies the height of the flyout.</param>
		/// <returns>The height of the flyout.</returns>
		public static double GetFlyoutHeight(BindableObject obj) => (double)obj.GetValue(FlyoutHeightProperty);
 
		/// <summary>
		/// Sets the height of the flyout.
		/// </summary>
		/// <remarks>
		/// The height of the flyout can be customized by setting the Shell.FlyoutHeight attached properties to double value.
		/// This enables scenarios such as reducing the height of the flyout so that it doesn't obscure the tab bar.
		/// </remarks>
		/// <param name="obj">The object that modifies the height of the flyout.</param>
		/// <param name="value">Defines the height of the flyout.</param>
		public static void SetFlyoutHeight(BindableObject obj, double value) => obj.SetValue(FlyoutHeightProperty, value);
 
		/// <summary>
		/// Gets a value indicating if the navigation bar is visible when when the given <paramref name="obj"/> is active. 
		/// </summary>
		/// <param name="obj">The object that that gets the navigation bar visibility.</param>
		/// <returns><see langword="true"/> if the navigation bar is visible; otherwise, <see langword="false"/>.</returns>
		public static bool GetNavBarIsVisible(BindableObject obj) => (bool)obj.GetValue(NavBarIsVisibleProperty);
 
		/// <summary>
		/// Controls if the navigation bar is visible when the given <paramref name="obj"/> is presented. 
		/// By default the value of the property is <see langword="true"/>.
		/// </summary>
		/// <param name="obj">The object that modifies the navigation bar visibility.</param>
		/// <param name="value"><see langword="true"/> to set the navigation bar as visible; otherwise, <see langword="false"/>.</param>
		public static void SetNavBarIsVisible(BindableObject obj, bool value) => obj.SetValue(NavBarIsVisibleProperty, value);
 
		/// <summary>
		/// Gets a value that represents if the navigation bar has a shadow when the given <paramref name="obj"/> is active.
		/// </summary>
		/// <param name="obj">The object that modifies if the navigation bar has a shadow.</param>
		/// <returns><see langword="true"/> if the navigation bar has a shadow when <paramref name="obj"/> is presented, otherwise, <see langword="false"/>.</returns>
		public static bool GetNavBarHasShadow(BindableObject obj) => (bool)obj.GetValue(NavBarHasShadowProperty);
 
		/// <summary>
		/// Controls whether the navigation bar has a shadow when the given <paramref name="obj"/> is active. 
		/// By default the value of the property is <see langword="true"/> on Android, and <see langword="false"/> on other platforms.
		/// </summary>
		/// <param name="obj">The object that modifies if the navigation bar has a shadow.</param>
		/// <param name="value">Manages if the navigation bar has a shadow.</param>
		public static void SetNavBarHasShadow(BindableObject obj, bool value) => obj.SetValue(NavBarHasShadowProperty, value);
 
		/// <summary>
		/// Gets the integrated search functionality.
		/// </summary>
		/// <param name="obj">The object that modifies the Shell search functionality.</param>
		/// <returns>The integrated search functionality.</returns>
		public static SearchHandler GetSearchHandler(BindableObject obj) => (SearchHandler)obj.GetValue(SearchHandlerProperty);
 
		/// <summary>
		/// Sets the handler responsible for implementing the integrated search functionality for when the given <paramref name="obj"/> is active.
		/// Enabling this property results in a search box being added at the top of the page.
		/// </summary>
		/// <param name="obj">The object that modifies the Shell search functionality.</param>
		/// <param name="handler">Defines the integrated search functionality.</param>
		public static void SetSearchHandler(BindableObject obj, SearchHandler handler) => obj.SetValue(SearchHandlerProperty, handler);
 
		/// <summary>
		/// Gets the tabs visibility when the given <paramref name="obj"/> is active.
		/// </summary>
		/// <param name="obj">The object that modifies the tabs visibility.</param>
		/// <returns><see langword="true"/> if the tab bar is visible; otherwise, <see langword="false"/>.</returns>
		public static bool GetTabBarIsVisible(BindableObject obj) => (bool)obj.GetValue(TabBarIsVisibleProperty);
 
		/// <summary>
		/// Sets the tabs visibility when the given <paramref name="obj"/> is active.
		/// </summary>
		/// <remarks>
		/// The tab bar and tabs are visible in Shell applications by default. However, the tab bar can be hidden by setting the Shell.TabBarIsVisible attached property to false.
		/// While this property can be set on a subclassed Shell object, it's typically set on any ShellContent or ContentPage objects that want to make the tab bar invisible.
		/// </remarks>
		/// <param name="obj">The object that modifies the tabs visibility.</param>
		/// <param name="value"><see langword="true"/> to set the tab bar as visible; otherwise, <see langword="false"/>.</param>
		public static void SetTabBarIsVisible(BindableObject obj, bool value) => obj.SetValue(TabBarIsVisibleProperty, value);
 
		/// <summary>
		/// Gets any <see cref = "View" /> to be displayed in the navigation bar when the given <paramref name="obj"/> is active.
		/// </summary>
		/// <param name="obj">The object to which the TitleView is set.</param>
		/// <returns>The View to be displayed in the navigation bar.</returns>
		public static View GetTitleView(BindableObject obj) => (View)obj.GetValue(TitleViewProperty);
 
		/// <summary>
		/// Sets any <see cref = "View" /> to be displayed in the navigation bar when the given <paramref name="obj"/> is active.
		/// </summary>
		/// <param name="obj">The object to which the TitleView is set.</param>
		/// <param name="value">The View to be displayed in the navigation bar.</param>
		public static void SetTitleView(BindableObject obj, View value) => obj.SetValue(TitleViewProperty, value);
 
		static void OnFlyoutBehaviorChanged(BindableObject bindable, object oldValue, object newValue)
		{
			var element = (Element)bindable;
 
			while (!Application.IsApplicationOrWindowOrNull(element))
			{
				if (element is Shell shell)
					shell.NotifyFlyoutBehaviorObservers();
				element = element.Parent;
			}
		}
 
		/// <summary>
		/// Defines the background color in the Shell chrome. 
		/// The color won't fill in behind the Shell content.
		/// </summary>
		public static readonly new BindableProperty BackgroundColorProperty =
			BindableProperty.CreateAttached("BackgroundColor", typeof(Color), typeof(Shell), null,
				propertyChanged: OnShellAppearanceValueChanged);
 
		/// <summary>
		/// Defines the color to shade text and icons that are disabled.
		/// </summary>
		public static readonly BindableProperty DisabledColorProperty =
			BindableProperty.CreateAttached("DisabledColor", typeof(Color), typeof(Shell), null,
				propertyChanged: OnShellAppearanceValueChanged);
 
		/// <summary>
		/// Defines the color to shade text and icons.
		/// </summary>
		public static readonly BindableProperty ForegroundColorProperty =
			BindableProperty.CreateAttached("ForegroundColor", typeof(Color), typeof(Shell), null,
				propertyChanged: OnShellAppearanceValueChanged);
 
		/// <summary>
		/// Defines the background color for the tab bar. If the property is unset, the <see cref = "BackgroundColorProperty" /> value is used.
		/// </summary>
		public static readonly BindableProperty TabBarBackgroundColorProperty =
			BindableProperty.CreateAttached("TabBarBackgroundColor", typeof(Color), typeof(Shell), null,
				propertyChanged: OnShellAppearanceValueChanged);
 
		/// <summary>
		/// Defines the disabled color for the tab bar. If the property is unset, the <see cref = "DisabledColorProperty" /> value is used.
		/// </summary>
		public static readonly BindableProperty TabBarDisabledColorProperty =
			BindableProperty.CreateAttached("TabBarDisabledColor", typeof(Color), typeof(Shell), null,
				propertyChanged: OnShellAppearanceValueChanged);
 
		/// <summary>Bindable property for attached property <c>TabBarForegroundColor</c>.</summary>
		public static readonly BindableProperty TabBarForegroundColorProperty =
			BindableProperty.CreateAttached("TabBarForegroundColor", typeof(Color), typeof(Shell), null,
				propertyChanged: OnShellAppearanceValueChanged);
 
		/// <summary>
		/// Defines the title color for the tab bar. If the property is unset, the <see cref = "TitleColorProperty" /> value will be used.
		/// </summary>
		public static readonly BindableProperty TabBarTitleColorProperty =
			BindableProperty.CreateAttached("TabBarTitleColor", typeof(Color), typeof(Shell), null,
				propertyChanged: OnShellAppearanceValueChanged);
 
		/// <summary>
		/// Defines the unselected color for the tab bar. If the property is unset, the <see cref = "UnselectedColorProperty" /> value is used.
		/// </summary>
		public static readonly BindableProperty TabBarUnselectedColorProperty =
			BindableProperty.CreateAttached("TabBarUnselectedColor", typeof(Color), typeof(Shell), null,
				propertyChanged: OnShellAppearanceValueChanged);
 
		/// <summary>
		/// Defines the title color for the tab bar. If the property is unset, the <see cref = "TitleColorProperty" /> value will be used.
		/// </summary>
		public static readonly BindableProperty TitleColorProperty =
			BindableProperty.CreateAttached("TitleColor", typeof(Color), typeof(Shell), null,
				propertyChanged: OnShellAppearanceValueChanged);
 
		/// <summary>
		/// Defines the unselected color for the tab bar. If the property is unset, the <see cref = "UnselectedColorProperty" /> value is used.
		/// </summary>
		public static readonly BindableProperty UnselectedColorProperty =
			BindableProperty.CreateAttached("UnselectedColor", typeof(Color), typeof(Shell), null,
				propertyChanged: OnShellAppearanceValueChanged);
 
		/// <summary>
		/// The backdrop of the flyout, which is the appearance of the flyout overlay.
		/// </summary>
		public static readonly BindableProperty FlyoutBackdropProperty =
			BindableProperty.CreateAttached(nameof(FlyoutBackdrop), typeof(Brush), typeof(Shell), Brush.Default,
				propertyChanged: OnShellAppearanceValueChanged);
 
		/// <summary>
		/// The width of the flyout.
		/// This enables scenarios such as expanding the flyout across the entire screen.
		/// </summary>
		public static readonly BindableProperty FlyoutWidthProperty =
			BindableProperty.CreateAttached(nameof(FlyoutWidth), typeof(double), typeof(Shell), -1d,
				propertyChanged: OnShellAppearanceValueChanged);
 
		/// <summary>
		/// The height of the flyout.
		/// This enables scenarios such as reducing the height of the flyout so that it doesn't obscure the tab bar.
		/// </summary>
		public static readonly BindableProperty FlyoutHeightProperty =
			BindableProperty.CreateAttached(nameof(FlyoutHeight), typeof(double), typeof(Shell), -1d,
				propertyChanged: OnShellAppearanceValueChanged);
 
		/// <summary>
		/// Gets the background color in the Shell chrome. 
		/// </summary>
		/// <param name="obj">The object to which the background color is set.</param>
		/// <returns>The background color from the Shell chrome.</returns>
		public static Color GetBackgroundColor(BindableObject obj) => (Color)obj.GetValue(BackgroundColorProperty);
 
		/// <summary>
		/// Sets the background color in the Shell chrome. 
		/// The color won't fill in behind the Shell content.
		/// </summary>
		/// <param name="obj">The object to which the background color is set.</param>
		/// <param name="value">The background color for the Shell chrome.</param>
		public static void SetBackgroundColor(BindableObject obj, Color value) => obj.SetValue(BackgroundColorProperty, value);
 
		/// <summary>
		/// Gets the color to shade text and icons that are disabled.
		/// </summary>
		/// <param name="obj">The object to which the disabled color is set.</param>
		/// <returns>The disabled color for the tab bar.</returns>
		public static Color GetDisabledColor(BindableObject obj) => (Color)obj.GetValue(DisabledColorProperty);
 
		/// <summary>
		/// Sets the color to shade text and icons that are disabled.
		/// </summary>
		/// <param name="obj">The object to which the disabled color is set.</param>
		/// <param name="value">The disabled color for the tab bar.</param>
		public static void SetDisabledColor(BindableObject obj, Color value) => obj.SetValue(DisabledColorProperty, value);
 
		/// <summary>
		/// Gets the foreground color for the tab bar. 
		/// </summary>
		/// <param name="obj">The object to which the foreground color is set.</param>
		/// <returns>The foreground color for the tab bar.</returns>
		public static Color GetForegroundColor(BindableObject obj) => (Color)obj.GetValue(ForegroundColorProperty);
 
		/// <summary>
		/// Defines the foreground color for the tab bar. 
		/// If the property is unset, the <see cref = "ForegroundColorProperty" /> value is used.
		/// </summary>
		/// <param name="obj">The object to which the foreground color is set.</param>
		/// <param name="value">The foreground color for the tab bar.</param>
		public static void SetForegroundColor(BindableObject obj, Color value) => obj.SetValue(ForegroundColorProperty, value);
 
		/// <summary>
		/// Gets the background color for the tab bar.
		/// </summary>
		/// <param name="obj">The object to which the color is set.</param>
		/// <returns>The background color for the tab bar.</returns>
		public static Color GetTabBarBackgroundColor(BindableObject obj) => (Color)obj.GetValue(TabBarBackgroundColorProperty);
 
		/// <summary>
		/// Sets the background color for the tab bar. 
		/// If the property is unset, the BackgroundColor property value is used.
		/// </summary>
		/// <param name="obj">The object to which the color is set.</param>
		/// <param name="value">The background color for the tab bar.</param>
		public static void SetTabBarBackgroundColor(BindableObject obj, Color value) => obj.SetValue(TabBarBackgroundColorProperty, value);
 
		/// <summary>
		/// Gets the background color for the tab bar. 
		/// </summary>
		/// <param name="obj">The object to which the color is set.</param>
		/// <returns>The background color for the tab bar.</returns>
		public static Color GetTabBarDisabledColor(BindableObject obj) => (Color)obj.GetValue(TabBarDisabledColorProperty);
 
		/// <summary>
		/// Sets the disabled color for the tab bar. 
		/// If the property is unset, the <see cref = "DisabledColorProperty" /> value is used.
		/// </summary>
		/// <param name="obj">The object to which the color is set.</param>
		/// <param name="value">The color to set for the tab bar is disabled.</param>
		public static void SetTabBarDisabledColor(BindableObject obj, Color value) => obj.SetValue(TabBarDisabledColorProperty, value);
 
		/// <summary>
		/// Gets the color of the tab bar when its disabled. 
		/// </summary>
		/// <param name="obj">The object to which the color is set.</param>
		/// <returns>The disabled color for the tab bar.</returns>
		public static Color GetTabBarForegroundColor(BindableObject obj) => (Color)obj.GetValue(TabBarForegroundColorProperty);
 
		/// <summary>
		/// Sets the foreground color for the tab bar. 
		/// If the property is unset, the ForegroundColor property value is used.
		/// </summary>
		/// <param name="obj">The object to which the color is set.</param>
		/// <param name="value">The foreground color for the tab bar.</param>
		public static void SetTabBarForegroundColor(BindableObject obj, Color value) => obj.SetValue(TabBarForegroundColorProperty, value);
 
		/// <summary>
		/// Gets the foreground color for the tab bar. 
		/// </summary>
		/// <param name="obj">The object to which the color is set.</param>
		/// <returns>The foreground color for the tab bar.</returns>
		public static Color GetTabBarTitleColor(BindableObject obj) => (Color)obj.GetValue(TabBarTitleColorProperty);
 
		/// <summary>
		/// Sets the title color for the tab bar. 
		/// If the property is unset, the <see cref="TitleColorProperty" /> value will be used.
		/// </summary>
		/// <param name="obj">The object to which the color is set.</param>
		/// <param name="value">The title color for the tab bar.</param>
		public static void SetTabBarTitleColor(BindableObject obj, Color value) => obj.SetValue(TabBarTitleColorProperty, value);
 
		/// <summary>
		/// Gets the title color for the tab bar. 
		/// </summary>
		/// <param name="obj">The object to which the color is set.</param>
		/// <returns>The title color for the tab bar.</returns>
		public static Color GetTabBarUnselectedColor(BindableObject obj) => (Color)obj.GetValue(TabBarUnselectedColorProperty);
 
		/// <summary>
		/// Sets the unselected color for the tab bar. 
		/// If the property is unset, the UnselectedColor property value is used.
		/// </summary>
		/// <param name="obj">The object to which the color is set.</param>
		/// <param name="value">The unselected color for the tab bar.</param>
		public static void SetTabBarUnselectedColor(BindableObject obj, Color value) => obj.SetValue(TabBarUnselectedColorProperty, value);
 
		/// <summary>
		/// Gets the color used for the title of the current page.
		/// </summary>
		/// <param name="obj">The object to which the color is set.</param>
		/// <returns>The unselected color for the tab bar.</returns>
		public static Color GetTitleColor(BindableObject obj) => (Color)obj.GetValue(TitleColorProperty);
 
		/// <summary>
		/// Sets the color used for the title of the current page.
		/// </summary>
		/// <param name="obj">The object to which the color is set.</param>
		/// <param name="value">The color used for the title of the current page.</param>
		public static void SetTitleColor(BindableObject obj, Color value) => obj.SetValue(TitleColorProperty, value);
 
		/// <summary>
		/// Gets the color for unselected text and icons in the Shell chrome.
		/// </summary>
		/// <param name="obj">The object to which the color is set.</param>
		/// <returns>The color for unselected text and icons in the Shell chrome.</returns>
		public static Color GetUnselectedColor(BindableObject obj) => (Color)obj.GetValue(UnselectedColorProperty);
 
		/// <summary>
		/// Sets the color for unselected text and icons in the Shell chrome.
		/// </summary>
		/// <param name="obj">The object to which the color is set.</param>
		/// <param name="value">The color for unselected text and icons in the Shell chrome.</param>
		public static void SetUnselectedColor(BindableObject obj, Color value) => obj.SetValue(UnselectedColorProperty, value);
 
		/// <summary>
		/// Gets the color for unselected text and icons in the Shell chrome.
		/// </summary>
		/// <param name="obj">The object to which the color is set.</param>
		/// <returns>The color for unselected text and icons in the Shell chrome.</returns>
		public static Brush GetFlyoutBackdrop(BindableObject obj) => (Brush)obj.GetValue(FlyoutBackdropProperty);
 
		/// <summary>
		/// Sets the backdrop of the flyout, which is the appearance of the flyout overlay.
		/// </summary>
		/// <param name="obj">The object that sets the backdrop brush.</param>
		/// <param name="value">The brushed used in the backdrop of the flyout.</param>
		public static void SetFlyoutBackdrop(BindableObject obj, Brush value) => obj.SetValue(FlyoutBackdropProperty, value);
 
		static void OnShellAppearanceValueChanged(BindableObject bindable, object oldValue, object newValue)
		{
			var item = (Element)bindable;
			var source = item;
 
			while (!Application.IsApplicationOrWindowOrNull(item))
			{
				if (item is IShellController shell)
				{
					shell.AppearanceChanged(source, true);
					return;
				}
				item = item.Parent;
			}
		}
 
		static readonly BindablePropertyKey CurrentStatePropertyKey =
			BindableProperty.CreateReadOnly(nameof(CurrentState), typeof(ShellNavigationState), typeof(Shell), null);
 
		static readonly BindablePropertyKey ItemsPropertyKey = BindableProperty.CreateReadOnly(nameof(Items), typeof(ShellItemCollection), typeof(Shell), null,
				defaultValueCreator: bo => new ShellItemCollection { Inner = new ElementCollection<ShellItem>(((Shell)bo).InternalChildren) });
 
		List<(IAppearanceObserver Observer, Element Pivot)> _appearanceObservers = new List<(IAppearanceObserver Observer, Element Pivot)>();
		List<IFlyoutBehaviorObserver> _flyoutBehaviorObservers = new List<IFlyoutBehaviorObserver>();
 
 
		internal static BindableObject GetBindableObjectWithFlyoutItemTemplate(BindableObject bo)
		{
			if (bo is IMenuItemController)
			{
				if (bo is MenuItem mi && mi.Parent != null && mi.Parent.IsSet(MenuItemTemplateProperty))
					return mi.Parent;
				else if (bo is MenuShellItem msi && msi.MenuItem != null && msi.MenuItem.IsSet(MenuItemTemplateProperty))
					return msi.MenuItem;
			}
 
			return bo;
		}
 
		DataTemplate IShellController.GetFlyoutItemDataTemplate(BindableObject bo)
		{
			BindableProperty bp = bo is IMenuItemController ? MenuItemTemplateProperty : ItemTemplateProperty;
			var bindableObjectWithTemplate = GetBindableObjectWithFlyoutItemTemplate(bo);
 
			if (bindableObjectWithTemplate.IsSet(bp))
			{
				return (DataTemplate)bindableObjectWithTemplate.GetValue(bp);
			}
 
			if (IsSet(bp))
			{
				return (DataTemplate)GetValue(bp);
			}
 
			return BaseShellItem.CreateDefaultFlyoutItemCell(bo);
		}
 
		event EventHandler IShellController.StructureChanged
		{
			add { _structureChanged += value; }
			remove { _structureChanged -= value; }
		}
 
		event EventHandler _structureChanged;
 
		event EventHandler IShellController.FlyoutItemsChanged
		{
			add { _flyoutManager.FlyoutItemsChanged += value; }
			remove { _flyoutManager.FlyoutItemsChanged -= value; }
		}
 
		View IShellController.FlyoutHeader => FlyoutHeaderView;
		View IShellController.FlyoutFooter => FlyoutFooterView;
		View IShellController.FlyoutContent => FlyoutContentView;
 
		IShellController ShellController => this;
 
		void IShellController.AddAppearanceObserver(IAppearanceObserver observer, Element pivot)
		{
			_appearanceObservers.Add((observer, pivot));
			var appearance = GetAppearanceForPivot(pivot);
			UpdateToolbarAppearanceFeatures(pivot, appearance);
			observer.OnAppearanceChanged(appearance);
		}
 
		void IShellController.AddFlyoutBehaviorObserver(IFlyoutBehaviorObserver observer)
		{
			_flyoutBehaviorObservers.Add(observer);
 
			// We need to wait until the visible page has been created before we try to calculate
			// the flyout behavior
			if (GetVisiblePage() != null)
				observer.OnFlyoutBehaviorChanged(GetEffectiveFlyoutBehavior());
		}
 
		void UpdateToolbarAppearanceFeatures(Element pivot, ShellAppearance appearance)
		{
			// Android sets these inside its renderer
			// once we convert Android to be all handlers we can adjust
			if (pivot is ShellContent || pivot is ShellSection || pivot is ContentPage)
			{
				appearance = appearance ?? GetAppearanceForPivot(pivot);
				Toolbar.BarTextColor = appearance?.TitleColor ?? DefaultTitleColor;
				Toolbar.BarBackground = appearance?.BackgroundColor ?? DefaultBackgroundColor;
				Toolbar.IconColor = appearance?.ForegroundColor ?? DefaultForegroundColor;
			}
		}
 
#if ANDROID
		static Color DefaultBackgroundColor => ResolveThemeColor(Color.FromArgb("#2c3e50"), Color.FromArgb("#1B3147"));
		static readonly Color DefaultForegroundColor = Colors.White;
		static readonly Color DefaultTitleColor = Colors.White;
 
		static bool IsDarkTheme => (Application.Current?.RequestedTheme == AppTheme.Dark);
 
		static Color ResolveThemeColor(Color light, Color dark)
		{
			if (IsDarkTheme)
			{
				return dark;
			}
 
			return light;
		}
#else
		static Color DefaultBackgroundColor => null;
		static readonly Color DefaultForegroundColor = null;
		static readonly Color DefaultTitleColor = null;
#endif
 
 
		void IShellController.AppearanceChanged(Element source, bool appearanceSet)
		{
			if (!appearanceSet)
			{
				// This bubbles up whenever there is an kind of structure/page change
				// So its also quite useful for checking the FlyoutBehavior conditions
				NotifyFlyoutBehaviorObservers();
			}
 
			UpdateToolbarAppearanceFeatures(source, null);
 
			// here we wish to notify every element whose "pivot line" contains the source
			// To do that we first need to find the leaf node in the line, and then walk up
			// to see if we find the source on the way up.
 
			// If this is not an appearanceSet event but just a structural change, then we only
			// need walk up from the source to look for the pivot as items below the change
			// can't be affected by it
 
			foreach (var (Observer, Pivot) in _appearanceObservers)
			{
				var observer = Observer;
				var pivot = Pivot;
 
				Element target;
				Element leaf;
				if (appearanceSet)
				{
					leaf = WalkToPage(pivot);
					target = source;
				}
				else
				{
					leaf = source;
					target = pivot;
				}
 
				while (!Application.IsApplicationOrWindowOrNull(leaf))
				{
					if (leaf == target)
					{
						var appearance = GetAppearanceForPivot(pivot);
						UpdateToolbarAppearanceFeatures(pivot, appearance);
						observer.OnAppearanceChanged(appearance);
						break;
					}
 
					leaf = leaf.Parent;
				}
			}
		}
 
		ShellNavigationState IShellController.GetNavigationState(ShellItem shellItem, ShellSection shellSection, ShellContent shellContent, bool includeStack)
			=> ShellNavigationManager.GetNavigationState(shellItem, shellSection, shellContent, includeStack ? shellSection.Stack.ToList() : null, includeStack ? shellSection.Navigation.ModalStack.ToList() : null);
 
		void OnFlyoutItemSelected(Element element, bool platformInitiated) =>
			OnFlyoutItemSelectedAsync(element, platformInitiated).FireAndForget();
 
		void IShellController.OnFlyoutItemSelected(Element element) =>
			OnFlyoutItemSelected(element, true);
 
		Task IShellController.OnFlyoutItemSelectedAsync(Element element) =>
			OnFlyoutItemSelectedAsync(element, true);
 
		Task OnFlyoutItemSelectedAsync(Element element, bool platformInitiated)
		{
			ShellItem shellItem = null;
			ShellSection shellSection = null;
			ShellContent shellContent = null;
 
			switch (element)
			{
				case MenuShellItem menuShellItem:
					((IMenuItemController)menuShellItem.MenuItem).Activate();
					break;
				case ShellItem i:
					shellItem = i;
					break;
				case ShellSection s:
					shellItem = s.Parent as ShellItem;
					shellSection = s;
					break;
				case ShellContent c:
					shellItem = c.Parent.Parent as ShellItem;
					shellSection = c.Parent as ShellSection;
					shellContent = c;
					break;
				case MenuItem m:
					((IMenuItemController)m).Activate();
					break;
			}
 
			if (shellItem == null || !shellItem.IsEnabled)
				return Task.CompletedTask;
 
			shellSection = shellSection ?? shellItem.CurrentItem;
			shellContent = shellContent ?? shellSection?.CurrentItem;
 
			if (platformInitiated && FlyoutIsPresented && GetEffectiveFlyoutBehavior() != FlyoutBehavior.Locked)
				SetValueFromRenderer(FlyoutIsPresentedProperty, false);
 
			if (shellSection == null)
				shellItem.PropertyChanged += OnShellItemPropertyChanged;
			else if (shellContent == null)
				shellSection.PropertyChanged += OnShellItemPropertyChanged;
			else
			{
				if (this.CurrentItem == null)
				{
					var state = ShellNavigationManager.GetNavigationState(shellItem, shellSection, shellContent, shellSection.Navigation.NavigationStack, null);
					var requestBuilder = new RouteRequestBuilder(new List<string>()
					{
						shellItem.Route,
						shellSection.Route,
						shellContent.Route
					});
 
					var node = new ShellUriHandler.NodeLocation();
					node.SetNode(shellContent);
					requestBuilder.AddMatch(node);
 
					var navRequest =
						new ShellNavigationRequest(
							new RequestDefinition(requestBuilder, this),
							 ShellNavigationRequest.WhatToDoWithTheStack.ReplaceIt,
							 String.Empty,
							 String.Empty);
 
					var navParameters = new ShellNavigationParameters()
					{
						TargetState = state,
						Animated = false,
						EnableRelativeShellRoutes = false,
						DeferredArgs = null,
						Parameters = null
					};
 
					return _navigationManager
						.GoToAsync(navParameters, navRequest);
 
				}
				else
				{
					var navParameters = ShellNavigationManager.GetNavigationParameters(shellItem, shellSection, shellContent, shellSection.Navigation.NavigationStack, null);
 
					return _navigationManager
						.GoToAsync(navParameters);
				}
			}
 
			return Task.CompletedTask;
		}
 
		void OnShellItemPropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			if (e.PropertyName == CurrentItemProperty.PropertyName)
			{
				(sender as BindableObject).PropertyChanged -= OnShellItemPropertyChanged;
				if (sender is ShellItem item)
					OnFlyoutItemSelected(item, false);
				else if (sender is ShellSection section)
					OnFlyoutItemSelected(section.Parent, false);
			}
		}
 
		bool IShellController.ProposeNavigation(ShellNavigationSource source, ShellItem shellItem, ShellSection shellSection, ShellContent shellContent, IReadOnlyList<Page> stack, bool canCancel)
		{
			return _navigationManager.ProposeNavigationOutsideGotoAsync(source, shellItem, shellSection, shellContent, stack, canCancel, true);
		}
 
		bool IShellController.RemoveAppearanceObserver(IAppearanceObserver observer)
		{
			for (int i = 0; i < _appearanceObservers.Count; i++)
			{
				if (_appearanceObservers[i].Observer == observer)
				{
					_appearanceObservers.RemoveAt(i);
					return true;
				}
			}
 
			return false;
		}
 
		bool IShellController.RemoveFlyoutBehaviorObserver(IFlyoutBehaviorObserver observer)
			=> _flyoutBehaviorObservers.Remove(observer);
 
		void IShellController.UpdateCurrentState(ShellNavigationSource source)
		{
			var oldState = CurrentState;
			var shellItem = CurrentItem;
			var shellSection = shellItem?.CurrentItem;
			var shellContent = shellSection?.CurrentItem;
			var stack = shellSection?.Stack;
			var modalStack = shellSection?.Navigation?.ModalStack;
			var result = ShellNavigationManager.GetNavigationState(shellItem, shellSection, shellContent, stack, modalStack);
 
			if (result?.Location != oldState?.Location)
			{
				SetValueFromRenderer(CurrentStatePropertyKey, result);
				_navigationManager.HandleNavigated(new ShellNavigatedEventArgs(oldState, CurrentState, source));
			}
		}
 
		ReadOnlyCollection<ShellItem> IShellController.GetItems() => ((ShellItemCollection)Items).VisibleItemsReadOnly;
 
		event NotifyCollectionChangedEventHandler IShellController.ItemsCollectionChanged
		{
			add { ((ShellItemCollection)Items).VisibleItemsChanged += value; }
			remove { ((ShellItemCollection)Items).VisibleItemsChanged -= value; }
		}
 
		/// <include file="../../../docs/Microsoft.Maui.Controls/Shell.xml" path="//Member[@MemberName='Current']/Docs/*" />
		public static Shell Current
		{
			get
			{
				if (Application.Current is null || Application.Current.Windows.Count == 0)
					return null;
 
				if (Application.Current.Windows.Count == 1)
				{
					return Application.Current.Windows[0].Page as Shell;
				}
 
				// Check if shell is activated
				Shell currentShell = null;
				Shell returnIfThereIsJustOneShell = null;
				bool tooManyShells = false;
				foreach (var window in Application.Current.Windows)
				{
					if (window.Page is Shell shell)
					{
						if (window.IsActivated)
						{
							if (currentShell is not null)
							{
								currentShell = null;
								break;
							}
 
							currentShell = shell;
						}
 
						if (returnIfThereIsJustOneShell is not null)
						{
							tooManyShells = true;
						}
					}
				}
 
				if (currentShell is not null)
				{
					return currentShell;
				}
 
				if (!tooManyShells && returnIfThereIsJustOneShell is not null)
				{
					return returnIfThereIsJustOneShell;
				}
 
				throw new InvalidOperationException($"Unable to determine the current Shell instance you want to use. Please access Shell via the Windows property on {Application.Current.GetType()}.");
			}
		}
 
		internal ShellNavigationManager NavigationManager => _navigationManager;
		/// <include file="../../../docs/Microsoft.Maui.Controls/Shell.xml" path="//Member[@MemberName='GoToAsync'][1]/Docs/*" />
		public Task GoToAsync(ShellNavigationState state)
		{
			return _navigationManager.GoToAsync(state, null, false);
		}
 
		/// <include file="../../../docs/Microsoft.Maui.Controls/Shell.xml" path="//Member[@MemberName='GoToAsync'][2]/Docs/*" />
		public Task GoToAsync(ShellNavigationState state, bool animate)
		{
			return _navigationManager.GoToAsync(state, animate, false);
		}
 
		/// <include file="../../../docs/Microsoft.Maui.Controls/Shell.xml" path="//Member[@MemberName='GoToAsync'][1]/Docs/*" />
#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
		public Task GoToAsync(ShellNavigationState state, IDictionary<string, object> parameters)
#pragma warning restore CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
		{
			return _navigationManager.GoToAsync(state, null, false, parameters: new ShellRouteParameters(parameters));
		}
 
		/// <include file="../../../docs/Microsoft.Maui.Controls/Shell.xml" path="//Member[@MemberName='GoToAsync'][2]/Docs/*" />
#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
		public Task GoToAsync(ShellNavigationState state, bool animate, IDictionary<string, object> parameters)
#pragma warning restore CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
		{
			return _navigationManager.GoToAsync(state, animate, false, parameters: new ShellRouteParameters(parameters));
		}
 
		/// <summary>
		/// This method navigates to a <see cref="ShellNavigationState" /> and returns a <see cref="Task" /> that will complete once the navigation animation.
		/// </summary>
		/// <param name="state">Defines the path for Shell to navigate to.</param>
		/// <param name="shellNavigationQueryParameters">Parameters to use for this specific navigation operation.</param>
		/// <returns></returns>
		public Task GoToAsync(ShellNavigationState state, ShellNavigationQueryParameters shellNavigationQueryParameters)
		{
			return _navigationManager.GoToAsync(state, null, false, parameters: new ShellRouteParameters(shellNavigationQueryParameters));
		}
 
		/// <summary>
		/// This method navigates to a <see cref="ShellNavigationState" /> and returns a <see cref="Task" />.
		/// </summary>
		/// <param name="state">Defines the path for Shell to navigate to.</param>
		/// <param name="animate">Indicates if your transition is animated</param>
		/// <param name="shellNavigationQueryParameters">Parameters to use for this specific navigation operation.</param>
		/// <returns></returns>
		public Task GoToAsync(ShellNavigationState state, bool animate, ShellNavigationQueryParameters shellNavigationQueryParameters)
		{
			return _navigationManager.GoToAsync(state, animate, false, parameters: new ShellRouteParameters(shellNavigationQueryParameters));
		}
 
		/// <summary>
		/// The currently selected ShellItem.
		/// </summary>
		public static readonly BindableProperty CurrentItemProperty =
			BindableProperty.Create(nameof(CurrentItem), typeof(ShellItem), typeof(Shell), null, BindingMode.TwoWay,
				propertyChanging: OnCurrentItemChanging,
				propertyChanged: OnCurrentItemChanged);
 
		/// <summary>Bindable property for <see cref="CurrentState"/>.</summary>
		public static readonly BindableProperty CurrentStateProperty = CurrentStatePropertyKey.BindableProperty;
 
		/// <summary>
		/// Sets the flyout background image, of type ImageSource, to a file, embedded resource, URI, or stream.
		/// </summary>
		/// <remarks>
		/// The flyout background image appears beneath the flyout header and behind any flyout items, menu items, and the flyout footer. 
		/// </remarks>
		public static readonly BindableProperty FlyoutBackgroundImageProperty =
			BindableProperty.Create(nameof(FlyoutBackgroundImage), typeof(ImageSource), typeof(Shell), default(ImageSource), BindingMode.OneTime);
 
		/// <summary>
		/// The aspect ratio of the background image.
		/// </summary>
		public static readonly BindableProperty FlyoutBackgroundImageAspectProperty =
			BindableProperty.Create(nameof(FlyoutBackgroundImageAspect), typeof(Aspect), typeof(Shell), default(Aspect), BindingMode.OneTime);
 
		/// <summary>
		/// The background color of the Shell Flyout.
		/// </summary>
		public static readonly BindableProperty FlyoutBackgroundColorProperty =
			BindableProperty.Create(nameof(FlyoutBackgroundColor), typeof(Color), typeof(Shell), null, BindingMode.OneTime);
 
		/// <summary>Bindable property for <see cref="FlyoutBackground"/>.</summary>
		public static readonly BindableProperty FlyoutBackgroundProperty =
			BindableProperty.Create(nameof(FlyoutBackground), typeof(Brush), typeof(Shell), SolidColorBrush.Default, BindingMode.OneTime);
 
		/// <summary>Bindable property for <see cref="FlyoutHeaderBehavior"/>.</summary>
		public static readonly BindableProperty FlyoutHeaderBehaviorProperty =
			BindableProperty.Create(nameof(FlyoutHeaderBehavior), typeof(FlyoutHeaderBehavior), typeof(Shell), FlyoutHeaderBehavior.Default, BindingMode.OneTime);
 
		/// <summary>
		/// The flyout header appearance.
		/// The flyout header is the content that optionally appears at the top of the flyout.
		/// </summary>
		public static readonly BindableProperty FlyoutHeaderProperty =
			BindableProperty.Create(nameof(FlyoutHeader), typeof(object), typeof(Shell), null, BindingMode.OneTime,
				propertyChanging: OnFlyoutHeaderChanging);
 
		/// <summary>
		/// The flyout footer appearance.
		/// The flyout footer is the content that optionally appears at the bottom of the flyout.
		/// </summary>
		public static readonly BindableProperty FlyoutFooterProperty =
			BindableProperty.Create(nameof(FlyoutFooter), typeof(object), typeof(Shell), null, BindingMode.OneTime,
				propertyChanging: OnFlyoutFooterChanging);
 
		/// <summary>
		/// The flyout header appearance can be defined by setting a <see cref = "DataTemplate" />.
		/// </summary>
		public static readonly BindableProperty FlyoutHeaderTemplateProperty =
			BindableProperty.Create(nameof(FlyoutHeaderTemplate), typeof(DataTemplate), typeof(Shell), null, BindingMode.OneTime,
				propertyChanging: OnFlyoutHeaderTemplateChanging);
 
		/// <summary>
		/// The flyout footer appearance can be defined by setting a <see cref = "DataTemplate" />.
		/// </summary>
		public static readonly BindableProperty FlyoutFooterTemplateProperty =
			BindableProperty.Create(nameof(FlyoutFooterTemplate), typeof(DataTemplate), typeof(Shell), null, BindingMode.OneTime,
				propertyChanging: OnFlyoutFooterTemplateChanging);
 
		/// <summary>
		/// The flyout can be programmatically opened and closed by setting the FlyoutIsPresented property to a boolean value that indicates whether the flyout is currently open.
		/// </summary>
		public static readonly BindableProperty FlyoutIsPresentedProperty =
			BindableProperty.Create(nameof(FlyoutIsPresented), typeof(bool), typeof(Shell), false, BindingMode.TwoWay);
 
		/// <summary>Bindable property for <see cref="Items"/>.</summary>
		public static readonly BindableProperty ItemsProperty = ItemsPropertyKey.BindableProperty;
 
		/// <summary>
		/// By default, Shell applications have a hamburger icon which, when pressed, opens the flyout.
		/// This icon can be changed by setting the FlyoutIcon property.
		/// </summary>
		public static readonly BindableProperty FlyoutIconProperty =
			BindableProperty.Create(nameof(FlyoutIcon), typeof(ImageSource), typeof(Shell), null);
 
		/// <summary>
		/// Modifies the behavior of the flyout scroll.
		/// By default, a flyout can be scrolled vertically when the flyout items don't fit in the flyout. 
		/// </summary>
		public static readonly BindableProperty FlyoutVerticalScrollModeProperty =
			BindableProperty.Create(nameof(FlyoutVerticalScrollMode), typeof(ScrollMode), typeof(Shell), ScrollMode.Auto);
 
		View _flyoutHeaderView;
		View _flyoutFooterView;
		ShellNavigationManager _navigationManager;
		ShellFlyoutItemsManager _flyoutManager;
		Page _previousPage;
 
		/// <include file="../../../docs/Microsoft.Maui.Controls/Shell.xml" path="//Member[@MemberName='.ctor']/Docs/*" />
		public Shell()
		{
			Toolbar = new ShellToolbar(this);
 
			_navigationManager = new ShellNavigationManager(this);
			_navigationManager.Navigated += (_, args) => SendNavigated(args);
			_navigationManager.Navigating += (_, args) => SendNavigating(args);
 
			_flyoutManager = new ShellFlyoutItemsManager(this);
			Navigation = new NavigationImpl(this);
			Route = Routing.GenerateImplicitRoute("shell");
			Initialize();
 
			if (Application.Current != null)
			{
				this.SetBinding(Shell.FlyoutBackgroundColorProperty,
					new AppThemeBinding { Light = Colors.White, Dark = Colors.Black, Mode = BindingMode.OneWay });
			}
 
			ShellController.FlyoutItemsChanged += (_, __) => Handler?.UpdateValue(nameof(FlyoutItems));
			ShellController.ItemsCollectionChanged += (_, __) => Handler?.UpdateValue(nameof(Items));
		}
 
		private protected override void OnHandlerChangingCore(HandlerChangingEventArgs args)
		{
			base.OnHandlerChangingCore(args);
 
			if (Application.Current == null)
				return;
 
			if (args.NewHandler == null)
#pragma warning disable CS0618 // Type or member is obsolete
				Application.Current.RequestedThemeChanged -= OnRequestedThemeChanged;
 
			if (args.NewHandler != null && args.OldHandler == null)
				Application.Current.RequestedThemeChanged += OnRequestedThemeChanged;
#pragma warning restore CS0618 // Type or member is obsolete
		}
 
		private void OnRequestedThemeChanged(object sender, AppThemeChangedEventArgs e)
		{
			ShellController.AppearanceChanged(CurrentPage, false);
		}
 
		void Initialize()
		{
			if (CurrentItem != null)
				SetCurrentItem()
					.FireAndForget();
 
			((ShellElementCollection)Items).VisibleItemsChangedInternal += async (s, e) =>
			{
				await SetCurrentItem();
 
				SendStructureChanged();
				SendFlyoutItemsChanged();
			};
 
			async Task SetCurrentItem()
			{
				var shellItems = ShellController.GetItems();
 
				if (CurrentItem != null && shellItems.Contains(CurrentItem))
					return;
 
				ShellItem shellItem = null;
 
				// If shell item has been removed try to renavigate to current location
				// Just in case the item was replaced. This is mainly relevant for hot reload
				if (CurrentItem != null)
				{
					try
					{
						var location = CurrentState.Location;
						var navRequest = ShellUriHandler.GetNavigationRequest(this, ((ShellNavigationState)location).FullLocation, false, false);
 
						if (navRequest != null)
						{
							var item = navRequest.Request.Item;
							var section = navRequest.Request.Section;
							var Content = navRequest.Request.Content;
 
							if (IsValidRoute(item) && IsValidRoute(section) && IsValidRoute(Content))
							{
								await GoToAsync(location, false);
								return;
							}
 
							bool IsValidRoute(BaseShellItem baseShellItem)
							{
								if (baseShellItem == null)
									return true;
 
								if (!baseShellItem.IsVisible)
									return false;
 
								return baseShellItem.IsPartOfVisibleTree();
							}
						}
					}
					catch (Exception exc)
					{
						Application.Current?.FindMauiContext()?.CreateLogger<Shell>()?.LogWarning(exc, "If you're using hot reload add a route to everything in your shell file");
					}
				}
 
				if (shellItem == null)
				{
					foreach (var item in shellItems)
					{
						if (item is ShellItem && ValidDefaultShellItem(item))
						{
							shellItem = item;
							break;
						}
					}
				}
 
				if (shellItem != null)
					await OnFlyoutItemSelectedAsync(shellItem, false).ConfigureAwait(false);
			}
		}
 
		/// <summary>
		/// Modifies the behavior of the flyout scroll.
		/// </summary>
		public ScrollMode FlyoutVerticalScrollMode
		{
			get => (ScrollMode)GetValue(FlyoutVerticalScrollModeProperty);
			set => SetValue(FlyoutVerticalScrollModeProperty, value);
		}
 
		public event EventHandler<ShellNavigatedEventArgs> Navigated;
		public event EventHandler<ShellNavigatingEventArgs> Navigating;
 
 
		/// <summary>
		/// Gets or sets the icon that, when pressed, opens the flyout.
		/// </summary>
		public ImageSource FlyoutIcon
		{
			get => (ImageSource)GetValue(FlyoutIconProperty);
			set => SetValue(FlyoutIconProperty, value);
		}
 
		/// <summary>
		/// The currently selected ShellItem.
		/// </summary>
		public ShellItem CurrentItem
		{
			get => (ShellItem)GetValue(CurrentItemProperty);
			set => SetValue(CurrentItemProperty, value);
		}
 
		internal ShellContent CurrentContent => CurrentItem?.CurrentItem?.CurrentItem;
		internal ShellSection CurrentSection => CurrentItem?.CurrentItem;
 
		/// <include file="../../../docs/Microsoft.Maui.Controls/Shell.xml" path="//Member[@MemberName='CurrentState']/Docs/*" />
		public ShellNavigationState CurrentState => (ShellNavigationState)GetValue(CurrentStateProperty);
 
		/// <summary>
		/// Gets or sets the flyout background image. Of type ImageSource, could be a file, embedded resource, URI, or stream.
		/// </summary>
		[System.ComponentModel.TypeConverter(typeof(ImageSourceConverter))]
		public ImageSource FlyoutBackgroundImage
		{
			get => (ImageSource)GetValue(FlyoutBackgroundImageProperty);
			set => SetValue(FlyoutBackgroundImageProperty, value);
		}
 
		/// <summary>
		/// Gets or sets the aspect ratio of the background image.
		/// </summary>
		public Aspect FlyoutBackgroundImageAspect
		{
			get => (Aspect)GetValue(FlyoutBackgroundImageAspectProperty);
			set => SetValue(FlyoutBackgroundImageAspectProperty, value);
		}
 
		/// <summary>
		/// Gets or sets the background color of the flyout.
		/// </summary>
		public Color FlyoutBackgroundColor
		{
			get => (Color)GetValue(FlyoutBackgroundColorProperty);
			set => SetValue(FlyoutBackgroundColorProperty, value);
		}
 
		/// <summary>
		/// Gets or sets the background color of the Shell Flyout.
		/// </summary>
		public Brush FlyoutBackground
		{
			get => (Brush)GetValue(FlyoutBackgroundProperty);
			set => SetValue(FlyoutBackgroundProperty, value);
		}
 
		/// <summary>
		/// Gets or sets the backdrop of the flyout, which is the appearance of the flyout overlay.
		/// </summary>
		public Brush FlyoutBackdrop
		{
			get => (Brush)GetValue(FlyoutBackdropProperty);
			set => SetValue(FlyoutBackdropProperty, value);
		}
 
		/// <summary>
		/// Gets or sets the width of the flyout.
		/// </summary>
		public double FlyoutWidth
		{
			get => (double)GetValue(FlyoutWidthProperty);
			set => SetValue(FlyoutWidthProperty, value);
		}
 
		/// <summary>
		/// Gets or sets the height of the flyout.
		/// </summary>
		public double FlyoutHeight
		{
			get => (double)GetValue(FlyoutHeightProperty);
			set => SetValue(FlyoutHeightProperty, value);
		}
 
		/// <summary>
		/// Gets or sets the behavior to open the flyout.
		/// </summary>
		public FlyoutBehavior FlyoutBehavior
		{
			get => (FlyoutBehavior)GetValue(FlyoutBehaviorProperty);
			set => SetValue(FlyoutBehaviorProperty, value);
		}
 
		/// <summary>
		/// Gets or sets the View that define the appearance of the flyout header.
		/// The flyout header is the content that optionally appears at the top of the flyout.
		/// </summary>
		public object FlyoutHeader
		{
			get => GetValue(FlyoutHeaderProperty);
			set => SetValue(FlyoutHeaderProperty, value);
		}
 
		/// <summary>
		/// Gets or sets the View that define the appearance of the flyout footer.
		/// The flyout footer is the content that optionally appears at the bottom of the flyout.
		/// </summary>
		public object FlyoutFooter
		{
			get => GetValue(FlyoutFooterProperty);
			set => SetValue(FlyoutFooterProperty, value);
		}
 
		/// <summary>
		/// Gets or sets the header behavior for the flyout.
		/// </summary>
		public FlyoutHeaderBehavior FlyoutHeaderBehavior
		{
			get => (FlyoutHeaderBehavior)GetValue(FlyoutHeaderBehaviorProperty);
			set => SetValue(FlyoutHeaderBehaviorProperty, value);
		}
 
		/// <summary>
		/// Gets or sets the flyout header appearance using a <see cref = "DataTemplate" />.
		/// </summary>
		public DataTemplate FlyoutHeaderTemplate
		{
			get => (DataTemplate)GetValue(FlyoutHeaderTemplateProperty);
			set => SetValue(FlyoutHeaderTemplateProperty, value);
		}
 
		/// <summary>
		/// Gets or sets the flyout footer appearance using a <see cref = "DataTemplate" />.
		/// </summary>
		public DataTemplate FlyoutFooterTemplate
		{
			get => (DataTemplate)GetValue(FlyoutFooterTemplateProperty);
			set => SetValue(FlyoutFooterTemplateProperty, value);
		}
 
 
		/// <summary>
		/// Gets or sets the visible status of the flyout.
		/// </summary>
		public bool FlyoutIsPresented
		{
			get => (bool)GetValue(FlyoutIsPresentedProperty);
			set => SetValue(FlyoutIsPresentedProperty, value);
		}
 
		/// <include file="../../../docs/Microsoft.Maui.Controls/Shell.xml" path="//Member[@MemberName='Items']/Docs/*" />
		public IList<ShellItem> Items => (IList<ShellItem>)GetValue(ItemsProperty);
 
		/// <summary>
		/// Gets or sets <see cref = "DataTemplate" /> applied to each of the Items.
		/// </summary>
		public DataTemplate ItemTemplate
		{
			get => GetItemTemplate(this);
			set => SetItemTemplate(this, value);
		}
 
		/// <summary>
		/// Gets or sets the <see cref = "DataTemplate" /> applied to MenuItem objects in the MenuItems collection.
		/// </summary>
		public DataTemplate MenuItemTemplate
		{
			get => GetMenuItemTemplate(this);
			set => SetMenuItemTemplate(this, value);
		}
 
		internal string Route
		{
			get => Routing.GetRoute(this);
			set => Routing.SetRoute(this, value);
		}
 
		internal string RouteHost { get; set; } = "shell";
 
		internal string RouteScheme { get; set; } = "app";
 
		View FlyoutHeaderView
		{
			get => _flyoutHeaderView;
		}
 
		View FlyoutFooterView
		{
			get => _flyoutFooterView;
		}
 
		protected override void OnBindingContextChanged()
		{
			base.OnBindingContextChanged();
 
			if (FlyoutHeaderView != null)
				SetInheritedBindingContext(FlyoutHeaderView, BindingContext);
 
			if (FlyoutFooterView != null)
				SetInheritedBindingContext(FlyoutFooterView, BindingContext);
 
			if (FlyoutContentView != null)
				SetInheritedBindingContext(FlyoutContentView, BindingContext);
		}
 
 
		internal void SendFlyoutItemsChanged() => _flyoutManager.CheckIfFlyoutItemsChanged();
 
		public IEnumerable FlyoutItems => _flyoutManager.FlyoutItems;
 
		List<List<Element>> IShellController.GenerateFlyoutGrouping() =>
			_flyoutManager.GenerateFlyoutGrouping();
 
		internal void SendStructureChanged()
		{
			UpdateChecked(this);
			_structureChanged?.Invoke(this, EventArgs.Empty);
		}
 
		protected override bool OnBackButtonPressed()
		{
#if WINDOWS || !PLATFORM
			var backButtonBehavior = GetBackButtonBehavior(GetVisiblePage());
			if (backButtonBehavior != null)
			{
				var command = backButtonBehavior.GetPropertyIfSet<ICommand>(BackButtonBehavior.CommandProperty, null);
				var commandParameter = backButtonBehavior.GetPropertyIfSet<object>(BackButtonBehavior.CommandParameterProperty, null);
 
				if (command != null)
				{
					command.Execute(commandParameter);
					return true;
				}
			}
#endif
 
			if (GetVisiblePage() is Page page && page.SendBackButtonPressed())
				return true;
 
			var currentContent = CurrentItem?.CurrentItem;
			if (currentContent != null && currentContent.Stack.Count > 1)
			{
				NavigationPop();
				return true;
			}
 
			var args = new ShellNavigatingEventArgs(this.CurrentState, new ShellNavigationState(""), ShellNavigationSource.Pop, true);
			_navigationManager.HandleNavigating(args);
			return args.Cancelled;
 
			async void NavigationPop()
			{
				try
				{
					await currentContent.Navigation.PopAsync();
				}
				catch (Exception exc)
				{
					Application.Current?.FindMauiContext()?.CreateLogger<Shell>()?.LogWarning(exc, "Failed to Navigate Back");
				}
			}
		}
 
		bool ValidDefaultShellItem(Element child) => !(child is MenuShellItem);
 
		void SendNavigated(ShellNavigatedEventArgs args)
		{
			Navigated?.Invoke(this, args);
			OnNavigated(args);
 
			if (_previousPage != null)
				_previousPage.PropertyChanged -= OnCurrentPagePropertyChanged;
 
			NavigationType navigationType = NavigationType.PageSwap;
 
			switch (args.Source)
			{
				case ShellNavigationSource.Pop:
					navigationType = NavigationType.Pop;
					break;
				case ShellNavigationSource.ShellItemChanged:
					navigationType = NavigationType.PageSwap;
					break;
				case ShellNavigationSource.ShellSectionChanged:
					navigationType = NavigationType.PageSwap;
					break;
				case ShellNavigationSource.ShellContentChanged:
					navigationType = NavigationType.PageSwap;
					break;
				case ShellNavigationSource.Push:
					navigationType = NavigationType.Push;
					break;
				case ShellNavigationSource.PopToRoot:
					navigationType = NavigationType.PopToRoot;
					break;
				case ShellNavigationSource.Insert:
					navigationType = NavigationType.Insert;
					break;
			}
 
			_previousPage?.SendNavigatedFrom(new NavigatedFromEventArgs(CurrentPage, navigationType));
			CurrentPage?.SendNavigatedTo(new NavigatedToEventArgs(_previousPage));
			_previousPage = null;
 
			if (CurrentPage != null)
				CurrentPage.PropertyChanged += OnCurrentPagePropertyChanged;
 
			CurrentItem?.Handler?.UpdateValue(Shell.TabBarIsVisibleProperty.PropertyName);
		}
 
		internal PropertyChangedEventHandler CurrentPagePropertyChanged;
 
		void OnCurrentPagePropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			CurrentPagePropertyChanged?.Invoke(this, e);
 
			if (e.Is(Shell.TabBarIsVisibleProperty))
				CurrentItem?.Handler?.UpdateValue(Shell.TabBarIsVisibleProperty.PropertyName);
		}
 
		void SendNavigating(ShellNavigatingEventArgs args)
		{
			Navigating?.Invoke(this, args);
			OnNavigating(args);
 
			if (!args.Cancelled)
			{
				_previousPage = CurrentPage;
				CurrentPage?.SendNavigatingFrom(new NavigatingFromEventArgs());
			}
		}
 
		protected virtual void OnNavigated(ShellNavigatedEventArgs args)
		{
		}
 
		protected virtual void OnNavigating(ShellNavigatingEventArgs args)
		{
 
		}
 
		static void OnCurrentItemChanged(BindableObject bindable, object oldValue, object newValue)
		{
			if (oldValue is ShellItem oldShellItem)
			{
				oldShellItem.SendDisappearing();
 
				foreach (var section in oldShellItem.Items)
				{
					foreach (var content in section.Items)
					{
						content.EvaluateDisconnect();
					}
				}
			}
 
			if (newValue == null)
				return;
 
			if (newValue is ShellItem newShellItem)
				newShellItem.SendAppearing();
 
			var shell = (Shell)bindable;
			UpdateChecked(shell);
 
			shell.ShellController.AppearanceChanged(shell, false);
			shell.ShellController.UpdateCurrentState(ShellNavigationSource.ShellItemChanged);
 
			if (shell.CurrentItem?.CurrentItem != null)
				shell.ShellController.AppearanceChanged(shell.CurrentItem.CurrentItem, false);
		}
 
		static void OnCurrentItemChanging(BindableObject bindable, object oldValue, object newValue)
		{
			var shell = (Shell)bindable;
			var shellItem = (ShellItem)newValue;
 
			if (!shell.Items.Contains(shellItem))
				shell.Items.Add(shellItem);
 
			var shellSection = shellItem.CurrentItem;
			var shellContent = shellSection.CurrentItem;
			var stack = shellSection.Stack;
			shell._navigationManager.ProposeNavigationOutsideGotoAsync(ShellNavigationSource.ShellItemChanged, shellItem, shellSection, shellContent, stack, false, true);
		}
 
		static void UpdateChecked(Element root, bool isChecked = true)
		{
			if (root is BaseShellItem baseItem)
			{
				if (!isChecked && !baseItem.IsChecked)
					return;
				baseItem.SetValue(BaseShellItem.IsCheckedPropertyKey, isChecked);
			}
 
			if (root is Shell shell)
			{
				ShellItem currentItem = shell.CurrentItem;
				var items = shell.ShellController.GetItems();
				var count = items.Count;
				for (int i = 0; i < count; i++)
				{
					ShellItem item = items[i];
					UpdateChecked(item, isChecked && item == currentItem);
				}
			}
			else if (root is ShellItem shellItem)
			{
				var currentItem = shellItem.CurrentItem;
				var items = (shellItem as IShellItemController).GetItems();
				var count = items.Count;
				for (int i = 0; i < count; i++)
				{
					ShellSection item = items[i];
					UpdateChecked(item, isChecked && item == currentItem);
				}
			}
			else if (root is ShellSection shellSection)
			{
				var currentItem = shellSection.CurrentItem;
				var items = (shellSection as IShellSectionController).GetItems();
				var count = items.Count;
				for (int i = 0; i < count; i++)
				{
					ShellContent item = items[i];
					UpdateChecked(item, isChecked && item == currentItem);
				}
			}
		}
 
		static void OnFlyoutHeaderChanging(BindableObject bindable, object oldValue, object newValue)
		{
			var shell = (Shell)bindable;
			shell.OnFlyoutHeaderChanged(oldValue, newValue);
		}
 
		static void OnFlyoutFooterChanging(BindableObject bindable, object oldValue, object newValue)
		{
			var shell = (Shell)bindable;
			shell.OnFlyoutFooterChanged(oldValue, newValue);
		}
 
		static void OnFlyoutHeaderTemplateChanging(BindableObject bindable, object oldValue, object newValue)
		{
			var shell = (Shell)bindable;
			shell.OnFlyoutHeaderTemplateChanged((DataTemplate)oldValue, (DataTemplate)newValue);
		}
 
		static void OnFlyoutFooterTemplateChanging(BindableObject bindable, object oldValue, object newValue)
		{
			var shell = (Shell)bindable;
			shell.OnFlyoutFooterTemplateChanged((DataTemplate)oldValue, (DataTemplate)newValue);
		}
 
		static void OnTitleViewChanged(BindableObject bindable, object oldValue, object newValue) =>
			bindable.AddRemoveLogicalChildren(oldValue, newValue);
 
		internal FlyoutBehavior GetEffectiveFlyoutBehavior()
		{
			ShellItem rootItem = null;
			return GetEffectiveValue(Shell.FlyoutBehaviorProperty,
				() =>
				{
					if (this.IsSet(FlyoutBehaviorProperty))
						return FlyoutBehavior;
 
					if (rootItem is FlyoutItem)
						return FlyoutBehavior.Flyout;
					else if (rootItem is TabBar)
						return FlyoutBehavior.Disabled;
					// This means the user hasn't specified
					// a ShellItem so we don't want the flyout to show up
					// if there is only one ShellItem.
					//
					// This will happen if the user only specifies a
					// single ContentPage
					else if (rootItem != null && Routing.IsImplicit(rootItem))
					{
						if (Items.Count <= 1)
							return FlyoutBehavior.Disabled;
					}
 
					return FlyoutBehavior;
				},
				(o) => rootItem = rootItem ?? o as ShellItem);
		}
 
		internal T GetEffectiveValue<T>(BindableProperty property, T defaultValue, bool ignoreImplicit = false)
		{
			return GetEffectiveValue<T>(property, () => defaultValue, null, ignoreImplicit: ignoreImplicit);
		}
 
		internal T GetEffectiveValue<T>(
			BindableProperty property,
			Func<T> defaultValue,
			Action<Element> observer,
			Element element = null,
			bool ignoreImplicit = false)
		{
			element = element ?? (Element)GetCurrentShellPage() ?? CurrentContent;
			while (element != this && element != null)
			{
				observer?.Invoke(element);
 
				if (ignoreImplicit && Routing.IsImplicit(element))
				{
					// If this is an implicitly created route.
					// A route that the user doesn't have inside their Shell file.
					// Then we don't want to consider it as a value to use.
					// So we let the code just go to the next parent.
				}
				else if (element.IsSet(property))
					return (T)element.GetValue(property);
 
				element = element.Parent;
			}
 
			if (defaultValue == null)
				return default(T);
 
			return defaultValue();
		}
 
		ShellAppearance GetAppearanceForPivot(Element pivot)
		{
			// this algorithm is pretty simple
			// 1) Get the "CurrentPage" by walking down from the pivot
			//		Walking down goes Shell -> ShellItem -> ShellContent -> ShellContent.Stack.Last
			// 2) Walk up from the pivot to the root Shell. Stop walking as soon as you find a ShellAppearance and return
			// 3) If nothing found, return null
 
			pivot = WalkToPage(pivot);
 
			bool foundShellContent = false;
			bool anySet = false;
			ShellAppearance result = new ShellAppearance();
			// Now we walk up
			while (!Application.IsApplicationOrWindowOrNull(pivot))
			{
				if (pivot is ShellContent)
					foundShellContent = true;
 
				// One minor deviation here. Even though a pushed page is technically the child of
				// a ShellSection and not the ShellContent, we want the root ShellContent to 
				// be taken into account. Yes this could behave oddly if the developer switches
				// tabs while a page is pushed, however that is in the developers wheelhouse
				// and this will be the generally expected behavior.
				if (!foundShellContent && pivot is ShellSection shellSection && shellSection.CurrentItem != null)
				{
					if (result.Ingest(shellSection.CurrentItem))
						anySet = true;
				}
 
				if (result.Ingest(pivot))
					anySet = true;
 
				pivot = pivot.Parent;
			}
 
			if (anySet)
			{
				result.MakeComplete();
				return result;
			}
			return null;
		}
 
		internal void NotifyFlyoutBehaviorObservers()
		{
			if (CurrentItem == null || GetVisiblePage() == null)
				return;
 
			var behavior = (this as IFlyoutView).FlyoutBehavior;
			for (int i = 0; i < _flyoutBehaviorObservers.Count; i++)
				_flyoutBehaviorObservers[i].OnFlyoutBehaviorChanged(behavior);
 
			Handler?.UpdateValue(nameof(IFlyoutView.FlyoutBehavior));
		}
 
		void OnFlyoutHeaderChanged(object oldVal, object newVal)
		{
			ShellTemplatedViewManager.OnViewDataChanged(
				FlyoutHeaderTemplate,
				ref _flyoutHeaderView,
				newVal,
				(element) => RemoveLogicalChild(element),
				AddLogicalChild);
		}
 
		void OnFlyoutHeaderTemplateChanged(DataTemplate oldValue, DataTemplate newValue)
		{
			ShellTemplatedViewManager.OnViewTemplateChanged(
				newValue,
				ref _flyoutHeaderView,
				FlyoutHeader,
				(element) => RemoveLogicalChild(element),
				AddLogicalChild,
				this);
		}
 
		void OnFlyoutFooterChanged(object oldVal, object newVal)
		{
			ShellTemplatedViewManager.OnViewDataChanged(
				FlyoutFooterTemplate,
				ref _flyoutFooterView,
				newVal,
				(element) => RemoveLogicalChild(element),
				AddLogicalChild);
		}
 
		void OnFlyoutFooterTemplateChanged(DataTemplate oldValue, DataTemplate newValue)
		{
			ShellTemplatedViewManager.OnViewTemplateChanged(
				newValue,
				ref _flyoutFooterView,
				FlyoutFooter,
				(element) => RemoveLogicalChild(element),
				AddLogicalChild,
				this);
		}
 
		internal Element GetVisiblePage()
		{
			if (CurrentItem?.CurrentItem is IShellSectionController scc)
				return scc.PresentedPage;
 
			return null;
		}
 
		internal void SendPageAppearing(Page page)
		{
			if (Toolbar is ShellToolbar shellToolbar)
				shellToolbar.ApplyChanges();
 
			page.SendAppearing();
		}
 
		// This returns the current shell page that's visible
		// without including the modal stack
		internal Page GetCurrentShellPage()
		{
			var navStack = CurrentSection?.Navigation?.NavigationStack;
			Page currentPage = null;
 
			if (navStack != null)
			{
				currentPage = navStack[navStack.Count - 1] ??
					((IShellContentController)CurrentContent)?.Page;
			}
 
			return currentPage;
		}
 
		Element WalkToPage(Element element)
		{
			switch (element)
			{
				case Shell shell:
					element = shell.CurrentItem;
					break;
				case ShellItem shellItem:
					element = shellItem.CurrentItem;
					break;
				case ShellSection shellSection:
					var controller = (IShellSectionController)element;
					// this is the same as .Last but easier and will add in the root if not null
					// it generally wont be null but this is just in case
					element = controller.PresentedPage ?? element;
					break;
			}
 
			return element;
		}
 
		void IPropertyPropagationController.PropagatePropertyChanged(string propertyName)
		{
			PropertyPropagationExtensions.PropagatePropertyChanged(propertyName, this, ((IVisualTreeElement)this).GetVisualChildren());
		}
 
		[Obsolete("Use ArrangeOverride instead")]
		protected override void LayoutChildren(double x, double y, double width, double height)
		{
			// Page by default tries to layout all logical children
			// we don't want this behavior with shell
		}
 
		IView IFlyoutView.Flyout => this.FlyoutContentView;
		IView IFlyoutView.Detail => null;
 
		bool IFlyoutView.IsPresented { get => FlyoutIsPresented; set => FlyoutIsPresented = value; }
 
		bool IFlyoutView.IsGestureEnabled => false;
 
		FlyoutBehavior IFlyoutView.FlyoutBehavior
		{
			get
			{
				return GetEffectiveFlyoutBehavior();
			}
		}
 
		protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
		{
			base.OnPropertyChanged(propertyName);
			if (propertyName == Shell.FlyoutIsPresentedProperty.PropertyName)
				Handler?.UpdateValue(nameof(IFlyoutView.IsPresented));
		}
 
		#region Shell Flyout Content
 
 
		/// <summary>
		/// Flyout items, which represent the flyout content.
		/// </summary>
		/// <remarks>
		/// Can optionally be replaced with custom content.
		/// </remarks>
		public static readonly BindableProperty FlyoutContentProperty =
			BindableProperty.Create(nameof(FlyoutContent), typeof(object), typeof(Shell), null, BindingMode.OneTime, propertyChanging: OnFlyoutContentChanging);
 
		/// <summary>
		/// The flyout content can be defined by setting a <see cref = "DataTemplate" />.
		/// A flyout header can optionally be displayed above your flyout content, and a flyout footer can optionally be displayed below your flyout content.
		/// </summary>
		public static readonly BindableProperty FlyoutContentTemplateProperty =
			BindableProperty.Create(nameof(FlyoutContentTemplate), typeof(DataTemplate), typeof(Shell), null, BindingMode.OneTime, propertyChanging: OnFlyoutContentTemplateChanging);
 
		View _flyoutContentView;
 
		public object FlyoutContent
		{
			get => GetValue(FlyoutContentProperty);
			set => SetValue(FlyoutContentProperty, value);
		}
 
		public DataTemplate FlyoutContentTemplate
		{
			get => (DataTemplate)GetValue(FlyoutContentTemplateProperty);
			set => SetValue(FlyoutContentTemplateProperty, value);
		}
 
		View FlyoutContentView
		{
			get => _flyoutContentView;
		}
 
		void OnFlyoutContentChanged(object oldVal, object newVal)
		{
			ShellTemplatedViewManager.OnViewDataChanged(
				FlyoutContentTemplate,
				ref _flyoutContentView,
				newVal,
				(element) => RemoveLogicalChild(element),
				AddLogicalChild);
		}
 
		void OnFlyoutContentTemplateChanged(DataTemplate oldValue, DataTemplate newValue)
		{
			ShellTemplatedViewManager.OnViewTemplateChanged(
				newValue,
				ref _flyoutContentView,
				FlyoutContent,
				(element) => RemoveLogicalChild(element),
				AddLogicalChild,
				this);
		}
 
		static void OnFlyoutContentChanging(BindableObject bindable, object oldValue, object newValue)
		{
			var shell = (Shell)bindable;
			shell.OnFlyoutContentChanged(oldValue, newValue);
		}
 
		static void OnFlyoutContentTemplateChanging(BindableObject bindable, object oldValue, object newValue)
		{
			var shell = (Shell)bindable;
			shell.OnFlyoutContentTemplateChanged((DataTemplate)oldValue, (DataTemplate)newValue);
		}
		#endregion
 
		class NavigationImpl : NavigationProxy
		{
			readonly Shell _shell;
 
			NavigationProxy SectionProxy => _shell.CurrentItem?.CurrentItem?.NavigationProxy;
 
			public NavigationImpl(Shell shell) => _shell = shell;
 
			protected override IReadOnlyList<Page> GetNavigationStack() => SectionProxy?.NavigationStack;
 
			protected override void OnInsertPageBefore(Page page, Page before) => SectionProxy.InsertPageBefore(page, before);
 
			protected override Task<Page> OnPopAsync(bool animated) => SectionProxy.PopAsync(animated);
 
			protected override Task OnPopToRootAsync(bool animated) => SectionProxy.PopToRootAsync(animated);
 
			protected override Task OnPushAsync(Page page, bool animated) => SectionProxy.PushAsync(page, animated);
 
			protected override void OnRemovePage(Page page) => SectionProxy.RemovePage(page);
 
			protected override async Task<Page> OnPopModal(bool animated)
			{
				if (!_shell.NavigationManager.AccumulateNavigatedEvents)
				{
					var page = _shell.CurrentPage;
					await _shell.GoToAsync("..", animated);
					return page;
				}
 
				var modalPopped = await base.OnPopModal(animated);
 
				if (ModalStack.Count == 0 && !_shell.CurrentItem.CurrentItem.IsPoppingModalStack)
					_shell.CurrentItem.SendAppearing();
 
				return modalPopped;
			}
 
			protected override async Task OnPushModal(Page modal, bool animated)
			{
				if (_shell.CurrentSection is null)
				{
					await base.OnPushModal(modal, animated);
					return;
				}
 
				if (!_shell.NavigationManager.AccumulateNavigatedEvents)
				{
					// This will route the modal push through the shell section which is setup
					// to update the shell state after a modal push
					await _shell.CurrentSection.Navigation.PushModalAsync(modal, animated);
					return;
				}
 
				if (ModalStack.Count == 0)
					_shell.CurrentItem.SendDisappearing();
 
				await base.OnPushModal(modal, animated);
 
				modal.NavigationProxy.Inner = new NavigationImplWrapper(modal.NavigationProxy.Inner, this);
			}
 
			class NavigationImplWrapper : NavigationProxy
			{
				readonly INavigation _shellProxy;
 
				public NavigationImplWrapper(INavigation proxy, INavigation shellProxy)
				{
					Inner = proxy;
					_shellProxy = shellProxy;
 
				}
 
				protected override Task<Page> OnPopModal(bool animated) => _shellProxy.PopModalAsync(animated);
 
				protected override Task OnPushModal(Page modal, bool animated) => _shellProxy.PushModalAsync(modal, animated);
			}
		}
	}
}