File: Android\PopupManager.cs
Web Access
Project: src\src\Compatibility\Core\src\Compatibility.csproj (Microsoft.Maui.Controls.Compatibility)
using System;
using System.Collections.Generic;
using System.Linq;
using Android.App;
using Android.Content;
using Android.Text;
using Android.Views;
using Android.Widget;
using Microsoft.Maui.Controls.Internals;
using AppCompatActivity = AndroidX.AppCompat.App.AppCompatActivity;
using AppCompatAlertDialog = AndroidX.AppCompat.App.AlertDialog;
using AWindow = Android.Views.Window;
 
namespace Microsoft.Maui.Controls.Compatibility.Platform.Android
{
	[System.Obsolete]
	internal static class PopupManager
	{
		static readonly List<PopupRequestHelper> s_subscriptions = new List<PopupRequestHelper>();
 
		internal static void Subscribe(Activity context)
		{
			if (s_subscriptions.Any(s => s.Activity == context))
			{
				return;
			}
 
			s_subscriptions.Add(new PopupRequestHelper(context));
		}
 
		internal static void Unsubscribe(Activity context)
		{
			var toRemove = s_subscriptions.Where(s => s.Activity == context).ToList();
			foreach (PopupRequestHelper popupRequestHelper in toRemove)
			{
				popupRequestHelper.Dispose();
				s_subscriptions.Remove(popupRequestHelper);
			}
		}
 
		internal static void ResetBusyCount(Activity context)
		{
			s_subscriptions.FirstOrDefault(s => s.Activity == context)?.ResetBusyCount();
		}
 
		internal sealed class PopupRequestHelper : IDisposable
		{
			int _busyCount;
			bool? _supportsProgress;
 
			internal PopupRequestHelper(Activity context)
			{
				Activity = context;
				MessagingCenter.Subscribe<Page, bool>(Activity, Page.BusySetSignalName, OnPageBusy);
				MessagingCenter.Subscribe<Page, AlertArguments>(Activity, Page.AlertSignalName, OnAlertRequested);
				MessagingCenter.Subscribe<Page, PromptArguments>(Activity, Page.PromptSignalName, OnPromptRequested);
				MessagingCenter.Subscribe<Page, ActionSheetArguments>(Activity, Page.ActionSheetSignalName, OnActionSheetRequested);
			}
 
			public Activity Activity { get; }
 
			public void Dispose()
			{
				MessagingCenter.Unsubscribe<Page, bool>(Activity, Page.BusySetSignalName);
				MessagingCenter.Unsubscribe<Page, AlertArguments>(Activity, Page.AlertSignalName);
				MessagingCenter.Unsubscribe<Page, PromptArguments>(Activity, Page.PromptSignalName);
				MessagingCenter.Unsubscribe<Page, ActionSheetArguments>(Activity, Page.ActionSheetSignalName);
			}
 
			public void ResetBusyCount()
			{
				_busyCount = 0;
			}
 
			void OnPageBusy(Page sender, bool enabled)
			{
				// Verify that the page making the request is part of this activity 
				if (!PageIsInThisContext(sender))
				{
					return;
				}
 
				_busyCount = Math.Max(0, enabled ? _busyCount + 1 : _busyCount - 1);
 
				UpdateProgressBarVisibility(_busyCount > 0);
			}
 
