File: Android\AppCompat\FlyoutPageRenderer.cs
Web Access
Project: src\src\Compatibility\Core\src\Compatibility.csproj (Microsoft.Maui.Controls.Compatibility)
using System;
using System.ComponentModel;
using System.Threading.Tasks;
using Android.Content;
using Android.OS;
using Android.Views;
using AndroidX.DrawerLayout.Widget;
using AndroidX.Fragment.App;
using AView = Android.Views.View;
 
namespace Microsoft.Maui.Controls.Compatibility.Platform.Android
{
	using global::Android.Graphics.Drawables;
	using Microsoft.Extensions.Logging;
	using Microsoft.Maui.Controls.Compatibility.Platform.Android.AppCompat;
	using Microsoft.Maui.Controls.Compatibility.Platform.Android.FastRenderers;
	using Microsoft.Maui.Controls.Platform;
	using Microsoft.Maui.Devices;
	using Microsoft.Maui.Graphics;
 
	[System.Obsolete(Compatibility.Hosting.MauiAppBuilderExtensions.UseMapperInstead)]
	public class FlyoutPageRenderer : DrawerLayout, IVisualElementRenderer, DrawerLayout.IDrawerListener, IManageFragments, ILifeCycleState
	{
		#region Statics
 
		//from Android source code
		const uint DefaultScrimColor = 0x99000000;
 
		#endregion
 
		int _currentLockMode = -1;
		FlyoutPageContainer _detailLayout;
		FlyoutPageContainer _flyoutLayout;
		bool _disposed;
		bool _isPresentingFromCore;
		bool _presented;
		bool _defaultAutomationSet;
		VisualElementTracker _tracker;
		FragmentManager _fragmentManager;
		string _defaultContentDescription;
 
		public FlyoutPageRenderer(Context context) : base(context)
		{
		}
 
		FlyoutPage Element { get; set; }
 
		IFlyoutPageController FlyoutPageController => Element as IFlyoutPageController;
 
		bool Presented
		{
			get { return _presented; }
			set
			{
				if (value == _presented)
					return;
				UpdateSplitViewLayout();
				_presented = value;
				if (Element.FlyoutLayoutBehavior == FlyoutLayoutBehavior.Default && FlyoutPageController.ShouldShowSplitMode)
					return;
				if (_presented)
					OpenDrawer(_flyoutLayout);
				else
					CloseDrawer(_flyoutLayout);
			}
		}
 
		IPageController FlyoutPagePageController => Element.Flyout as IPageController;
		IPageController DetailPageController => Element.Detail as IPageController;
		IPageController PageController => Element as IPageController;
 
		void IDrawerListener.OnDrawerClosed(global::Android.Views.View drawerView)
		{
		}
 
		void IDrawerListener.OnDrawerOpened(global::Android.Views.View drawerView)
		{
		}
 
		void IDrawerListener.OnDrawerSlide(global::Android.Views.View drawerView, float slideOffset)
		{
		}
 
		void IDrawerListener.OnDrawerStateChanged(int newState)
		{
			_presented = IsDrawerVisible(_flyoutLayout);
			UpdateIsPresented();
		}
 
		void IManageFragments.SetFragmentManager(FragmentManager fragmentManager)
		{
			if (_fragmentManager == null)
				_fragmentManager = fragmentManager;
		}
 
		VisualElement IVisualElementRenderer.Element => Element;
 
		event EventHandler<VisualElementChangedEventArgs> IVisualElementRenderer.ElementChanged
		{
			add { ElementChanged += value; }
			remove { ElementChanged -= value; }
		}
 
		event EventHandler<PropertyChangedEventArgs> IVisualElementRenderer.ElementPropertyChanged
		{
			add { ElementPropertyChanged += value; }
			remove { ElementPropertyChanged -= value; }
		}
 
		SizeRequest IVisualElementRenderer.GetDesiredSize(int widthConstraint, int heightConstraint)
		{
			Measure(widthConstraint, heightConstraint);
			return new SizeRequest(new Size(MeasuredWidth, MeasuredHeight));
		}
 
