diff --git a/samples/XCT.Sample.Android/MainActivity.cs b/samples/XCT.Sample.Android/MainActivity.cs
index 9a00b8c73..d2392c841 100644
--- a/samples/XCT.Sample.Android/MainActivity.cs
+++ b/samples/XCT.Sample.Android/MainActivity.cs
@@ -2,7 +2,6 @@
using Android.Content.PM;
using Android.OS;
using Android.Runtime;
-using Xamarin.Essentials;
namespace Xamarin.CommunityToolkit.Sample.Droid
{
@@ -17,14 +16,14 @@ protected override void OnCreate(Bundle savedInstanceState)
base.OnCreate(savedInstanceState);
global::Xamarin.Forms.Forms.SetFlags("CollectionView_Experimental");
- Platform.Init(this, savedInstanceState);
+ Essentials.Platform.Init(this, savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
LoadApplication(new App());
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults)
{
- Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
+ Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
diff --git a/samples/XCT.Sample/Pages/Views/PopupGalleryPage.xaml b/samples/XCT.Sample/Pages/Views/PopupGalleryPage.xaml
new file mode 100644
index 000000000..54b0596e8
--- /dev/null
+++ b/samples/XCT.Sample/Pages/Views/PopupGalleryPage.xaml
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/XCT.Sample/Pages/Views/PopupGalleryPage.xaml.cs b/samples/XCT.Sample/Pages/Views/PopupGalleryPage.xaml.cs
new file mode 100644
index 000000000..63fa5ecaf
--- /dev/null
+++ b/samples/XCT.Sample/Pages/Views/PopupGalleryPage.xaml.cs
@@ -0,0 +1,7 @@
+namespace Xamarin.CommunityToolkit.Sample.Pages.Views
+{
+ public partial class PopupGalleryPage
+ {
+ public PopupGalleryPage() => InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/samples/XCT.Sample/Pages/Views/Popups/ButtonPopup.xaml b/samples/XCT.Sample/Pages/Views/Popups/ButtonPopup.xaml
new file mode 100644
index 000000000..b87a4e2c9
--- /dev/null
+++ b/samples/XCT.Sample/Pages/Views/Popups/ButtonPopup.xaml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/XCT.Sample/Pages/Views/Popups/ButtonPopup.xaml.cs b/samples/XCT.Sample/Pages/Views/Popups/ButtonPopup.xaml.cs
new file mode 100644
index 000000000..8c23d1d4d
--- /dev/null
+++ b/samples/XCT.Sample/Pages/Views/Popups/ButtonPopup.xaml.cs
@@ -0,0 +1,9 @@
+namespace Xamarin.CommunityToolkit.Sample.Pages.Views.Popups
+{
+ public partial class ButtonPopup
+ {
+ public ButtonPopup() => InitializeComponent();
+
+ void Button_Clicked(object sender, System.EventArgs e) => Dismiss(null);
+ }
+}
\ No newline at end of file
diff --git a/samples/XCT.Sample/Pages/Views/Popups/CsharpBindingPopup.xaml b/samples/XCT.Sample/Pages/Views/Popups/CsharpBindingPopup.xaml
new file mode 100644
index 000000000..7565ac229
--- /dev/null
+++ b/samples/XCT.Sample/Pages/Views/Popups/CsharpBindingPopup.xaml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/XCT.Sample/Pages/Views/Popups/CsharpBindingPopup.xaml.cs b/samples/XCT.Sample/Pages/Views/Popups/CsharpBindingPopup.xaml.cs
new file mode 100644
index 000000000..1f4a2ba3a
--- /dev/null
+++ b/samples/XCT.Sample/Pages/Views/Popups/CsharpBindingPopup.xaml.cs
@@ -0,0 +1,13 @@
+using Xamarin.CommunityToolkit.Sample.ViewModels.Views.Popups;
+
+namespace Xamarin.CommunityToolkit.Sample.Pages.Views.Popups
+{
+ public partial class CsharpBindingPopup
+ {
+ public CsharpBindingPopup()
+ {
+ InitializeComponent();
+ BindingContext = new CsharpBindingPopupViewModel();
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/XCT.Sample/Pages/Views/Popups/MultipleButtonPopup.xaml b/samples/XCT.Sample/Pages/Views/Popups/MultipleButtonPopup.xaml
new file mode 100644
index 000000000..4e8471020
--- /dev/null
+++ b/samples/XCT.Sample/Pages/Views/Popups/MultipleButtonPopup.xaml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/XCT.Sample/Pages/Views/Popups/MultipleButtonPopup.xaml.cs b/samples/XCT.Sample/Pages/Views/Popups/MultipleButtonPopup.xaml.cs
new file mode 100644
index 000000000..ca000ce33
--- /dev/null
+++ b/samples/XCT.Sample/Pages/Views/Popups/MultipleButtonPopup.xaml.cs
@@ -0,0 +1,11 @@
+namespace Xamarin.CommunityToolkit.Sample.Pages.Views.Popups
+{
+ public partial class MultipleButtonPopup
+ {
+ public MultipleButtonPopup() => InitializeComponent();
+
+ void Cancel_Clicked(object sender, System.EventArgs e) => Dismiss(false);
+
+ void Okay_Clicked(object sender, System.EventArgs e) => Dismiss(true);
+ }
+}
\ No newline at end of file
diff --git a/samples/XCT.Sample/Pages/Views/Popups/NoLightDismissPopup.xaml b/samples/XCT.Sample/Pages/Views/Popups/NoLightDismissPopup.xaml
new file mode 100644
index 000000000..d5793142c
--- /dev/null
+++ b/samples/XCT.Sample/Pages/Views/Popups/NoLightDismissPopup.xaml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/XCT.Sample/Pages/Views/Popups/NoLightDismissPopup.xaml.cs b/samples/XCT.Sample/Pages/Views/Popups/NoLightDismissPopup.xaml.cs
new file mode 100644
index 000000000..bbaff0d7d
--- /dev/null
+++ b/samples/XCT.Sample/Pages/Views/Popups/NoLightDismissPopup.xaml.cs
@@ -0,0 +1,9 @@
+namespace Xamarin.CommunityToolkit.Sample.Pages.Views.Popups
+{
+ public partial class NoLightDismissPopup
+ {
+ public NoLightDismissPopup() => InitializeComponent();
+
+ void Button_Clicked(object sender, System.EventArgs e) => Dismiss(null);
+ }
+}
\ No newline at end of file
diff --git a/samples/XCT.Sample/Pages/Views/Popups/OpenedEventSimplePopup.xaml b/samples/XCT.Sample/Pages/Views/Popups/OpenedEventSimplePopup.xaml
new file mode 100644
index 000000000..24cf3d7db
--- /dev/null
+++ b/samples/XCT.Sample/Pages/Views/Popups/OpenedEventSimplePopup.xaml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/XCT.Sample/Pages/Views/Popups/OpenedEventSimplePopup.xaml.cs b/samples/XCT.Sample/Pages/Views/Popups/OpenedEventSimplePopup.xaml.cs
new file mode 100644
index 000000000..31080bab5
--- /dev/null
+++ b/samples/XCT.Sample/Pages/Views/Popups/OpenedEventSimplePopup.xaml.cs
@@ -0,0 +1,21 @@
+using Xamarin.CommunityToolkit.UI.Views;
+
+namespace Xamarin.CommunityToolkit.Sample.Pages.Views.Popups
+{
+ public partial class OpenedEventSimplePopup
+ {
+ public OpenedEventSimplePopup()
+ {
+ InitializeComponent();
+ Opened += OnOpened;
+ }
+
+ void OnOpened(object sender, PopupOpenedEventArgs e)
+ {
+ Opened -= OnOpened;
+
+ Title.Text = "Opened Event Popup";
+ Message.Text = "The content of this popup was updated after the popup was rendered";
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/XCT.Sample/Pages/Views/Popups/PopupAnchorPage.xaml b/samples/XCT.Sample/Pages/Views/Popups/PopupAnchorPage.xaml
new file mode 100644
index 000000000..2b95b7385
--- /dev/null
+++ b/samples/XCT.Sample/Pages/Views/Popups/PopupAnchorPage.xaml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/XCT.Sample/Pages/Views/Popups/PopupAnchorPage.xaml.cs b/samples/XCT.Sample/Pages/Views/Popups/PopupAnchorPage.xaml.cs
new file mode 100644
index 000000000..8aaf9728d
--- /dev/null
+++ b/samples/XCT.Sample/Pages/Views/Popups/PopupAnchorPage.xaml.cs
@@ -0,0 +1,35 @@
+using Xamarin.Forms;
+
+namespace Xamarin.CommunityToolkit.Sample.Pages.Views.Popups
+{
+ public partial class PopupAnchorPage
+ {
+ public PopupAnchorPage() => InitializeComponent();
+
+ void OnPanUpdated(object sender, PanUpdatedEventArgs e)
+ {
+ if (sender is Label label)
+ {
+ if (Device.RuntimePlatform == Device.Android)
+ {
+ label.TranslationX += e.TotalX;
+ label.TranslationY += e.TotalY;
+ }
+ else
+ {
+ switch (e.StatusType)
+ {
+ case GestureStatus.Running:
+ label.TranslationX = e.TotalX;
+ label.TranslationY = e.TotalY;
+ break;
+ case GestureStatus.Completed:
+ label.TranslationX += e.TotalX;
+ label.TranslationY += e.TotalY;
+ break;
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/XCT.Sample/Pages/Views/Popups/PopupPositionPage.xaml b/samples/XCT.Sample/Pages/Views/Popups/PopupPositionPage.xaml
new file mode 100644
index 000000000..fdb89a59e
--- /dev/null
+++ b/samples/XCT.Sample/Pages/Views/Popups/PopupPositionPage.xaml
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/XCT.Sample/Pages/Views/Popups/PopupPositionPage.xaml.cs b/samples/XCT.Sample/Pages/Views/Popups/PopupPositionPage.xaml.cs
new file mode 100644
index 000000000..090c7d20b
--- /dev/null
+++ b/samples/XCT.Sample/Pages/Views/Popups/PopupPositionPage.xaml.cs
@@ -0,0 +1,7 @@
+namespace Xamarin.CommunityToolkit.Sample.Pages.Views.Popups
+{
+ public partial class PopupPositionPage
+ {
+ public PopupPositionPage() => InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/samples/XCT.Sample/Pages/Views/Popups/PopupSize.cs b/samples/XCT.Sample/Pages/Views/Popups/PopupSize.cs
new file mode 100644
index 000000000..00cc6be6b
--- /dev/null
+++ b/samples/XCT.Sample/Pages/Views/Popups/PopupSize.cs
@@ -0,0 +1,13 @@
+using Xamarin.Forms;
+
+namespace Xamarin.CommunityToolkit.Sample.Pages.Views.Popups
+{
+ static class PopupSize
+ {
+ public static Size Android => new Size(1200, 1000);
+
+ public static Size iOS => new Size(250, 350);
+
+ public static Size UWP => new Size(300, 400);
+ }
+}
diff --git a/samples/XCT.Sample/Pages/Views/Popups/ReturnResultPopup.xaml b/samples/XCT.Sample/Pages/Views/Popups/ReturnResultPopup.xaml
new file mode 100644
index 000000000..315d3329c
--- /dev/null
+++ b/samples/XCT.Sample/Pages/Views/Popups/ReturnResultPopup.xaml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/XCT.Sample/Pages/Views/Popups/ReturnResultPopup.xaml.cs b/samples/XCT.Sample/Pages/Views/Popups/ReturnResultPopup.xaml.cs
new file mode 100644
index 000000000..871627399
--- /dev/null
+++ b/samples/XCT.Sample/Pages/Views/Popups/ReturnResultPopup.xaml.cs
@@ -0,0 +1,12 @@
+namespace Xamarin.CommunityToolkit.Sample.Pages.Views.Popups
+{
+ public partial class ReturnResultPopup
+ {
+ public ReturnResultPopup() =>
+ InitializeComponent();
+
+ protected override string GetLightDismissResult() => "Light Dismiss";
+
+ void Button_Clicked(object sender, System.EventArgs e) => Dismiss("Close button tapped");
+ }
+}
\ No newline at end of file
diff --git a/samples/XCT.Sample/Pages/Views/Popups/SimplePopup.xaml b/samples/XCT.Sample/Pages/Views/Popups/SimplePopup.xaml
new file mode 100644
index 000000000..5c88e5097
--- /dev/null
+++ b/samples/XCT.Sample/Pages/Views/Popups/SimplePopup.xaml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/XCT.Sample/Pages/Views/Popups/SimplePopup.xaml.cs b/samples/XCT.Sample/Pages/Views/Popups/SimplePopup.xaml.cs
new file mode 100644
index 000000000..0adc37d6a
--- /dev/null
+++ b/samples/XCT.Sample/Pages/Views/Popups/SimplePopup.xaml.cs
@@ -0,0 +1,7 @@
+namespace Xamarin.CommunityToolkit.Sample.Pages.Views.Popups
+{
+ public partial class SimplePopup
+ {
+ public SimplePopup() => InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/samples/XCT.Sample/Pages/Views/Popups/ToggleSizePopup.xaml b/samples/XCT.Sample/Pages/Views/Popups/ToggleSizePopup.xaml
new file mode 100644
index 000000000..5926e946b
--- /dev/null
+++ b/samples/XCT.Sample/Pages/Views/Popups/ToggleSizePopup.xaml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/XCT.Sample/Pages/Views/Popups/ToggleSizePopup.xaml.cs b/samples/XCT.Sample/Pages/Views/Popups/ToggleSizePopup.xaml.cs
new file mode 100644
index 000000000..a4c34429c
--- /dev/null
+++ b/samples/XCT.Sample/Pages/Views/Popups/ToggleSizePopup.xaml.cs
@@ -0,0 +1,27 @@
+using Xamarin.Forms;
+
+namespace Xamarin.CommunityToolkit.Sample.Pages.Views.Popups
+{
+ public partial class ToggleSizePopup
+ {
+ Size originalSize;
+
+ public ToggleSizePopup()
+ {
+ InitializeComponent();
+ originalSize = Size;
+ }
+
+ void Button_Clicked(object sender, System.EventArgs e)
+ {
+ if (originalSize == Size)
+ {
+ Size = new Size(originalSize.Width * 1.25, originalSize.Height * 1.25);
+ }
+ else
+ {
+ Size = originalSize;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/XCT.Sample/Pages/Views/Popups/TransparentPopup.xaml b/samples/XCT.Sample/Pages/Views/Popups/TransparentPopup.xaml
new file mode 100644
index 000000000..5d7a58609
--- /dev/null
+++ b/samples/XCT.Sample/Pages/Views/Popups/TransparentPopup.xaml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/XCT.Sample/Pages/Views/Popups/TransparentPopup.xaml.cs b/samples/XCT.Sample/Pages/Views/Popups/TransparentPopup.xaml.cs
new file mode 100644
index 000000000..c260895a6
--- /dev/null
+++ b/samples/XCT.Sample/Pages/Views/Popups/TransparentPopup.xaml.cs
@@ -0,0 +1,7 @@
+namespace Xamarin.CommunityToolkit.Sample.Pages.Views.Popups
+{
+ public partial class TransparentPopup
+ {
+ public TransparentPopup() => InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/samples/XCT.Sample/Pages/Views/Popups/XamlBindingPopup.xaml b/samples/XCT.Sample/Pages/Views/Popups/XamlBindingPopup.xaml
new file mode 100644
index 000000000..c15942eda
--- /dev/null
+++ b/samples/XCT.Sample/Pages/Views/Popups/XamlBindingPopup.xaml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/XCT.Sample/Pages/Views/Popups/XamlBindingPopup.xaml.cs b/samples/XCT.Sample/Pages/Views/Popups/XamlBindingPopup.xaml.cs
new file mode 100644
index 000000000..16d026950
--- /dev/null
+++ b/samples/XCT.Sample/Pages/Views/Popups/XamlBindingPopup.xaml.cs
@@ -0,0 +1,7 @@
+namespace Xamarin.CommunityToolkit.Sample.Pages.Views.Popups
+{
+ public partial class XamlBindingPopup
+ {
+ public XamlBindingPopup() => InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/samples/XCT.Sample/ViewModels/Views/PopupControlViewModel.cs b/samples/XCT.Sample/ViewModels/Views/PopupControlViewModel.cs
new file mode 100644
index 000000000..2e6fd0d7d
--- /dev/null
+++ b/samples/XCT.Sample/ViewModels/Views/PopupControlViewModel.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Generic;
+using System.Windows.Input;
+using Xamarin.CommunityToolkit.Extensions;
+using Xamarin.CommunityToolkit.Sample.Models;
+using Xamarin.CommunityToolkit.Sample.Pages.Views.Popups;
+using Xamarin.CommunityToolkit.UI.Views;
+using Xamarin.Forms;
+
+namespace Xamarin.CommunityToolkit.Sample.ViewModels.Views
+{
+ public class PopupGalleryViewModel
+ {
+ INavigation Navigation => App.Current.MainPage.Navigation;
+
+ public IEnumerable Examples { get; } = new List {
+ new SectionModel(typeof(SimplePopup), "Simple Popup", Color.Red, "Displays a basic popup centered on the screen"),
+ new SectionModel(typeof(PopupPositionPage), "Custom Positioning Popup", Color.Red, "Displays a basic popup anywhere on the screen using VerticalOptions and HorizontalOptions"),
+ new SectionModel(typeof(ButtonPopup), "Popup With 1 Button", Color.Red, "Displays a basic popup with a confirm button"),
+ new SectionModel(typeof(MultipleButtonPopup), "Popup With Multiple Buttons", Color.Red, "Displays a basic popup with a cancel and confirm button"),
+ new SectionModel(typeof(NoLightDismissPopup), "Simple Popup Without Light Dismiss", Color.Red, "Displays a basic popup but does not allow the user to close it if they tap outside of the popup. In other words the LightDismiss is set to false."),
+ new SectionModel(typeof(ToggleSizePopup), "Toggle Size Popup", Color.Red, "Displays a popup that can have it's size updated by pressing a button"),
+ new SectionModel(typeof(TransparentPopup), "Transparent Popup", Color.Red, "Displays a popup with a transparent background"),
+ new SectionModel(typeof(PopupAnchorPage), "Anchor Popup", Color.Red, "Popups can be anchored to other view's on the screen"),
+ new SectionModel(typeof(OpenedEventSimplePopup), "Opened Event Popup", Color.Red, "Popup with opened event"),
+ new SectionModel(typeof(ReturnResultPopup), "Return Result Popup", Color.Red, "A popup that returns a string message when dismissed"),
+ new SectionModel(typeof(XamlBindingPopup), "Xaml Binding Popup", Color.Red, "A simple popup that uses XAML BindingContext"),
+ new SectionModel(typeof(CsharpBindingPopup), "C# Binding Popup", Color.Red, "A simple popup that uses C# BindingContext")
+ };
+
+ public ICommand DisplayPopup => new Command(OnDisplayPopup);
+
+ async void OnDisplayPopup(Type popupType)
+ {
+ var view = (VisualElement)Activator.CreateInstance(popupType);
+
+ if (view is Popup popup)
+ {
+ var result = await Navigation.ShowPopupAsync(popup);
+ await App.Current.MainPage.DisplayAlert("Popup Result", result, "OKAY");
+ }
+ else if (view is BasePopup basePopup)
+ {
+ Navigation.ShowPopup(basePopup);
+ }
+ else if (view is Page page)
+ {
+ await Navigation.PushAsync(page);
+ }
+ }
+ }
+}
diff --git a/samples/XCT.Sample/ViewModels/Views/Popups/CsharpBindingPopupViewModel.cs b/samples/XCT.Sample/ViewModels/Views/Popups/CsharpBindingPopupViewModel.cs
new file mode 100644
index 000000000..ce53e9799
--- /dev/null
+++ b/samples/XCT.Sample/ViewModels/Views/Popups/CsharpBindingPopupViewModel.cs
@@ -0,0 +1,9 @@
+namespace Xamarin.CommunityToolkit.Sample.ViewModels.Views.Popups
+{
+ public class CsharpBindingPopupViewModel
+ {
+ public string Title { get; } = "Xaml Binding Popup";
+
+ public string Message { get; } = "This is a native popup with a Xamarin.Forms View being rendered. The behaviors of the popup will confirm to 100% native look and feel, but still allows you to use your Xamarin.Forms controls.";
+ }
+}
diff --git a/samples/XCT.Sample/ViewModels/Views/Popups/PopupAnchorViewModel.cs b/samples/XCT.Sample/ViewModels/Views/Popups/PopupAnchorViewModel.cs
new file mode 100644
index 000000000..440bf9c3d
--- /dev/null
+++ b/samples/XCT.Sample/ViewModels/Views/Popups/PopupAnchorViewModel.cs
@@ -0,0 +1,26 @@
+using System.Windows.Input;
+using Xamarin.CommunityToolkit.Extensions;
+using Xamarin.CommunityToolkit.Sample.Pages.Views.Popups;
+using Xamarin.Forms;
+
+namespace Xamarin.CommunityToolkit.Sample.ViewModels.Views.Popups
+{
+ public class PopupAnchorViewModel
+ {
+ public PopupAnchorViewModel()
+ {
+ ShowPopup = new Command(OnShowPopup);
+ }
+
+ INavigation Navigation => Application.Current.MainPage.Navigation;
+
+ public ICommand ShowPopup { get; }
+
+ void OnShowPopup(View anchor)
+ {
+ var popup = new TransparentPopup();
+ popup.Anchor = anchor;
+ Navigation.ShowPopup(popup);
+ }
+ }
+}
diff --git a/samples/XCT.Sample/ViewModels/Views/Popups/PopupPositionViewModel.cs b/samples/XCT.Sample/ViewModels/Views/Popups/PopupPositionViewModel.cs
new file mode 100644
index 000000000..4e12e874d
--- /dev/null
+++ b/samples/XCT.Sample/ViewModels/Views/Popups/PopupPositionViewModel.cs
@@ -0,0 +1,79 @@
+using System.Windows.Input;
+using Xamarin.CommunityToolkit.Extensions;
+using Xamarin.CommunityToolkit.Sample.Pages.Views.Popups;
+using Xamarin.Forms;
+
+namespace Xamarin.CommunityToolkit.Sample.ViewModels.Views.Popups
+{
+ public class PopupPositionViewModel
+ {
+ public PopupPositionViewModel()
+ {
+ DisplayPopup = new Command(OnDisplayPopup);
+ }
+
+ INavigation Navigation => Application.Current.MainPage.Navigation;
+
+ public ICommand DisplayPopup { get; }
+
+ void OnDisplayPopup(PopupPosition position)
+ {
+ var popup = new SimplePopup();
+
+ switch (position)
+ {
+ case PopupPosition.TopLeft:
+ popup.VerticalOptions = new LayoutOptions(LayoutAlignment.Start, true);
+ popup.HorizontalOptions = new LayoutOptions(LayoutAlignment.Start, true);
+ break;
+ case PopupPosition.Top:
+ popup.VerticalOptions = new LayoutOptions(LayoutAlignment.Start, true);
+ popup.HorizontalOptions = new LayoutOptions(LayoutAlignment.Center, true);
+ break;
+ case PopupPosition.TopRight:
+ popup.VerticalOptions = new LayoutOptions(LayoutAlignment.Start, true);
+ popup.HorizontalOptions = new LayoutOptions(LayoutAlignment.End, true);
+ break;
+ case PopupPosition.Left:
+ popup.VerticalOptions = new LayoutOptions(LayoutAlignment.Center, true);
+ popup.HorizontalOptions = new LayoutOptions(LayoutAlignment.Start, true);
+ break;
+ case PopupPosition.Center:
+ popup.VerticalOptions = new LayoutOptions(LayoutAlignment.Center, true);
+ popup.HorizontalOptions = new LayoutOptions(LayoutAlignment.Center, true);
+ break;
+ case PopupPosition.Right:
+ popup.VerticalOptions = new LayoutOptions(LayoutAlignment.Center, true);
+ popup.HorizontalOptions = new LayoutOptions(LayoutAlignment.End, true);
+ break;
+ case PopupPosition.BottomLeft:
+ popup.VerticalOptions = new LayoutOptions(LayoutAlignment.End, true);
+ popup.HorizontalOptions = new LayoutOptions(LayoutAlignment.Start, true);
+ break;
+ case PopupPosition.Bottom:
+ popup.VerticalOptions = new LayoutOptions(LayoutAlignment.End, true);
+ popup.HorizontalOptions = new LayoutOptions(LayoutAlignment.Center, true);
+ break;
+ case PopupPosition.BottomRight:
+ popup.VerticalOptions = new LayoutOptions(LayoutAlignment.End, true);
+ popup.HorizontalOptions = new LayoutOptions(LayoutAlignment.End, true);
+ break;
+ }
+
+ Navigation.ShowPopup(popup);
+ }
+
+ public enum PopupPosition
+ {
+ TopLeft = 0,
+ Top = 1,
+ TopRight = 2,
+ Left = 3,
+ Center = 4,
+ Right = 5,
+ BottomLeft = 6,
+ Bottom = 7,
+ BottomRight = 8
+ }
+ }
+}
diff --git a/samples/XCT.Sample/ViewModels/Views/Popups/XamlBindingPopupViewModel.cs b/samples/XCT.Sample/ViewModels/Views/Popups/XamlBindingPopupViewModel.cs
new file mode 100644
index 000000000..421f699dc
--- /dev/null
+++ b/samples/XCT.Sample/ViewModels/Views/Popups/XamlBindingPopupViewModel.cs
@@ -0,0 +1,9 @@
+namespace Xamarin.CommunityToolkit.Sample.ViewModels.Views.Popups
+{
+ public class XamlBindingPopupViewModel
+ {
+ public string Title { get; } = "Xaml Binding Popup";
+
+ public string Message { get; } = "This is a native popup with a Xamarin.Forms View being rendered. The behaviors of the popup will confirm to 100% native look and feel, but still allows you to use your Xamarin.Forms controls.";
+ }
+}
diff --git a/samples/XCT.Sample/ViewModels/Views/ViewsGalleryViewModel.cs b/samples/XCT.Sample/ViewModels/Views/ViewsGalleryViewModel.cs
index 26dd7d0dc..1a1bc370d 100644
--- a/samples/XCT.Sample/ViewModels/Views/ViewsGalleryViewModel.cs
+++ b/samples/XCT.Sample/ViewModels/Views/ViewsGalleryViewModel.cs
@@ -48,7 +48,10 @@ public class ViewsGalleryViewModel : BaseGalleryViewModel
"A control to display a set of tabs and their respective content."),
new SectionModel(typeof(UniformGridPage), "UniformGrid",
- "The UniformGrid is just like the Grid, with all rows and columns will have the same size.")
+ "The UniformGrid is just like the Grid, with all rows and columns will have the same size."),
+
+ new SectionModel(typeof(PopupGalleryPage), "Popup",
+ "The popup control renders native popups from the shared code. This page demonstrates a variety of different techniques for displaying native popups.")
};
}
}
\ No newline at end of file
diff --git a/samples/XCT.Sample/Xamarin.CommunityToolkit.Sample.csproj b/samples/XCT.Sample/Xamarin.CommunityToolkit.Sample.csproj
index af5548634..132d64517 100644
--- a/samples/XCT.Sample/Xamarin.CommunityToolkit.Sample.csproj
+++ b/samples/XCT.Sample/Xamarin.CommunityToolkit.Sample.csproj
@@ -12,6 +12,7 @@
+
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Extensions/NavigableElement/NavigableElementExtensions.shared.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Extensions/NavigableElement/NavigableElementExtensions.shared.cs
new file mode 100644
index 000000000..78b262388
--- /dev/null
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Extensions/NavigableElement/NavigableElementExtensions.shared.cs
@@ -0,0 +1,42 @@
+using System.Threading.Tasks;
+using Xamarin.CommunityToolkit.UI.Views;
+using Xamarin.Forms;
+
+namespace Xamarin.CommunityToolkit.Extensions
+{
+ ///
+ /// Extension methods for
+ ///
+ public static class NavigableElementExtensions
+ {
+ ///
+ /// Displays a popup.
+ ///
+ ///
+ /// The current that has a valid .
+ ///
+ ///
+ /// The to display.
+ ///
+ public static void ShowPopup(this NavigableElement element, BasePopup popup) =>
+ element.Navigation.ShowPopup(popup);
+
+ ///
+ /// Displays a poup and returns a result.
+ ///
+ ///
+ /// The result that is returned when the popup is dismissed.
+ ///
+ ///
+ /// The current that has a valid .
+ ///
+ ///
+ /// The to display.
+ ///
+ ///
+ /// A task that will complete once the is dismissed.
+ ///
+ public static Task ShowPopupAsync(this NavigableElement element, Popup popup) =>
+ element.Navigation.ShowPopupAsync(popup);
+ }
+}
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Extensions/Navigation/NavigationExtensions.android.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Extensions/Navigation/NavigationExtensions.android.cs
new file mode 100644
index 000000000..668c99914
--- /dev/null
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Extensions/Navigation/NavigationExtensions.android.cs
@@ -0,0 +1,18 @@
+using System.Threading.Tasks;
+using Xamarin.CommunityToolkit.UI.Views;
+using Xamarin.Forms.Platform.Android;
+
+namespace Xamarin.CommunityToolkit.Extensions
+{
+ public static partial class NavigationExtensions
+ {
+ static void PlatformShowPopup(BasePopup popup) =>
+ Platform.CreateRendererWithContext(popup, ToolkitPlatform.Context);
+
+ static Task PlatformShowPopupAsync(Popup popup)
+ {
+ PlatformShowPopup(popup);
+ return popup.Result;
+ }
+ }
+}
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Extensions/Navigation/NavigationExtensions.ios.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Extensions/Navigation/NavigationExtensions.ios.cs
new file mode 100644
index 000000000..30620892f
--- /dev/null
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Extensions/Navigation/NavigationExtensions.ios.cs
@@ -0,0 +1,18 @@
+using System.Threading.Tasks;
+using Xamarin.CommunityToolkit.UI.Views;
+using Xamarin.Forms.Platform.iOS;
+
+namespace Xamarin.CommunityToolkit.Extensions
+{
+ public static partial class NavigationExtensions
+ {
+ static void PlatformShowPopup(BasePopup popup) =>
+ Platform.CreateRenderer(popup);
+
+ static Task PlatformShowPopupAsync(Popup popup)
+ {
+ PlatformShowPopup(popup);
+ return popup.Result;
+ }
+ }
+}
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Extensions/Navigation/NavigationExtensions.netstandard.macos.tvos.watchos.tizen.wpf.gtk.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Extensions/Navigation/NavigationExtensions.netstandard.macos.tvos.watchos.tizen.wpf.gtk.cs
new file mode 100644
index 000000000..61a1c791b
--- /dev/null
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Extensions/Navigation/NavigationExtensions.netstandard.macos.tvos.watchos.tizen.wpf.gtk.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Threading.Tasks;
+using Xamarin.CommunityToolkit.UI.Views;
+using Xamarin.Forms;
+
+namespace Xamarin.CommunityToolkit.Extensions
+{
+ public static partial class NavigationExtensions
+ {
+ static void PlatformShowPopup(BasePopup popup) =>
+ throw new NotSupportedException($"The current platform '{Device.RuntimePlatform}' does not support Xamarin.CommunityToolkit.BasePopup");
+
+ static Task PlatformShowPopupAsync(Popup popup) =>
+ throw new NotSupportedException($"The current platform '{Device.RuntimePlatform}' does not support Xamarin.CommunityToolkit.Popup.");
+ }
+}
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Extensions/Navigation/NavigationExtensions.shared.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Extensions/Navigation/NavigationExtensions.shared.cs
new file mode 100644
index 000000000..a25a717bf
--- /dev/null
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Extensions/Navigation/NavigationExtensions.shared.cs
@@ -0,0 +1,42 @@
+using System.Threading.Tasks;
+using Xamarin.CommunityToolkit.UI.Views;
+using Xamarin.Forms;
+
+namespace Xamarin.CommunityToolkit.Extensions
+{
+ ///
+ /// Extension methods for .
+ ///
+ public static partial class NavigationExtensions
+ {
+ ///
+ /// Displays a popup.
+ ///
+ ///
+ /// The current .
+ ///
+ ///
+ /// The to display.
+ ///
+ public static void ShowPopup(this INavigation navigation, BasePopup popup) =>
+ PlatformShowPopup(popup);
+
+ ///
+ /// Displays a popup and returns a result.
+ ///
+ ///
+ /// The result that is returned when the popup is dismissed.
+ ///
+ ///
+ /// The current .
+ ///
+ ///
+ /// The to display.
+ ///
+ ///
+ /// A task that will complete once the is dismissed.
+ ///
+ public static Task ShowPopupAsync(this INavigation navigation, Popup popup) =>
+ PlatformShowPopupAsync(popup);
+ }
+}
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Extensions/Navigation/NavigationExtensions.uwp.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Extensions/Navigation/NavigationExtensions.uwp.cs
new file mode 100644
index 000000000..fb6c4decf
--- /dev/null
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Extensions/Navigation/NavigationExtensions.uwp.cs
@@ -0,0 +1,40 @@
+using System.Linq;
+using System.Threading.Tasks;
+using Xamarin.CommunityToolkit.UI.Views;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.UWP;
+
+namespace Xamarin.CommunityToolkit.Extensions
+{
+ public static partial class NavigationExtensions
+ {
+ static void PlatformShowPopup(BasePopup popup)
+ {
+ popup.Parent = GetCurrentPage(Application.Current.MainPage);
+ Platform.CreateRenderer(popup);
+
+ // https://github.com/xamarin/Xamarin.Forms/blob/0c95d0976cc089fe72476fb037851a64987de83c/Xamarin.Forms.Platform.iOS/PageExtensions.cs#L44
+ Page GetCurrentPage(Page currentPage)
+ {
+ if (currentPage.NavigationProxy.ModalStack.LastOrDefault() is Page modal)
+ return modal;
+ else if (currentPage is MasterDetailPage mdp)
+ return GetCurrentPage(mdp.Detail);
+ else if (currentPage is FlyoutPage fp)
+ return GetCurrentPage(fp.Detail);
+ else if (currentPage is Shell shell && shell.CurrentItem?.CurrentItem is IShellSectionController ssc)
+ return ssc.PresentedPage;
+ else if (currentPage is IPageContainer pc)
+ return GetCurrentPage(pc.CurrentPage);
+ else
+ return currentPage;
+ }
+ }
+
+ static Task PlatformShowPopupAsync(Popup popup)
+ {
+ PlatformShowPopup(popup);
+ return popup.Result;
+ }
+ }
+}
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Extensions/VisualElementExtension.shared.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Extensions/VisualElement/VisualElementExtension.shared.cs
similarity index 91%
rename from src/CommunityToolkit/Xamarin.CommunityToolkit/Extensions/VisualElementExtension.shared.cs
rename to src/CommunityToolkit/Xamarin.CommunityToolkit/Extensions/VisualElement/VisualElementExtension.shared.cs
index d7f4f9249..8906ec030 100644
--- a/src/CommunityToolkit/Xamarin.CommunityToolkit/Extensions/VisualElementExtension.shared.cs
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Extensions/VisualElement/VisualElementExtension.shared.cs
@@ -3,7 +3,10 @@
namespace Xamarin.CommunityToolkit.Extensions
{
- public static class VisualElementExtension
+ ///
+ /// Extension methods for .
+ ///
+ public static partial class VisualElementExtension
{
public static Task ColorTo(this VisualElement element, Color color, uint length = 250u, Easing easing = null)
{
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Extensions/VisualElement/VisualElementExtensions.uwp.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Extensions/VisualElement/VisualElementExtensions.uwp.cs
new file mode 100644
index 000000000..53ddcc1b0
--- /dev/null
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Extensions/VisualElement/VisualElementExtensions.uwp.cs
@@ -0,0 +1,51 @@
+using System;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.UWP;
+
+namespace Xamarin.CommunityToolkit.Extensions
+{
+ ///
+ /// Extension methods for .
+ ///
+ public static partial class VisualElementExtensions
+ {
+
+ ///
+ /// cleanup object to dispose and
+ /// destroy resources.
+ ///
+ ///
+ /// The to cleanup.
+ ///
+ ///
+ /// This extension method is ported from Xamarin.Forms and should remain in sync.
+ ///
+ internal static void Cleanup(this VisualElement self)
+ {
+ if (self == null)
+ throw new ArgumentNullException("self");
+
+ var renderer = Platform.GetRenderer(self);
+
+ foreach (var element in self.Descendants())
+ {
+ var visual = element as VisualElement;
+ if (visual == null)
+ continue;
+
+ var childRenderer = Platform.GetRenderer(visual);
+ if (childRenderer != null)
+ {
+ childRenderer.Dispose();
+ Platform.SetRenderer(visual, null);
+ }
+ }
+
+ if (renderer != null)
+ {
+ renderer.Dispose();
+ Platform.SetRenderer(self, null);
+ }
+ }
+ }
+}
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Startup/ToolkitPlatform.android.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Startup/ToolkitPlatform.android.cs
new file mode 100644
index 000000000..60d866add
--- /dev/null
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Startup/ToolkitPlatform.android.cs
@@ -0,0 +1,24 @@
+using Android.Content;
+using Xamarin.Forms.Platform.Android;
+
+namespace Xamarin.CommunityToolkit
+{
+ ///
+ /// Platform extension methods.
+ ///
+ internal static class ToolkitPlatform
+ {
+ ///
+ /// Gets the .
+ ///
+ internal static Context Context
+ {
+ get
+ {
+ var page = Forms.Application.Current.MainPage;
+ var renderer = page.GetRenderer();
+ return renderer.View.Context;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/Popup/Android/PopupRenderer.android.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/Popup/Android/PopupRenderer.android.cs
new file mode 100644
index 000000000..0642dbd37
--- /dev/null
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/Popup/Android/PopupRenderer.android.cs
@@ -0,0 +1,350 @@
+using System;
+using System.ComponentModel;
+using Android.App;
+using Android.Content;
+using Android.Graphics.Drawables;
+using Android.Views;
+using Android.Widget;
+using Xamarin.CommunityToolkit.UI.Views;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.Android;
+using static Android.App.ActionBar;
+using AView = Android.Views.View;
+using FormsPlatform = Xamarin.Forms.Platform.Android.Platform;
+using GravityFlags = Android.Views.GravityFlags;
+
+[assembly: ExportRenderer(typeof(BasePopup), typeof(PopupRenderer))]
+
+namespace Xamarin.CommunityToolkit.UI.Views
+{
+ public class PopupRenderer : Dialog, IVisualElementRenderer, IDialogInterfaceOnCancelListener
+ {
+ int? defaultLabelFor;
+ VisualElementTracker tracker;
+ ContainerView container;
+ bool isDisposed;
+
+ public BasePopup Element { get; private set; }
+
+ void IVisualElementRenderer.UpdateLayout() => tracker?.UpdateLayout();
+
+ VisualElement IVisualElementRenderer.Element => Element;
+
+ AView IVisualElementRenderer.View => container;
+
+ ViewGroup IVisualElementRenderer.ViewGroup => null;
+
+ VisualElementTracker IVisualElementRenderer.Tracker => tracker;
+
+ public event EventHandler ElementChanged;
+
+ public event EventHandler ElementPropertyChanged;
+
+ public PopupRenderer(Context context)
+ : base(context)
+ {
+ }
+
+ void IVisualElementRenderer.SetElement(VisualElement element)
+ {
+ if (element == null)
+ throw new ArgumentNullException(nameof(element));
+
+ if (!(element is BasePopup popup))
+ throw new ArgumentNullException("Element is not of type " + typeof(BasePopup), nameof(element));
+
+ var oldElement = Element;
+ Element = popup;
+ CreateControl();
+
+ if (oldElement != null)
+ oldElement.PropertyChanged -= OnElementPropertyChanged;
+
+ element.PropertyChanged += OnElementPropertyChanged;
+
+ if (tracker == null)
+ tracker = new VisualElementTracker(this);
+
+ OnElementChanged(new ElementChangedEventArgs(oldElement, Element));
+ }
+
+ protected virtual void OnElementChanged(ElementChangedEventArgs e)
+ {
+ if (e.NewElement != null && !isDisposed)
+ {
+ SetEvents();
+ SetColor();
+ SetSize();
+ SetAnchor();
+ SetLightDismiss();
+
+ Show();
+ }
+
+ ElementChanged?.Invoke(this, new VisualElementChangedEventArgs(e.OldElement, e.NewElement));
+ }
+
+ public override void Show()
+ {
+ base.Show();
+ Element?.OnOpened();
+ }
+
+ protected virtual void OnElementPropertyChanged(object sender, PropertyChangedEventArgs args)
+ {
+ if (args.PropertyName == BasePopup.VerticalOptionsProperty.PropertyName ||
+ args.PropertyName == BasePopup.HorizontalOptionsProperty.PropertyName ||
+ args.PropertyName == BasePopup.SizeProperty.PropertyName)
+ {
+ SetSize();
+ SetAnchor();
+ }
+ else if (args.PropertyName == BasePopup.ColorProperty.PropertyName)
+ {
+ SetColor();
+ }
+
+ ElementPropertyChanged?.Invoke(this, args);
+ }
+
+ void CreateControl()
+ {
+ if (container == null)
+ {
+ container = new ContainerView(Context, Element.Content);
+ SetContentView(container);
+ }
+ }
+
+ void SetEvents()
+ {
+ SetOnCancelListener(this);
+ Element.Dismissed += OnDismissed;
+ }
+
+ void SetColor()
+ {
+ Window.SetBackgroundDrawable(new ColorDrawable(Element.Color.ToAndroid()));
+ }
+
+ void SetSize()
+ {
+ if (Element.Size != default)
+ {
+ var decorView = (ViewGroup)Window.DecorView;
+ var child = (FrameLayout)decorView.GetChildAt(0);
+
+ var childLayoutParams = (FrameLayout.LayoutParams)child.LayoutParameters;
+ childLayoutParams.Width = (int)Element.Size.Width;
+ childLayoutParams.Height = (int)Element.Size.Height;
+ child.LayoutParameters = childLayoutParams;
+
+ var horizontalParams = -1;
+ switch (Element.Content.HorizontalOptions.Alignment)
+ {
+ case LayoutAlignment.Center:
+ case LayoutAlignment.End:
+ case LayoutAlignment.Start:
+ horizontalParams = LayoutParams.WrapContent;
+ break;
+ case LayoutAlignment.Fill:
+ horizontalParams = LayoutParams.MatchParent;
+ break;
+ }
+
+ var verticalParams = -1;
+ switch (Element.Content.VerticalOptions.Alignment)
+ {
+ case LayoutAlignment.Center:
+ case LayoutAlignment.End:
+ case LayoutAlignment.Start:
+ verticalParams = LayoutParams.WrapContent;
+ break;
+ case LayoutAlignment.Fill:
+ verticalParams = LayoutParams.MatchParent;
+ break;
+ }
+
+ if (Element.Content.WidthRequest > -1)
+ {
+ var inputMeasuredWidth = Element.Content.WidthRequest > Element.Size.Width ?
+ (int)Element.Size.Width : (int)Element.Content.WidthRequest;
+ container.Measure(inputMeasuredWidth, (int)MeasureSpecMode.Unspecified);
+ horizontalParams = container.MeasuredWidth;
+ }
+ else
+ {
+ container.Measure((int)Element.Size.Width, (int)MeasureSpecMode.Unspecified);
+ horizontalParams = container.MeasuredWidth > Element.Size.Width ?
+ (int)Element.Size.Width : container.MeasuredWidth;
+ }
+
+ if (Element.Content.HeightRequest > -1)
+ {
+ verticalParams = (int)Element.Content.HeightRequest;
+ }
+ else
+ {
+ var inputMeasuredWidth = Element.Content.WidthRequest > -1 ? horizontalParams : (int)Element.Size.Width;
+ container.Measure(inputMeasuredWidth, (int)MeasureSpecMode.Unspecified);
+ verticalParams = container.MeasuredHeight > Element.Size.Height ?
+ (int)Element.Size.Height : container.MeasuredHeight;
+ }
+
+ var containerLayoutParams = new FrameLayout.LayoutParams(horizontalParams, verticalParams);
+
+ switch (Element.Content.VerticalOptions.Alignment)
+ {
+ case LayoutAlignment.Start:
+ containerLayoutParams.Gravity = GravityFlags.Top;
+ break;
+ case LayoutAlignment.Center:
+ case LayoutAlignment.Fill:
+ containerLayoutParams.Gravity = GravityFlags.FillVertical;
+ containerLayoutParams.Height = (int)Element.Size.Height;
+ container.MatchHeight = true;
+ break;
+ case LayoutAlignment.End:
+ containerLayoutParams.Gravity = GravityFlags.Bottom;
+ break;
+ }
+
+ switch (Element.Content.HorizontalOptions.Alignment)
+ {
+ case LayoutAlignment.Start:
+ containerLayoutParams.Gravity |= GravityFlags.Left;
+ break;
+ case LayoutAlignment.Center:
+ case LayoutAlignment.Fill:
+ containerLayoutParams.Gravity |= GravityFlags.FillHorizontal;
+ containerLayoutParams.Width = (int)Element.Size.Width;
+ container.MatchWidth = true;
+ break;
+ case LayoutAlignment.End:
+ containerLayoutParams.Gravity |= GravityFlags.Right;
+ break;
+ }
+
+ container.LayoutParameters = containerLayoutParams;
+ }
+ }
+
+ void SetAnchor()
+ {
+ if (Element.Anchor != null)
+ {
+ var anchorView = FormsPlatform.GetRenderer(Element.Anchor).View;
+ var locationOnScreen = new int[2];
+ anchorView.GetLocationOnScreen(locationOnScreen);
+
+ Window.SetGravity(GravityFlags.Top | GravityFlags.Left);
+ Window.DecorView.Measure((int)MeasureSpecMode.Unspecified, (int)MeasureSpecMode.Unspecified);
+
+ // This logic is tricky, please read these notes if you need to modify
+ // Android window coordinate starts (0,0) at the top left and (max,max) at the bottom right. All of the positions
+ // that are being handled in this operation assume the point is at the top left of the rectangle. This means the
+ // calculation operates in this order:
+ // 1. Calculate top-left position of Anchor
+ // 2. Calculate the Actual Center of the Anchor by adding the width /2 and height / 2
+ // 3. Calculate the top-left point of where the dialog should be positioned by subtracting the Width / 2 and height / 2
+ // of the dialog that is about to be drawn.
+ Window.Attributes.X = locationOnScreen[0] + (anchorView.Width / 2) - (Window.DecorView.MeasuredWidth / 2);
+ Window.Attributes.Y = locationOnScreen[1] + (anchorView.Height / 2) - (Window.DecorView.MeasuredHeight / 2);
+ }
+ else
+ {
+ SetDialogPosition();
+ }
+ }
+
+ void SetLightDismiss()
+ {
+ if (Element.IsLightDismissEnabled)
+ return;
+
+ SetCancelable(false);
+ SetCanceledOnTouchOutside(false);
+ }
+
+ void SetDialogPosition()
+ {
+ var gravityFlags = GravityFlags.Center;
+ switch (Element.VerticalOptions.Alignment)
+ {
+ case LayoutAlignment.Start:
+ gravityFlags = GravityFlags.Top;
+ break;
+ case LayoutAlignment.End:
+ gravityFlags = GravityFlags.Bottom;
+ break;
+ default:
+ gravityFlags = GravityFlags.CenterVertical;
+ break;
+ }
+
+ switch (Element.HorizontalOptions.Alignment)
+ {
+ case LayoutAlignment.Start:
+ gravityFlags |= GravityFlags.Left;
+ break;
+ case LayoutAlignment.End:
+ gravityFlags |= GravityFlags.Right;
+ break;
+ default:
+ gravityFlags |= GravityFlags.CenterHorizontal;
+ break;
+ }
+
+ Window.SetGravity(gravityFlags);
+ }
+
+ void OnDismissed(object sender, PopupDismissedEventArgs e)
+ {
+ Dismiss();
+ }
+
+ public void OnCancel(IDialogInterface dialog)
+ {
+ if (Element.IsLightDismissEnabled)
+ Element.LightDismiss();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (isDisposed)
+ return;
+
+ isDisposed = true;
+ if (disposing)
+ {
+ tracker?.Dispose();
+ tracker = null;
+
+ if (Element != null)
+ {
+ Element.PropertyChanged -= OnElementPropertyChanged;
+ Element = null;
+ }
+ }
+
+ base.Dispose(disposing);
+ }
+
+ SizeRequest IVisualElementRenderer.GetDesiredSize(int widthConstraint, int heightConstraint)
+ {
+ if (isDisposed || container == null)
+ return default(SizeRequest);
+
+ container.Measure(widthConstraint, heightConstraint);
+ return new SizeRequest(new Size(container.MeasuredWidth, container.MeasuredHeight), default(Size));
+ }
+
+ void IVisualElementRenderer.SetLabelFor(int? id)
+ {
+ if (defaultLabelFor == null)
+ defaultLabelFor = container.LabelFor;
+
+ container.LabelFor = (int)(id ?? defaultLabelFor);
+ }
+ }
+}
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/Popup/BasePopup.shared.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/Popup/BasePopup.shared.cs
new file mode 100644
index 000000000..da137d302
--- /dev/null
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/Popup/BasePopup.shared.cs
@@ -0,0 +1,175 @@
+using System;
+using Xamarin.CommunityToolkit.Helpers;
+using Xamarin.Forms;
+
+namespace Xamarin.CommunityToolkit.UI.Views
+{
+ ///
+ /// The popup control's base implementation.
+ ///
+ [ContentProperty(nameof(Content))]
+ public abstract class BasePopup : VisualElement
+ {
+ readonly WeakEventManager dismissWeakEventManager = new WeakEventManager();
+ readonly WeakEventManager openedWeakEventManager = new WeakEventManager();
+
+ ///
+ /// Instantiates a new instance of .
+ ///
+ protected BasePopup()
+ {
+ Color = Color.White;
+ VerticalOptions = LayoutOptions.CenterAndExpand;
+ HorizontalOptions = LayoutOptions.CenterAndExpand;
+ IsLightDismissEnabled = true;
+ }
+
+ public static readonly BindableProperty ContentProperty = BindableProperty.Create(nameof(Content), typeof(View), typeof(BasePopup), propertyChanged: OnContentChanged);
+
+ public static readonly BindableProperty ColorProperty = BindableProperty.Create(nameof(Color), typeof(Color), typeof(BasePopup), Color.Default);
+ public static readonly BindableProperty SizeProperty = BindableProperty.Create(nameof(Size), typeof(Size), typeof(BasePopup), default(Size));
+
+ public static readonly BindableProperty VerticalOptionsProperty = BindableProperty.Create(nameof(VerticalOptions), typeof(LayoutOptions), typeof(BasePopup), LayoutOptions.CenterAndExpand);
+ public static readonly BindableProperty HorizontalOptionsProperty = BindableProperty.Create(nameof(HorizontalOptions), typeof(LayoutOptions), typeof(BasePopup), LayoutOptions.CenterAndExpand);
+
+ ///
+ /// Gets or sets the content to render in the Popup.
+ ///
+ ///
+ /// The View can be or type: , or
+ ///
+ public virtual View Content
+ {
+ get => (View)GetValue(ContentProperty);
+ set => SetValue(ContentProperty, value);
+ }
+
+ ///
+ /// Gets or sets the of the Popup.
+ ///
+ ///
+ /// This color sets the native background color of the , which is
+ /// independent of any background color configured in the actual View.
+ ///
+ public Color Color
+ {
+ get => (Color)GetValue(ColorProperty);
+ set => SetValue(ColorProperty, value);
+ }
+
+ ///
+ /// Gets or sets the for positioning the vertically on the screen.
+ ///
+ public LayoutOptions VerticalOptions
+ {
+ get => (LayoutOptions)GetValue(VerticalOptionsProperty);
+ set => SetValue(VerticalOptionsProperty, value);
+ }
+
+ ///
+ /// Gets or sets the for positioning the horizontally on the screen.
+ ///
+ public LayoutOptions HorizontalOptions
+ {
+ get => (LayoutOptions)GetValue(HorizontalOptionsProperty);
+ set => SetValue(HorizontalOptionsProperty, value);
+ }
+
+ ///
+ /// Gets or sets the anchor.
+ ///
+ ///
+ /// The Anchor is where the Popup will render closest to. When an Anchor is configured
+ /// the popup will appear centered over that control or as close as possible.
+ ///
+ public View Anchor { get; set; }
+
+ ///
+ /// Gets or sets the of the Popup Display.
+ ///
+ ///
+ /// The Popup will always try to constrain the actual size of the
+ /// to the of the View unless a is specified.
+ /// If the contiains a
+ /// will be required. This will allow the View to have a concept of
+ /// that varies from the actual of the
+ ///
+ public Size Size
+ {
+ get => (Size)GetValue(SizeProperty);
+ set => SetValue(SizeProperty, value);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the popup can be light dismissed.
+ ///
+ ///
+ /// When true and the user taps outside of the popup it will dismiss.
+ ///
+ public bool IsLightDismissEnabled { get; set; }
+
+ ///
+ /// Dismissed event is invoked when the popup is closed.
+ ///
+ public event EventHandler Dismissed
+ {
+ add => dismissWeakEventManager.AddEventHandler(value);
+ remove => dismissWeakEventManager.RemoveEventHandler(value);
+ }
+
+ ///
+ /// Opened event is invoked when the popup is opened.
+ ///
+ public event EventHandler Opened
+ {
+ add => openedWeakEventManager.AddEventHandler(value);
+ remove => openedWeakEventManager.RemoveEventHandler(value);
+ }
+
+ ///
+ /// Invokes the event.
+ ///
+ ///
+ /// The results to add to the .
+ ///
+ protected void OnDismissed(object result) =>
+ dismissWeakEventManager.RaiseEvent(this, new PopupDismissedEventArgs(result), nameof(Dismissed));
+
+ ///
+ /// Invokes the event.
+ ///
+ internal virtual void OnOpened() =>
+ openedWeakEventManager.RaiseEvent(this, new PopupOpenedEventArgs(), nameof(Opened));
+
+ ///
+ /// Invoked when the popup is light dismissed. In other words when the
+ /// user taps outside of the popup and it closes.
+ ///
+ protected internal virtual void LightDismiss()
+ {
+ // Note 1/9/2021
+ // Left empty by design
+ //
+ // This method needs to be left empty as it is not
+ // required for a child class to have an implementation
+ // of LightDismiss. This means if a renderer invokes
+ // this method nothing will happen unless the child
+ // class has an implementation.
+ }
+
+ ///
+ protected override void OnBindingContextChanged()
+ {
+ base.OnBindingContextChanged();
+
+ if (Content != null)
+ SetInheritedBindingContext(Content, BindingContext);
+ }
+
+ static void OnContentChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is BasePopup popup)
+ popup.OnBindingContextChanged();
+ }
+ }
+}
diff --git a/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/Popup/Popup.shared.cs b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/Popup/Popup.shared.cs
new file mode 100644
index 000000000..a92c4955b
--- /dev/null
+++ b/src/CommunityToolkit/Xamarin.CommunityToolkit/Views/Popup/Popup.shared.cs
@@ -0,0 +1,10 @@
+namespace Xamarin.CommunityToolkit.UI.Views
+{
+ ///
+ /// Default popup implementation that uses a
+ /// generic result.
+ ///
+ public class Popup : Popup