			void OnActionSheetRequested(Page sender, ActionSheetArguments arguments)
			{
				// Verify that the page making the request is part of this activity 
				if (!PageIsInThisContext(sender))
				{
					return;
				}
 
				var builder = new DialogBuilder(Activity);
 
				builder.SetTitle(arguments.Title);
				string[] items = arguments.Buttons.ToArray();
				builder.SetItems(items, (o, args) => arguments.Result.TrySetResult(items[args.Which]));
 
				if (arguments.Cancel != null)
					builder.SetPositiveButton(arguments.Cancel, (o, args) => arguments.Result.TrySetResult(arguments.Cancel));
 
				if (arguments.Destruction != null)
					builder.SetNegativeButton(arguments.Destruction, (o, args) => arguments.Result.TrySetResult(arguments.Destruction));
 
				var dialog = builder.Create();
				builder.Dispose();
				//to match current functionality of renderer we set cancelable on outside
				//and return null
				if (arguments.FlowDirection == FlowDirection.MatchParent && sender is IVisualElementController ve)
					dialog.Window.DecorView.UpdateFlowDirection(ve);
				else if (arguments.FlowDirection == FlowDirection.LeftToRight)
					dialog.Window.DecorView.LayoutDirection = LayoutDirection.Ltr;
				else if (arguments.FlowDirection == FlowDirection.RightToLeft)
					dialog.Window.DecorView.LayoutDirection = LayoutDirection.Rtl;
 
				dialog.SetCanceledOnTouchOutside(true);
				dialog.SetCancelEvent((o, e) => arguments.SetResult(null));
				dialog.Show();
 
				dialog.GetListView().TextDirection = GetTextDirection(sender, arguments.FlowDirection);
				LayoutDirection layoutDirection = GetLayoutDirection(sender, arguments.FlowDirection);
				if (arguments.Cancel != null)
					((dialog.GetButton((int)DialogButtonType.Positive).Parent) as global::Android.Views.View).LayoutDirection = layoutDirection;
				if (arguments.Destruction != null)
					((dialog.GetButton((int)DialogButtonType.Negative).Parent) as global::Android.Views.View).LayoutDirection = layoutDirection;
			}
 
			void OnAlertRequested(Page sender, AlertArguments arguments)
			{
				// Verify that the page making the request is part of this activity 
				if (!PageIsInThisContext(sender))
				{
					return;
				}
 
				int messageID = 16908299;
				var alert = new DialogBuilder(Activity).Create();
				if (arguments.FlowDirection == FlowDirection.MatchParent && sender is IVisualElementController ve)
					alert.Window.DecorView.UpdateFlowDirection(ve);
				else if (arguments.FlowDirection == FlowDirection.LeftToRight)
					alert.Window.DecorView.LayoutDirection = LayoutDirection.Ltr;
				else if (arguments.FlowDirection == FlowDirection.RightToLeft)
					alert.Window.DecorView.LayoutDirection = LayoutDirection.Rtl;
 
				alert.SetTitle(arguments.Title);
				alert.SetMessage(arguments.Message);
				if (arguments.Accept != null)
					alert.SetButton((int)DialogButtonType.Positive, arguments.Accept, (o, args) => arguments.SetResult(true));
				alert.SetButton((int)DialogButtonType.Negative, arguments.Cancel, (o, args) => arguments.SetResult(false));
				alert.SetCancelEvent((o, args) => { arguments.SetResult(false); });
				alert.Show();
 
				TextView textView = (TextView)alert.findViewByID(messageID);
				textView.TextDirection = GetTextDirection(sender, arguments.FlowDirection);
				((alert.GetButton((int)DialogButtonType.Negative).Parent) as global::Android.Views.View).LayoutDirection = GetLayoutDirection(sender, arguments.FlowDirection);
			}
 
			private LayoutDirection GetLayoutDirection(Page sender, FlowDirection flowDirection)
			{
				if (flowDirection == FlowDirection.LeftToRight)
					return LayoutDirection.Ltr;
				else if (flowDirection == FlowDirection.RightToLeft)
					return LayoutDirection.Rtl;
				else
				{
					if ((sender as IVisualElementController).EffectiveFlowDirection.IsRightToLeft())
						return LayoutDirection.Rtl;
					else if ((sender as IVisualElementController).EffectiveFlowDirection.IsLeftToRight())
						return LayoutDirection.Ltr;
				}
				return LayoutDirection.Ltr;
			}
 
			private TextDirection GetTextDirection(Page sender, FlowDirection flowDirection)
			{
				if (flowDirection == FlowDirection.LeftToRight)
					return TextDirection.Ltr;
				else if (flowDirection == FlowDirection.RightToLeft)
					return TextDirection.Rtl;
				else
				{
					if ((sender as IVisualElementController).EffectiveFlowDirection.IsRightToLeft())
						return TextDirection.Rtl;
					else if ((sender as IVisualElementController).EffectiveFlowDirection.IsLeftToRight())
						return TextDirection.Ltr;
				}
				return TextDirection.Ltr;
			}
 
