File: Android\AppCompat\FlyoutPageContainer.cs
Web Access
Project: src\src\Compatibility\Core\src\Compatibility.csproj (Microsoft.Maui.Controls.Compatibility)
using Android.App;
using Android.Content;
using Android.Content.Res;
using Android.Views;
using Microsoft.Maui.Controls.Compatibility.Platform.Android.AppCompat;
using Microsoft.Maui.Controls.Internals;
using Microsoft.Maui.Controls.Platform;
using Microsoft.Maui.Devices;
using Microsoft.Maui.Graphics;
using APlatform = Microsoft.Maui.Controls.Compatibility.Platform.Android.Platform;
using Fragment = AndroidX.Fragment.App.Fragment;
using FragmentContainer = Microsoft.Maui.Controls.Compatibility.Platform.Android.AppCompat.FragmentContainer;
using FragmentManager = AndroidX.Fragment.App.FragmentManager;
using FragmentTransaction = AndroidX.Fragment.App.FragmentTransaction;
 
namespace Microsoft.Maui.Controls.Compatibility.Platform.Android
{
	[System.Obsolete]
	internal class FlyoutPageContainer : ViewGroup, IManageFragments
	{
		const int DefaultFlyoutSize = 320;
		const int DefaultSmallFlyoutSize = 240;
		readonly bool _isFlyout;
		VisualElement _childView;
		PageContainer _pageContainer;
		FragmentManager _fragmentManager;
		FlyoutPage _parent;
		Fragment _currentFragment;
		bool _disposed;
		FragmentTransaction _transaction;
 
		public FlyoutPageContainer(FlyoutPage parent, bool isFlyout, Context context) : base(context)
		{
			Id = APlatform.GenerateViewId();
			_parent = parent;
			_isFlyout = isFlyout;
		}
 
		public bool MarkedForDispose { get; internal set; } = false;
 
		FragmentManager FragmentManager => _fragmentManager ?? (_fragmentManager = Context.GetFragmentManager());
		IFlyoutPageController FlyoutPageController => _parent as IFlyoutPageController;
 
		protected override void OnLayout(bool changed, int l, int t, int r, int b)
		{
			if (_childView == null)
				return;
 
			var bounds = GetBounds(_isFlyout, l, t, r, b);
			if (_isFlyout)
				FlyoutPageController.FlyoutBounds = bounds;
			else
				FlyoutPageController.DetailBounds = bounds;
 
			IVisualElementRenderer renderer = APlatform.GetRenderer(_childView);
			renderer?.UpdateLayout();
 
			// If we're using a PageContainer (i.e., we've wrapped our contents in a Fragment),
			// Make sure that it gets arranged
			if (_pageContainer != null)
			{
				if (_isFlyout)
				{
					var controller = (IFlyoutPageController)_parent;
					var width = (int)Context.ToPixels(controller.FlyoutBounds.Width);
					// When the base carrangedss computes the size of the Flyout container, it starts at the top of the 
					// screen and adds padding (_parent.FlyoutBounds.Top) to leave room for the status bar
					// When this container is arranged, it's already starting from the adjusted y value of the parent,
					// so we subtract _parent.FlyoutBounds.Top from our starting point (to get 0) and add it to the 
					// bottom (so the flyout page stretches to the bottom of the screen)
					var height = (int)Context.ToPixels(controller.FlyoutBounds.Height + controller.FlyoutBounds.Top);
					_pageContainer.Layout(0, 0, width, height);
				}
				else
				{
					_pageContainer.Layout(l, t, r, b);
				}
 
				_pageContainer.Child.UpdateLayout();
			}
		}
 
		public void UpdateFlowDirection() => _pageContainer?.UpdateFlowDirection(_parent);
 
		public VisualElement ChildView
		{
			get { return _childView; }
			set
			{
				if (_childView == value)
					return;
 
				RemoveAllViews();
				if (_childView != null)
					DisposeChildRenderers();
 
				_childView = value;
 
				if (_childView == null)
					return;
 
				AddChildView(_childView);
			}
		}
 
		protected virtual void AddChildView(VisualElement childView)
		{
			_pageContainer = null;
 
			Page page = childView as NavigationPage ?? (Page)(childView as TabbedPage);
 
			if (page == null)
			{
				// The thing we're adding is not a NavigationPage or TabbedPage, so we can just use the old AddChildView 
 
				if (_currentFragment != null)
				{
					if (!_parent.IsAttachedToRoot())
						return;
 
					// But first, if the previous occupant of this container was a fragment, we need to remove it properly
					FragmentTransaction transaction = FragmentManager.BeginTransactionEx();
					transaction.RemoveEx(_currentFragment);
					transaction.SetTransitionEx((int)FragmentTransit.None);
 
					if (IsAttachedToWindow)
						ExecuteTransaction(transaction);
					else
						_transaction = transaction;
 
					_currentFragment = null;
				}
 
				IVisualElementRenderer renderer = APlatform.GetRenderer(childView);
				if (renderer == null)
					APlatform.SetRenderer(childView, renderer = APlatform.CreateRenderer(childView, Context));
 
				if (renderer.View.Parent != this)
				{
					if (renderer.View.Parent != null)
						renderer.View.RemoveFromParent();
					SetDefaultBackgroundColor(renderer);
					AddView(renderer.View);
					renderer.UpdateLayout();
				}
			}
			else
			{
				if (!_parent.IsAttachedToRoot())
					return;
 
				// The renderers for NavigationPage and TabbedPage both host fragments, so they need to be wrapped in a 
				// FragmentContainer in order to get isolated fragment management
				Fragment fragment = FragmentContainer.CreateInstance(page);
 
				var fc = fragment as FragmentContainer;
 
				fc?.SetOnCreateCallback(pc =>
				{
					_pageContainer = pc;
					UpdateFlowDirection();
					SetDefaultBackgroundColor(pc.Child);
				});
 
				FragmentTransaction transaction = FragmentManager.BeginTransactionEx();
 
				if (_currentFragment != null)
					transaction.RemoveEx(_currentFragment);
 
				transaction.AddEx(Id, fragment);
				transaction.SetTransitionEx((int)FragmentTransit.None);
 
				if (IsAttachedToWindow)
					ExecuteTransaction(transaction);
				else
					_transaction = transaction;
 
				_currentFragment = fragment;
			}
		}
 