		void IVisualElementRenderer.SetElement(VisualElement element)
		{
			FlyoutPage oldElement = Element;
			FlyoutPage newElement = Element = element as FlyoutPage;
 
			if (oldElement != null)
			{
				DeviceDisplay.MainDisplayInfoChanged -= DeviceInfoPropertyChanged;
 
				((IFlyoutPageController)oldElement).BackButtonPressed -= OnBackButtonPressed;
 
				oldElement.PropertyChanged -= HandlePropertyChanged;
				oldElement.Appearing -= FlyoutPageAppearing;
				oldElement.Disappearing -= FlyoutPageDisappearing;
 
				RemoveDrawerListener(this);
 
				if (_detailLayout != null)
				{
					RemoveView(_detailLayout);
				}
 
				if (_flyoutLayout != null)
				{
					RemoveView(_flyoutLayout);
				}
			}
 
			if (newElement != null)
			{
				if (_detailLayout == null)
				{
					_detailLayout = new FlyoutPageContainer(newElement, false, Context)
					{
						LayoutParameters = new LayoutParams(ViewGroup.LayoutParams.WrapContent, ViewGroup.LayoutParams.WrapContent)
					};
 
					_flyoutLayout = new FlyoutPageContainer(newElement, true, Context)
					{
						LayoutParameters = new LayoutParams(ViewGroup.LayoutParams.WrapContent, ViewGroup.LayoutParams.WrapContent) { Gravity = (int)GravityFlags.Start }
					};
 
					if (_fragmentManager != null)
					{
						_detailLayout.SetFragmentManager(_fragmentManager);
						_flyoutLayout.SetFragmentManager(_fragmentManager);
					}
 
					AddView(_detailLayout);
					AddView(_flyoutLayout);
 
					DeviceDisplay.MainDisplayInfoChanged += DeviceInfoPropertyChanged;
 
					AddDrawerListener(this);
				}
 
				UpdateBackgroundColor(newElement);
				UpdateBackgroundImage(newElement);
 
				UpdateFlyout();
				UpdateDetail();
 
				UpdateFlowDirection();
 
				((IFlyoutPageController)newElement).BackButtonPressed += OnBackButtonPressed;
				newElement.PropertyChanged += HandlePropertyChanged;
				newElement.Appearing += FlyoutPageAppearing;
				newElement.Disappearing += FlyoutPageDisappearing;
 
				SetGestureState();
 
				Presented = newElement.IsPresented;
 
				newElement.SendViewInitialized(this);
			}
 
			OnElementChanged(oldElement, newElement);
 
			// Make sure to initialize this AFTER event is fired
			if (_tracker == null)
				_tracker = new VisualElementTracker(this);
 
			if (element != null && !string.IsNullOrEmpty(element.AutomationId))
				SetAutomationId(element.AutomationId);
 
			SetContentDescription();
		}
 
		void IVisualElementRenderer.SetLabelFor(int? id) => LabelFor = id ?? LabelFor;
 
		VisualElementTracker IVisualElementRenderer.Tracker => _tracker;
 
		void IVisualElementRenderer.UpdateLayout()
		{
			_tracker?.UpdateLayout();
		}
 
		AView IVisualElementRenderer.View => this;
 
		bool ILifeCycleState.MarkedForDispose { get; set; } = false;
 
		void SetupAutomationDefaults()
		{
			if (!_defaultAutomationSet)
			{
				_defaultAutomationSet = true;
				Controls.Platform.AutomationPropertiesProvider.SetupDefaults(this, ref _defaultContentDescription);
			}
		}
 
		protected virtual void SetAutomationId(string id)
		{
			SetupAutomationDefaults();
			Controls.Platform.AutomationPropertiesProvider.SetAutomationId(this, Element, id);
		}
 
		protected virtual void SetContentDescription()
		{
			SetupAutomationDefaults();
			Controls.Platform.AutomationPropertiesProvider.SetContentDescription(this, Element, _defaultContentDescription, null);
		}
 