			void OnPromptRequested(Page sender, PromptArguments arguments)
			{
				// Verify that the page making the request is part of this activity 
				if (!PageIsInThisContext(sender))
				{
					return;
				}
 
				var alertDialog = new DialogBuilder(Activity).Create();
				alertDialog.SetTitle(arguments.Title);
				alertDialog.SetMessage(arguments.Message);
 
				var frameLayout = new FrameLayout(Activity);
				var editText = new EditText(Activity) { Hint = arguments.Placeholder, Text = arguments.InitialValue };
				var layoutParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.WrapContent)
				{
					LeftMargin = (int)(22 * Activity.Resources.DisplayMetrics.Density),
					RightMargin = (int)(22 * Activity.Resources.DisplayMetrics.Density)
				};
 
				editText.LayoutParameters = layoutParams;
				editText.InputType = arguments.Keyboard.ToInputType();
				if (arguments.Keyboard == Keyboard.Numeric)
					editText.KeyListener = LocalizedDigitsKeyListener.Create(editText.InputType);
 
				if (arguments.MaxLength > -1)
					editText.SetFilters(new IInputFilter[] { new InputFilterLengthFilter(arguments.MaxLength) });
 
				frameLayout.AddView(editText);
				alertDialog.SetView(frameLayout);
 
				alertDialog.SetButton((int)DialogButtonType.Positive, arguments.Accept, (o, args) => arguments.SetResult(editText.Text));
				alertDialog.SetButton((int)DialogButtonType.Negative, arguments.Cancel, (o, args) => arguments.SetResult(null));
				alertDialog.SetCancelEvent((o, args) => { arguments.SetResult(null); });
 
				alertDialog.Window.SetSoftInputMode(SoftInput.StateVisible);
				alertDialog.Show();
				editText.RequestFocus();
			}
 
			void UpdateProgressBarVisibility(bool isBusy)
			{
				if (!SupportsProgress)
					return;
#pragma warning disable 612, 618
#pragma warning disable CA1416, CA1422 // Validate platform compatibility
				Activity.SetProgressBarIndeterminate(true);
				Activity.SetProgressBarIndeterminateVisibility(isBusy);
#pragma warning restore CA1416, CA1422 // Validate platform compatibility
#pragma warning restore 612, 618
			}
 
			internal bool SupportsProgress
			{
				get
				{
					if (_supportsProgress.HasValue)
					{
						return _supportsProgress.Value;
					}
 
					int progressCircularId = Activity.Resources.GetIdentifier("progress_circular", "id", "android");
					if (progressCircularId > 0)
						_supportsProgress = Activity.FindViewById(progressCircularId) != null;
					else
						_supportsProgress = true;
					return _supportsProgress.Value;
				}
			}
 
			bool PageIsInThisContext(Page page)
			{
				var renderer = Platform.GetRenderer(page);
 
				if (renderer?.View?.Context == null)
				{
					return false;
				}
 
				return renderer.View.Context.Equals(Activity);
			}
 
			// This is a proxy dialog builder class to support both pre-appcompat and appcompat dialogs for Alert,
			// ActionSheet, Prompt, etc. 
			internal sealed class DialogBuilder
			{
				AppCompatAlertDialog.Builder _appcompatBuilder;
				AlertDialog.Builder _legacyBuilder;
 
				bool _useAppCompat;
 
				public DialogBuilder(Activity activity)
				{
					if (activity is AppCompatActivity)
					{
						_appcompatBuilder = new AppCompatAlertDialog.Builder(activity);
						_useAppCompat = true;
					}
					else
					{
						_legacyBuilder = new AlertDialog.Builder(activity);
					}
				}
 
				public void SetTitle(string title)
				{
					if (_useAppCompat)
					{
						_appcompatBuilder.SetTitle(title);
					}
					else
					{
						_legacyBuilder.SetTitle(title);
					}
				}
 
				public void SetItems(string[] items, EventHandler<DialogClickEventArgs> handler)
				{
					if (_useAppCompat)
					{
						_appcompatBuilder.SetItems(items, handler);
					}
					else
					{
						_legacyBuilder.SetItems(items, handler);
					}
				}
 