		protected override void OnAttachedToWindow()
		{
			base.OnAttachedToWindow();
 
			if (_transaction == null)
				return;
 
			ExecuteTransaction(_transaction);
 
			_transaction = null;
		}
 
		public int TopPadding { get; set; }
 
		double DefaultWidthFlyout
		{
			get
			{
				double w = Context.FromPixels(Resources.DisplayMetrics.WidthPixels);
				return w < DefaultSmallFlyoutSize ? w : (w < DefaultFlyoutSize ? DefaultSmallFlyoutSize : DefaultFlyoutSize);
			}
		}
 
		public override bool OnInterceptTouchEvent(MotionEvent ev)
		{
			bool isShowingPopover = _parent.IsPresented && !FlyoutPageController.ShouldShowSplitMode;
			if (!_isFlyout && isShowingPopover)
				return true;
			return base.OnInterceptTouchEvent(ev);
		}
 
 
		protected override void Dispose(bool disposing)
		{
			if (_disposed)
			{
				return;
			}
 
			_disposed = true;
 
			if (disposing)
			{
				if (_currentFragment != null && !FragmentManager.IsDestroyed(Context))
				{
					FragmentTransaction transaction = FragmentManager.BeginTransactionEx();
					transaction.RemoveEx(_currentFragment);
					transaction.SetTransitionEx((int)FragmentTransit.None);
					transaction.CommitAllowingStateLossEx();
					FragmentManager.ExecutePendingTransactionsEx();
 
					_currentFragment = null;
				}
 
				_parent = null;
				_pageContainer = null;
				_fragmentManager = null;
				RemoveAllViews();
				DisposeChildRenderers();
			}
 
			base.Dispose(disposing);
		}
 
		public void SetFragmentManager(FragmentManager fragmentManager)
		{
			if (_fragmentManager == null)
				_fragmentManager = fragmentManager;
		}
 
		void ExecuteTransaction(FragmentTransaction transaction)
		{
			// We don't currently support fragment restoration 
			// So we don't need to worry about loss of this fragment's state
			transaction.CommitAllowingStateLossEx();
 
			// The transaction need to be executed after View has been attached
			// So Fragment Manager can find the View being added
			FragmentManager.ExecutePendingTransactionsEx();
		}
 
		void DisposeChildRenderers()
		{
			IVisualElementRenderer childRenderer = APlatform.GetRenderer(_childView);
			childRenderer?.Dispose();
			_childView?.ClearValue(APlatform.RendererProperty);
		}
 
		Rect GetBounds(bool isFlyoutPage, int left, int top, int right, int bottom)
		{
			double width = Context.FromPixels(right - left);
			double height = Context.FromPixels(bottom - top);
			double xPos = 0;
			bool supressPadding = false;
 
			//splitview
			if (FlyoutPageController.ShouldShowSplitMode)
			{
				//to keep some behavior we have on iPad where you can toggle and it won't do anything 
				bool isDefaultNoToggle = _parent.FlyoutLayoutBehavior == FlyoutLayoutBehavior.Default;
				xPos = isFlyoutPage ? 0 : (_parent.IsPresented || isDefaultNoToggle ? DefaultWidthFlyout : 0);
				width = isFlyoutPage ? DefaultWidthFlyout : _parent.IsPresented || isDefaultNoToggle ? width - DefaultWidthFlyout : width;
			}
			else
			{
				//if we are showing the normal popover master doesn't have padding
				supressPadding = isFlyoutPage;
				//popover make the master smaller
				width = isFlyoutPage && (DeviceDisplay.MainDisplayInfo.Orientation.IsLandscape() || DeviceInfo.Idiom == DeviceIdiom.Tablet) ? DefaultWidthFlyout : width;
			}
 
			double padding = supressPadding ? 0 : Context.FromPixels(TopPadding);
			return new Rect(xPos, padding, width, height - padding);
		}
 
		protected void SetDefaultBackgroundColor(IVisualElementRenderer renderer)
		{
			if (ChildView.BackgroundColor == null)
			{
				TypedArray colors = Context.Theme.ObtainStyledAttributes(new[] { global::Android.Resource.Attribute.ColorBackground });
				renderer.View.SetBackgroundColor(new global::Android.Graphics.Color(colors.GetColor(0, 0)));
			}
		}
	}
}