		protected override void Dispose(bool disposing)
		{
			if (_disposed)
				return;
 
			_disposed = true;
 
			if (disposing)
			{
				DeviceDisplay.MainDisplayInfoChanged -= DeviceInfoPropertyChanged;
 
				if (Element != null)
				{
					FlyoutPageController.BackButtonPressed -= OnBackButtonPressed;
					Element.PropertyChanged -= HandlePropertyChanged;
					Element.Appearing -= FlyoutPageAppearing;
					Element.Disappearing -= FlyoutPageDisappearing;
				}
 
				if (_flyoutLayout?.ChildView != null)
					_flyoutLayout.ChildView.PropertyChanged -= HandleFlyoutPropertyChanged;
 
				if (!this.IsDisposed())
					RemoveDrawerListener(this);
 
				if (_tracker != null)
				{
					_tracker.Dispose();
					_tracker = null;
				}
 
				if (_detailLayout != null)
				{
					RemoveView(_detailLayout);
					_detailLayout.Dispose();
					_detailLayout = null;
				}
 
				if (_flyoutLayout != null)
				{
					RemoveView(_flyoutLayout);
					_flyoutLayout.Dispose();
					_flyoutLayout = null;
				}
 
				if (Element != null)
				{
					Element.ClearValue(Microsoft.Maui.Controls.Compatibility.Platform.Android.Platform.RendererProperty);
					Element = null;
				}
			}
 
			base.Dispose(disposing);
		}
 
		protected override void OnAttachedToWindow()
		{
			base.OnAttachedToWindow();
			PageController.SendAppearing();
		}
 
		protected override void OnDetachedFromWindow()
		{
			base.OnDetachedFromWindow();
			PageController?.SendDisappearing();
		}
 
		protected virtual void OnElementChanged(VisualElement oldElement, VisualElement newElement)
		{
			ElementChanged?.Invoke(this, new VisualElementChangedEventArgs(oldElement, newElement));
		}
 
		protected override void OnLayout(bool changed, int l, int t, int r, int b)
		{
			base.OnLayout(changed, l, t, r, b);
			//hack to make the split layout handle touches the full width
			if (FlyoutPageController.ShouldShowSplitMode && _flyoutLayout != null)
				_flyoutLayout.Right = r;
		}
 
		async void DeviceInfoPropertyChanged(object sender, DisplayInfoChangedEventArgs e)
		{
			if (!FlyoutPageController.ShouldShowSplitMode && Presented)
			{
				FlyoutPageController.CanChangeIsPresented = true;
				//hack : when the orientation changes and we try to close the Flyout on Android		
				//sometimes Android picks the width of the screen previous to the rotation 		
				//this leaves a little of the flyout visible, the hack is to delay for 100ms closing the drawer
				await Task.Delay(100);
 
				//Renderer may have been disposed during the delay
				if (_disposed)
				{
					return;
				}
 
				CloseDrawer(_flyoutLayout);
			}
 
			UpdateSplitViewLayout();
		}
 
		event EventHandler<VisualElementChangedEventArgs> ElementChanged;
		event EventHandler<PropertyChangedEventArgs> ElementPropertyChanged;
 
		bool HasAncestorNavigationPage(Element element)
		{
			if (element.Parent == null)
				return false;
			else if (element.Parent is NavigationPage)
				return true;
			else
				return HasAncestorNavigationPage(element.Parent);
		}
 
		void HandleFlyoutPropertyChanged(object sender, PropertyChangedEventArgs e)
		{
		}
 
		void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			ElementPropertyChanged?.Invoke(this, e);
			if (e.PropertyName == "Flyout")
				UpdateFlyout();
			else if (e.PropertyName == "Detail")
				UpdateDetail();
			else if (e.PropertyName == FlyoutPage.IsGestureEnabledProperty.PropertyName)
				SetGestureState();
			else if (e.PropertyName == FlyoutPage.IsPresentedProperty.PropertyName)
			{
				_isPresentingFromCore = true;
				Presented = Element.IsPresented;
				_isPresentingFromCore = false;
			}
			else if (e.PropertyName == Page.BackgroundImageSourceProperty.PropertyName)
				UpdateBackgroundImage(Element);
			else if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
				UpdateBackgroundColor(Element);
			else if (e.PropertyName == VisualElement.FlowDirectionProperty.PropertyName)
				UpdateFlowDirection();
		}
 