				public void SetPositiveButton(string text, EventHandler<DialogClickEventArgs> handler)
				{
					if (_useAppCompat)
					{
						_appcompatBuilder.SetPositiveButton(text, handler);
					}
					else
					{
						_legacyBuilder.SetPositiveButton(text, handler);
					}
				}
 
				public void SetNegativeButton(string text, EventHandler<DialogClickEventArgs> handler)
				{
					if (_useAppCompat)
					{
						_appcompatBuilder.SetNegativeButton(text, handler);
					}
					else
					{
						_legacyBuilder.SetNegativeButton(text, handler);
					}
				}
 
				public FlexibleAlertDialog Create()
				{
					if (_useAppCompat)
					{
						return new FlexibleAlertDialog(_appcompatBuilder.Create());
					}
 
					return new FlexibleAlertDialog(_legacyBuilder.Create());
				}
 
				public void Dispose()
				{
					if (_useAppCompat)
					{
						_appcompatBuilder.Dispose();
					}
					else
					{
						_legacyBuilder.Dispose();
					}
				}
			}
 
			internal sealed class FlexibleAlertDialog
			{
				readonly AppCompatAlertDialog _appcompatAlertDialog;
				readonly AlertDialog _legacyAlertDialog;
				bool _useAppCompat;
 
				public FlexibleAlertDialog(AlertDialog alertDialog)
				{
					_legacyAlertDialog = alertDialog;
				}
 
				public FlexibleAlertDialog(AppCompatAlertDialog alertDialog)
				{
					_appcompatAlertDialog = alertDialog;
					_useAppCompat = true;
				}
 
				public void SetTitle(string title)
				{
					if (_useAppCompat)
					{
						_appcompatAlertDialog.SetTitle(title);
					}
					else
					{
						_legacyAlertDialog.SetTitle(title);
					}
				}
 
				public void SetMessage(string message)
				{
					if (_useAppCompat)
					{
						_appcompatAlertDialog.SetMessage(message);
					}
					else
					{
						_legacyAlertDialog.SetMessage(message);
					}
				}
 
				public void SetButton(int whichButton, string text, EventHandler<DialogClickEventArgs> handler)
				{
					if (_useAppCompat)
					{
						_appcompatAlertDialog.SetButton(whichButton, text, handler);
					}
					else
					{
						_legacyAlertDialog.SetButton(whichButton, text, handler);
					}
				}
 
				public global::Android.Widget.Button GetButton(int whichButton)
				{
					if (_useAppCompat)
					{
						return _appcompatAlertDialog.GetButton(whichButton);
					}
					else
					{
						return _legacyAlertDialog.GetButton(whichButton);
					}
				}
 
				public global::Android.Views.View GetListView()
				{
					if (_useAppCompat)
					{
						return _appcompatAlertDialog.ListView;
					}
					else
					{
						return _legacyAlertDialog.ListView;
					}
				}
 
				public void SetCancelEvent(EventHandler cancel)
				{
					if (_useAppCompat)
					{
						_appcompatAlertDialog.CancelEvent += cancel;
					}
					else
					{
						_legacyAlertDialog.CancelEvent += cancel;
					}
				}
 
				public void SetCanceledOnTouchOutside(bool canceledOnTouchOutSide)
				{
					if (_useAppCompat)
					{
						_appcompatAlertDialog.SetCanceledOnTouchOutside(canceledOnTouchOutSide);
					}
					else
					{
						_legacyAlertDialog.SetCanceledOnTouchOutside(canceledOnTouchOutSide);
					}
				}
 
				public void SetView(global::Android.Views.View view)
				{
					if (_useAppCompat)
					{
						_appcompatAlertDialog.SetView(view);
					}
					else
					{
						_legacyAlertDialog.SetView(view);
					}
				}
 
				public global::Android.Views.View findViewByID(int id)
				{
					if (_useAppCompat)
					{
						return _appcompatAlertDialog.FindViewById(id);
					}
					else
					{
						return _legacyAlertDialog.FindViewById(id);
					}
				}
 
				public AWindow Window => _useAppCompat ? _appcompatAlertDialog.Window : _legacyAlertDialog.Window;
 
				public void Show()
				{
					if (_useAppCompat)
					{
						_appcompatAlertDialog.Show();
					}
					else
					{
						_legacyAlertDialog.Show();
					}
				}
			}
		}
	}
}