		void FlyoutPageAppearing(object sender, EventArgs e)
		{
			FlyoutPagePageController?.SendAppearing();
			DetailPageController?.SendAppearing();
		}
 
		void FlyoutPageDisappearing(object sender, EventArgs e)
		{
			FlyoutPagePageController?.SendDisappearing();
			DetailPageController?.SendDisappearing();
		}
 
		void OnBackButtonPressed(object sender, BackButtonPressedEventArgs backButtonPressedEventArgs)
		{
			if (!IsDrawerOpen((int)GravityFlags.Start) || _currentLockMode == LockModeLockedOpen)
				return;
 
			CloseDrawer((int)GravityFlags.Start);
			backButtonPressedEventArgs.Handled = true;
		}
 
		void SetGestureState()
		{
			SetDrawerLockMode(Element.IsGestureEnabled ? LockModeUnlocked : LockModeLockedClosed);
		}
 
		void SetLockMode(int lockMode)
		{
			if (_currentLockMode != lockMode)
			{
				SetDrawerLockMode(lockMode);
				_currentLockMode = lockMode;
			}
		}
 
		void UpdateBackgroundColor(Page view)
		{
			Color backgroundColor = view.BackgroundColor;
			if (backgroundColor != null)
				SetBackgroundColor(backgroundColor.ToAndroid());
		}
 
		void UpdateBackgroundImage(Page view)
		{
			this.ApplyDrawableAsync(view, Page.BackgroundImageSourceProperty, Context,
				(Drawable drawable) =>
			{
				if (drawable != null)
					this.SetBackground(drawable);
			}).FireAndForget(e => Application.Current?.FindMauiContext()?.CreateLogger<FlyoutPageRenderer>()?
						.LogWarning(e, "Error updating the background image"));
		}
 
		void UpdateDetail()
		{
			if (_detailLayout.ChildView == null)
				Update();
			else
				// Queue up disposal of the previous renderers after the current layout updates have finished
				new Handler(Looper.MainLooper).Post(Update);
 
			void Update()
			{
				if (_detailLayout == null || _detailLayout.IsDisposed())
					return;
 
				Context.HideKeyboard(this);
				_detailLayout.ChildView = Element.Detail;
			}
		}
 
		void UpdateFlowDirection()
		{
			this.UpdateFlowDirection(Element);
			_detailLayout.UpdateFlowDirection();
		}
 
		void UpdateIsPresented()
		{
			if (_isPresentingFromCore)
				return;
			if (Presented != Element.IsPresented)
				((IElementController)Element).SetValueFromRenderer(FlyoutPage.IsPresentedProperty, Presented);
		}
 
		void UpdateFlyout()
		{
			if (_flyoutLayout.ChildView == null)
				Update();
			else
				// Queue up disposal of the previous renderers after the current layout updates have finished
				new Handler(Looper.MainLooper).Post(Update);
 
			void Update()
			{
				if (_flyoutLayout == null || _flyoutLayout.IsDisposed())
					return;
 
				if (_flyoutLayout.ChildView != null)
					_flyoutLayout.ChildView.PropertyChanged -= HandleFlyoutPropertyChanged;
 
				_flyoutLayout.ChildView = Element.Flyout;
 
				if (_flyoutLayout.ChildView != null)
					_flyoutLayout.ChildView.PropertyChanged += HandleFlyoutPropertyChanged;
			}
		}
 
		void UpdateSplitViewLayout()
		{
			if (DeviceInfo.Idiom == DeviceIdiom.Tablet)
			{
				bool isShowingSplit = FlyoutPageController.ShouldShowSplitMode || (FlyoutPageController.ShouldShowSplitMode && Element.FlyoutLayoutBehavior != FlyoutLayoutBehavior.Default && Element.IsPresented);
				SetLockMode(isShowingSplit ? LockModeLockedOpen : LockModeUnlocked);
				unchecked
				{
					SetScrimColor(isShowingSplit ? Colors.Transparent.ToAndroid() : (int)DefaultScrimColor);
				}
			}
		}
	}
}