Use file-scoped namespace

This commit is contained in:
Maksym Koshovyi
2022-02-19 20:27:07 +02:00
parent ea176051c2
commit 45cbe53703
18 changed files with 671 additions and 710 deletions

View File

@@ -5,3 +5,7 @@
indent_style = space
indent_size = 2
[*.cs]
# Namespace preferences
csharp_style_namespace_declarations = file_scoped:warning

View File

@@ -1,25 +1,24 @@
using Mopups.Pages;
namespace Mopups.Interfaces
namespace Mopups.Interfaces;
public interface IPopupNavigation
{
public interface IPopupNavigation
{
event EventHandler<PopupPage> Pushing;
event EventHandler<PopupPage> Pushing;
event EventHandler<PopupPage> Pushed;
event EventHandler<PopupPage> Pushed;
event EventHandler<PopupPage> Popping;
event EventHandler<PopupPage> Popping;
event EventHandler<PopupPage> Popped;
event EventHandler<PopupPage> Popped;
IReadOnlyList<PopupPage> PopupStack { get; }
IReadOnlyList<PopupPage> PopupStack { get; }
Task PushAsync(PopupPage page);
Task PushAsync(PopupPage page);
Task PopAsync();
Task PopAsync();
Task PopAllAsync();
Task PopAllAsync();
Task RemovePageAsync(PopupPage page);
}
Task RemovePageAsync(PopupPage page);
}

View File

@@ -1,11 +1,10 @@
using Mopups.Pages;
namespace Mopups.Interfaces
{
public interface IPopupPlatform
{
Task AddAsync(PopupPage page);
namespace Mopups.Interfaces;
Task RemoveAsync(PopupPage page);
}
public interface IPopupPlatform
{
Task AddAsync(PopupPage page);
Task RemoveAsync(PopupPage page);
}

View File

@@ -1,78 +1,77 @@
using AsyncAwaitBestPractices;
using Mopups.Services;
namespace Mopups.Pages
namespace Mopups.Pages;
public partial class PopupPage : ContentPage
{
public partial class PopupPage : ContentPage
public event EventHandler? BackgroundClicked;
public static readonly BindableProperty CloseWhenBackgroundIsClickedProperty = BindableProperty.Create(nameof(CloseWhenBackgroundIsClicked), typeof(bool), typeof(PopupPage), true);
public bool CloseWhenBackgroundIsClicked
{
get { return (bool)GetValue(CloseWhenBackgroundIsClickedProperty); }
set { SetValue(CloseWhenBackgroundIsClickedProperty, value); }
}
public event EventHandler? BackgroundClicked;
public static readonly BindableProperty BackgroundInputTransparentProperty = BindableProperty.Create(nameof(BackgroundInputTransparent), typeof(bool), typeof(PopupPage), false);
public static readonly BindableProperty CloseWhenBackgroundIsClickedProperty = BindableProperty.Create(nameof(CloseWhenBackgroundIsClicked), typeof(bool), typeof(PopupPage), true);
public bool BackgroundInputTransparent
{
get { return (bool)GetValue(BackgroundInputTransparentProperty); }
set { SetValue(BackgroundInputTransparentProperty, value); }
}
public bool CloseWhenBackgroundIsClicked
public static readonly BindableProperty HasKeyboardOffsetProperty = BindableProperty.Create(nameof(HasKeyboardOffset), typeof(bool), typeof(PopupPage), true);
public bool HasKeyboardOffset
{
get { return (bool)GetValue(HasKeyboardOffsetProperty); }
set { SetValue(HasKeyboardOffsetProperty, value); }
}
public static readonly BindableProperty KeyboardOffsetProperty = BindableProperty.Create(nameof(KeyboardOffset), typeof(double), typeof(PopupPage), 0d, BindingMode.OneWayToSource);
public double KeyboardOffset
{
get { return (double)GetValue(KeyboardOffsetProperty); }
private set { SetValue(KeyboardOffsetProperty, value); }
}
public PopupPage()
{
BackgroundColor = Color.FromArgb("#80000000");
}
protected override bool OnBackButtonPressed()
{
return false;
}
protected override void LayoutChildren(double x, double y, double width, double height)
{
height -= KeyboardOffset;
base.LayoutChildren(x, y, width, height);
}
protected virtual bool OnBackgroundClicked()
{
return CloseWhenBackgroundIsClicked;
}
internal void SendBackgroundClick()
{
BackgroundClicked?.Invoke(this, EventArgs.Empty);
if (OnBackgroundClicked())
{
get { return (bool)GetValue(CloseWhenBackgroundIsClickedProperty); }
set { SetValue(CloseWhenBackgroundIsClickedProperty, value); }
}
public static readonly BindableProperty BackgroundInputTransparentProperty = BindableProperty.Create(nameof(BackgroundInputTransparent), typeof(bool), typeof(PopupPage), false);
public bool BackgroundInputTransparent
{
get { return (bool)GetValue(BackgroundInputTransparentProperty); }
set { SetValue(BackgroundInputTransparentProperty, value); }
}
public static readonly BindableProperty HasKeyboardOffsetProperty = BindableProperty.Create(nameof(HasKeyboardOffset), typeof(bool), typeof(PopupPage), true);
public bool HasKeyboardOffset
{
get { return (bool)GetValue(HasKeyboardOffsetProperty); }
set { SetValue(HasKeyboardOffsetProperty, value); }
}
public static readonly BindableProperty KeyboardOffsetProperty = BindableProperty.Create(nameof(KeyboardOffset), typeof(double), typeof(PopupPage), 0d, BindingMode.OneWayToSource);
public double KeyboardOffset
{
get { return (double)GetValue(KeyboardOffsetProperty); }
private set { SetValue(KeyboardOffsetProperty, value); }
}
public PopupPage()
{
BackgroundColor = Color.FromArgb("#80000000");
}
protected override bool OnBackButtonPressed()
{
return false;
}
protected override void LayoutChildren(double x, double y, double width, double height)
{
height -= KeyboardOffset;
base.LayoutChildren(x, y, width, height);
}
protected virtual bool OnBackgroundClicked()
{
return CloseWhenBackgroundIsClicked;
}
internal void SendBackgroundClick()
{
BackgroundClicked?.Invoke(this, EventArgs.Empty);
if (OnBackgroundClicked())
{
MopupService.Instance.RemovePageAsync(this).SafeFireAndForget();
}
MopupService.Instance.RemovePageAsync(this).SafeFireAndForget();
}
}
}

View File

@@ -1,16 +1,15 @@
using Android.Views;
namespace Mopups.Droid.Gestures
namespace Mopups.Droid.Gestures;
internal class MopupGestureDetectorListener : GestureDetector.SimpleOnGestureListener
{
internal class MopupGestureDetectorListener : GestureDetector.SimpleOnGestureListener
public event EventHandler<MotionEvent>? Clicked;
public override bool OnSingleTapUp(MotionEvent? e)
{
public event EventHandler<MotionEvent>? Clicked;
if (e != null) Clicked?.Invoke(this, e);
public override bool OnSingleTapUp(MotionEvent? e)
{
if (e != null) Clicked?.Invoke(this, e);
return false;
}
return false;
}
}

View File

@@ -1,21 +1,18 @@
using Mopups.Pages;
using Mopups.Pages;
namespace Mopups.Droid.Extension;
namespace Mopups.Droid.Extension
internal static class PlatformExtension
{
internal static class PlatformExtension
public static IViewHandler GetOrCreateHandler(this VisualElement bindable)
{
public static IViewHandler GetOrCreateHandler(this VisualElement bindable)
try
{
try
{
return bindable.Handler ??= new PopupPageHandler();
}
catch (System.Exception ex)
{
throw;
}
return bindable.Handler ??= new PopupPageHandler();
}
catch (Exception)
{
throw;
}
}
}

View File

@@ -8,85 +8,84 @@ using Mopups.Interfaces;
using Mopups.Pages;
using Mopups.Services;
namespace Mopups.Droid.Implementation
namespace Mopups.Droid.Implementation;
public class AndroidMopups : IPopupPlatform
{
public class AndroidMopups : IPopupPlatform
private static FrameLayout? DecoreView => Platform.CurrentActivity.Window.DecorView as FrameLayout;
public static bool SendBackPressed(Action? backPressedHandler = null)
{
var popupNavigationInstance = MopupService.Instance;
private static FrameLayout? DecoreView => Platform.CurrentActivity.Window.DecorView as FrameLayout;
public static bool SendBackPressed(Action? backPressedHandler = null)
if (popupNavigationInstance.PopupStack.Count > 0)
{
var popupNavigationInstance = MopupService.Instance;
var lastPage = popupNavigationInstance.PopupStack[popupNavigationInstance.PopupStack.Count - 1];
if (popupNavigationInstance.PopupStack.Count > 0)
var isPreventClose = lastPage.SendBackButtonPressed();
if (!isPreventClose)
{
var lastPage = popupNavigationInstance.PopupStack[popupNavigationInstance.PopupStack.Count - 1];
var isPreventClose = lastPage.SendBackButtonPressed();
if (!isPreventClose)
{
popupNavigationInstance.PopAsync().SafeFireAndForget();
}
return true;
popupNavigationInstance.PopAsync().SafeFireAndForget();
}
backPressedHandler?.Invoke();
return false;
return true;
}
public Task AddAsync(PopupPage page)
{
try
{
var decoreView = DecoreView;
page.Parent = MauiApplication.Current.Application.Windows[0].Content as Element;
var AndroidNativeView = page.GetOrCreateHandler().NativeView as Android.Views.View;
decoreView?.AddView(AndroidNativeView);
return PostAsync(AndroidNativeView);
}
catch (Exception)
{
throw;
}
}
public Task RemoveAsync(PopupPage page)
{
var renderer = page.GetOrCreateHandler();
if (renderer != null)
{
DecoreView?.RemoveView(renderer.NativeView as Android.Views.View);
renderer.DisconnectHandler(); //?? no clue if works
page.Parent = null;
return PostAsync(DecoreView);
}
return Task.CompletedTask;
}
Task<bool> PostAsync(Android.Views.View nativeView)
{
if (nativeView == null)
{
return Task.FromResult(true);
}
var tcs = new TaskCompletionSource<bool>();
nativeView.Post(() => tcs.SetResult(true));
return tcs.Task;
}
backPressedHandler?.Invoke();
return false;
}
public Task AddAsync(PopupPage page)
{
try
{
var decoreView = DecoreView;
page.Parent = MauiApplication.Current.Application.Windows[0].Content as Element;
var AndroidNativeView = page.GetOrCreateHandler().NativeView as Android.Views.View;
decoreView?.AddView(AndroidNativeView);
return PostAsync(AndroidNativeView);
}
catch (Exception)
{
throw;
}
}
public Task RemoveAsync(PopupPage page)
{
var renderer = page.GetOrCreateHandler();
if (renderer != null)
{
DecoreView?.RemoveView(renderer.NativeView as Android.Views.View);
renderer.DisconnectHandler(); //?? no clue if works
page.Parent = null;
return PostAsync(DecoreView);
}
return Task.CompletedTask;
}
Task<bool> PostAsync(Android.Views.View nativeView)
{
if (nativeView == null)
{
return Task.FromResult(true);
}
var tcs = new TaskCompletionSource<bool>();
nativeView.Post(() => tcs.SetResult(true));
return tcs.Task;
}
}

View File

@@ -5,224 +5,221 @@ using Microsoft.Maui.Handlers;
using Microsoft.Maui.Platform;
using Mopups.Droid.Gestures;
namespace Mopups.Pages
namespace Mopups.Pages;
public class PopupPageHandler : ContentViewHandler
{
public class PopupPageHandler : ContentViewHandler
private readonly MopupGestureDetectorListener _gestureDetectorListener;
private readonly GestureDetector _gestureDetector;
private DateTime _downTime;
private Microsoft.Maui.Graphics.Point _downPosition;
private bool _disposed;
public PopupPageHandler()
{
private readonly MopupGestureDetectorListener _gestureDetectorListener;
private readonly GestureDetector _gestureDetector;
private DateTime _downTime;
private Microsoft.Maui.Graphics.Point _downPosition;
private bool _disposed;
public PopupPageHandler()
try
{
try
{
//--HACK--
this.SetMauiContext(new MauiContext(MauiApplication.Current.Services, MauiApplication.Current.ApplicationContext));
//
_gestureDetectorListener = new MopupGestureDetectorListener();
_gestureDetectorListener.Clicked += OnBackgroundClick;
_gestureDetector = new GestureDetector(MauiApplication.Current.ApplicationContext, _gestureDetectorListener);
ForceHandlerPauseWaitForVirtualView();
}
catch (Exception ex)
{
throw;
}
//--HACK--
void ForceHandlerPauseWaitForVirtualView()
this.SetMauiContext(new MauiContext(MauiApplication.Current.Services, MauiApplication.Current.ApplicationContext));
//
_gestureDetectorListener = new MopupGestureDetectorListener();
_gestureDetectorListener.Clicked += OnBackgroundClick;
_gestureDetector = new GestureDetector(MauiApplication.Current.ApplicationContext, _gestureDetectorListener);
ForceHandlerPauseWaitForVirtualView();
}
catch (Exception)
{
throw;
}
//--HACK--
void ForceHandlerPauseWaitForVirtualView()
{
Task.Run(async () =>
{
Task.Run(async () =>
while (this.VirtualView == null)
{
while (this.VirtualView == null)
{
await Task.Delay(100);
}
this.NativeView.LayoutChange += PopupPage_LayoutChange;
this.NativeView.Touch += NativeView_Touch;
this.NativeView.Touch += NativeView_Touch1;
});
}
await Task.Delay(100);
}
this.NativeView.LayoutChange += PopupPage_LayoutChange;
this.NativeView.Touch += NativeView_Touch;
this.NativeView.Touch += NativeView_Touch1;
});
}
}
private void NativeView_Touch1(object? sender, Android.Views.View.TouchEventArgs e)
private void NativeView_Touch1(object? sender, Android.Views.View.TouchEventArgs e)
{
OnTouchEvent(sender, e.Event);
}
private bool OnTouchEvent(object? sender, MotionEvent e)
{
if (_disposed)
{
OnTouchEvent(sender, e.Event);
}
private bool OnTouchEvent(object? sender, MotionEvent e)
{
if (_disposed)
{
return false;
}
var baseValue = (sender as Android.Views.View).OnTouchEvent(e);
_gestureDetector.OnTouchEvent(e);
if ((sender as PopupPage)?.BackgroundInputTransparent == true)
{
OnBackgroundClick(sender, e);
}
return false;
}
private void OnBackgroundClick(object? sender, MotionEvent e)
var baseValue = (sender as Android.Views.View).OnTouchEvent(e);
_gestureDetector.OnTouchEvent(e);
if ((sender as PopupPage)?.BackgroundInputTransparent == true)
{
var isInRegion = IsInRegion(e.RawX, e.RawY, (sender as Android.Views.View)!);
if (!isInRegion)
(sender as PopupPage).SendBackgroundClick();
OnBackgroundClick(sender, e);
}
// Fix for "CloseWhenBackgroundIsClicked not works on Android with Xamarin.Forms 2.4.0.280" #173
private bool IsInRegion(float x, float y, Android.Views.View v)
{
var mCoordBuffer = new int[2];
return false;
}
v.GetLocationOnScreen(mCoordBuffer);
return mCoordBuffer[0] + v.Width > x && // right edge
mCoordBuffer[1] + v.Height > y && // bottom edge
mCoordBuffer[0] < x && // left edge
mCoordBuffer[1] < y; // top edge
}
private void OnBackgroundClick(object? sender, MotionEvent e)
{
private void NativeView_Touch(object? sender, Android.Views.View.TouchEventArgs e)
var isInRegion = IsInRegion(e.RawX, e.RawY, (sender as Android.Views.View)!);
if (!isInRegion)
(sender as PopupPage).SendBackgroundClick();
}
// Fix for "CloseWhenBackgroundIsClicked not works on Android with Xamarin.Forms 2.4.0.280" #173
private bool IsInRegion(float x, float y, Android.Views.View v)
{
var mCoordBuffer = new int[2];
v.GetLocationOnScreen(mCoordBuffer);
return mCoordBuffer[0] + v.Width > x && // right edge
mCoordBuffer[1] + v.Height > y && // bottom edge
mCoordBuffer[0] < x && // left edge
mCoordBuffer[1] < y; // top edge
}
private void NativeView_Touch(object? sender, Android.Views.View.TouchEventArgs e)
{
try
{
try
DispatchTouchEvent(e.Event);
void DispatchTouchEvent(MotionEvent e)
{
DispatchTouchEvent(e.Event);
void DispatchTouchEvent(MotionEvent e)
if (e.Action == MotionEventActions.Down)
{
_downTime = DateTime.UtcNow;
_downPosition = new Point(e.RawX, e.RawY);
}
if (e.Action != MotionEventActions.Up)
{
return;
}
if (e.Action == MotionEventActions.Down)
if (_disposed)
return;
Android.Views.View? currentFocus1 = Platform.CurrentActivity.CurrentFocus;
if (currentFocus1 is Android.Widget.EditText)
{
Android.Views.View? currentFocus2 = Platform.CurrentActivity.CurrentFocus;
if (currentFocus1 == currentFocus2 && _downPosition.Distance(new(e.RawX, e.RawY)) <= Context.ToPixels(20.0) && !(DateTime.UtcNow - _downTime > TimeSpan.FromMilliseconds(200.0)))
{
_downTime = DateTime.UtcNow;
_downPosition = new Point(e.RawX, e.RawY);
}
if (e.Action != MotionEventActions.Up)
{
return;
}
if (_disposed)
return;
Android.Views.View? currentFocus1 = Platform.CurrentActivity.CurrentFocus;
if (currentFocus1 is Android.Widget.EditText)
{
Android.Views.View? currentFocus2 = Platform.CurrentActivity.CurrentFocus;
if (currentFocus1 == currentFocus2 && _downPosition.Distance(new(e.RawX, e.RawY)) <= Context.ToPixels(20.0) && !(DateTime.UtcNow - _downTime > TimeSpan.FromMilliseconds(200.0)))
int[] location = new int[2];
currentFocus1.GetLocationOnScreen(location);
float num1 = e.RawX + currentFocus1.Left - location[0];
float num2 = e.RawY + currentFocus1.Top - location[1];
if (!new Rectangle(currentFocus1.Left, currentFocus1.Top, currentFocus1.Width, currentFocus1.Height).Contains(num1, num2))
{
int[] location = new int[2];
currentFocus1.GetLocationOnScreen(location);
float num1 = e.RawX + currentFocus1.Left - location[0];
float num2 = e.RawY + currentFocus1.Top - location[1];
if (!new Rectangle(currentFocus1.Left, currentFocus1.Top, currentFocus1.Width, currentFocus1.Height).Contains(num1, num2))
{
Context.HideKeyboard(currentFocus1);
currentFocus1.ClearFocus();
}
Context.HideKeyboard(currentFocus1);
currentFocus1.ClearFocus();
}
}
}
}
catch (Exception ex)
{
throw;
}
}
private void PopupPage_LayoutChange(object? sender, Android.Views.View.LayoutChangeEventArgs e)
catch (Exception)
{
try
{
var activity = Microsoft.Maui.Essentials.Platform.CurrentActivity;
Microsoft.Maui.Thickness systemPadding;
var keyboardOffset = 0d;
var decoreView = activity.Window.DecorView;
var decoreHeight = decoreView.Height;
var decoreWidth = decoreView.Width;
using var visibleRect = new Android.Graphics.Rect();
decoreView.GetWindowVisibleDisplayFrame(visibleRect);
using var screenSize = new Android.Graphics.Point();
if (Build.VERSION.SdkInt >= BuildVersionCodes.M)
{
var windowInsets = activity.WindowManager.DefaultDisplay.Cutout;
var bottomPadding = windowInsets.SafeInsetBottom;
if (screenSize.Y - visibleRect.Bottom > bottomPadding)
{
keyboardOffset = Context.FromPixels(screenSize.Y - visibleRect.Bottom);
}
systemPadding = new Microsoft.Maui.Thickness
{
Left = Context.FromPixels(windowInsets.SafeInsetLeft),
Top = Context.FromPixels(windowInsets.SafeInsetTop),
Right = Context.FromPixels(windowInsets.SafeInsetRight),
Bottom = Context.FromPixels(bottomPadding)
};
}
else
{
var keyboardHeight = 0d;
if (visibleRect.Bottom < screenSize.Y)
{
keyboardHeight = screenSize.Y - visibleRect.Bottom;
keyboardOffset = Context.FromPixels(decoreHeight - visibleRect.Bottom);
}
systemPadding = new Microsoft.Maui.Thickness
{
Left = Context.FromPixels(visibleRect.Left),
Top = Context.FromPixels(visibleRect.Top),
Right = Context.FromPixels(decoreWidth - visibleRect.Right),
Bottom = Context.FromPixels(decoreHeight - visibleRect.Bottom - keyboardHeight)
};
}
//CurrentElement.SetValue(PopupPage.SystemPaddingProperty, systemPadding);
//CurrentElement.SetValue(PopupPage.KeyboardOffsetProperty, keyboardOffset);
this.NativeView.Layout((int)Context.FromPixels(e.Left), (int)Context.FromPixels(e.Top), (int)Context.FromPixels(e.Right), (int)Context.FromPixels(e.Bottom));
this.NativeView.ForceLayout();
//base.OnLayout(changed, l, t, r, b);
}
catch (Exception ex)
{
throw;
}
throw;
}
}
private void PopupPage_LayoutChange(object? sender, Android.Views.View.LayoutChangeEventArgs e)
{
try
{
var activity = Microsoft.Maui.Essentials.Platform.CurrentActivity;
Microsoft.Maui.Thickness systemPadding;
var keyboardOffset = 0d;
var decoreView = activity.Window.DecorView;
var decoreHeight = decoreView.Height;
var decoreWidth = decoreView.Width;
using var visibleRect = new Android.Graphics.Rect();
decoreView.GetWindowVisibleDisplayFrame(visibleRect);
using var screenSize = new Android.Graphics.Point();
if (Build.VERSION.SdkInt >= BuildVersionCodes.M)
{
var windowInsets = activity.WindowManager.DefaultDisplay.Cutout;
var bottomPadding = windowInsets.SafeInsetBottom;
if (screenSize.Y - visibleRect.Bottom > bottomPadding)
{
keyboardOffset = Context.FromPixels(screenSize.Y - visibleRect.Bottom);
}
systemPadding = new Microsoft.Maui.Thickness
{
Left = Context.FromPixels(windowInsets.SafeInsetLeft),
Top = Context.FromPixels(windowInsets.SafeInsetTop),
Right = Context.FromPixels(windowInsets.SafeInsetRight),
Bottom = Context.FromPixels(bottomPadding)
};
}
else
{
var keyboardHeight = 0d;
if (visibleRect.Bottom < screenSize.Y)
{
keyboardHeight = screenSize.Y - visibleRect.Bottom;
keyboardOffset = Context.FromPixels(decoreHeight - visibleRect.Bottom);
}
systemPadding = new Microsoft.Maui.Thickness
{
Left = Context.FromPixels(visibleRect.Left),
Top = Context.FromPixels(visibleRect.Top),
Right = Context.FromPixels(decoreWidth - visibleRect.Right),
Bottom = Context.FromPixels(decoreHeight - visibleRect.Bottom - keyboardHeight)
};
}
//CurrentElement.SetValue(PopupPage.SystemPaddingProperty, systemPadding);
//CurrentElement.SetValue(PopupPage.KeyboardOffsetProperty, keyboardOffset);
this.NativeView.Layout((int)Context.FromPixels(e.Left), (int)Context.FromPixels(e.Top), (int)Context.FromPixels(e.Right), (int)Context.FromPixels(e.Bottom));
this.NativeView.ForceLayout();
//base.OnLayout(changed, l, t, r, b);
}
catch (Exception)
{
throw;
}
}
}

View File

@@ -1,51 +1,50 @@
using Mopups.Interfaces;
namespace Mopups.Services
namespace Mopups.Services;
public static class MopupService
{
public static class MopupService
static IPopupNavigation? _customNavigation;
static readonly Lazy<IPopupNavigation> implementation = new(() => CreatePopupNavigation(), System.Threading.LazyThreadSafetyMode.PublicationOnly);
/// <summary>
/// Gets if the plugin is supported on the current platform.
/// </summary>
public static bool IsSupported => implementation.Value != null;
/// <summary>
/// Current plugin implementation to use
/// </summary>
public static IPopupNavigation Instance
{
static IPopupNavigation? _customNavigation;
static readonly Lazy<IPopupNavigation> implementation = new(() => CreatePopupNavigation(), System.Threading.LazyThreadSafetyMode.PublicationOnly);
/// <summary>
/// Gets if the plugin is supported on the current platform.
/// </summary>
public static bool IsSupported => implementation.Value != null;
/// <summary>
/// Current plugin implementation to use
/// </summary>
public static IPopupNavigation Instance
get
{
get
IPopupNavigation lazyEvalPopupNavigation = _customNavigation ?? implementation.Value;
if (lazyEvalPopupNavigation == null)
{
IPopupNavigation lazyEvalPopupNavigation = _customNavigation ?? implementation.Value;
if (lazyEvalPopupNavigation == null)
{
throw NotImplementedInReferenceAssembly();
}
return lazyEvalPopupNavigation;
throw NotImplementedInReferenceAssembly();
}
return lazyEvalPopupNavigation;
}
public static void SetInstance(IPopupNavigation instance)
{
_customNavigation = instance;
}
public static void RestoreDefaultInstance()
{
_customNavigation = null;
}
static IPopupNavigation CreatePopupNavigation()
{
return new PopupNavigation();
}
internal static Exception NotImplementedInReferenceAssembly() =>
new NotImplementedException("This functionality is not implemented in the portable version of this assembly. You should reference the NuGet package from your main application project in order to reference the platform-specific implementation.");
}
public static void SetInstance(IPopupNavigation instance)
{
_customNavigation = instance;
}
public static void RestoreDefaultInstance()
{
_customNavigation = null;
}
static IPopupNavigation CreatePopupNavigation()
{
return new PopupNavigation();
}
internal static Exception NotImplementedInReferenceAssembly() =>
new NotImplementedException("This functionality is not implemented in the portable version of this assembly. You should reference the NuGet package from your main application project in order to reference the platform-specific implementation.");
}

View File

@@ -1,116 +1,112 @@
using AsyncAwaitBestPractices;
using AsyncAwaitBestPractices;
using Mopups.Interfaces;
using Mopups.Pages;
namespace Mopups.Services
namespace Mopups.Services;
public class PopupNavigation : IPopupNavigation
{
public class PopupNavigation : IPopupNavigation
private readonly object _locker = new();
public IReadOnlyList<PopupPage> PopupStack => _popupStack;
private readonly List<PopupPage> _popupStack = new();
public event EventHandler<PopupPage> Pushing;
public event EventHandler<PopupPage> Pushed;
public event EventHandler<PopupPage> Popping;
public event EventHandler<PopupPage> Popped;
private static readonly Lazy<IPopupPlatform> lazyImplementation = new(() => GeneratePopupPlatform(), System.Threading.LazyThreadSafetyMode.PublicationOnly);
private readonly IPopupPlatform PopupPlatform = lazyImplementation.Value;
private static IPopupPlatform GeneratePopupPlatform()
{
private readonly object _locker = new();
public IReadOnlyList<PopupPage> PopupStack => _popupStack;
private readonly List<PopupPage> _popupStack = new();
public event EventHandler<PopupPage> Pushing;
public event EventHandler<PopupPage> Pushed;
public event EventHandler<PopupPage> Popping;
public event EventHandler<PopupPage> Popped;
return PullPlatformImplementation();
private static readonly Lazy<IPopupPlatform> lazyImplementation = new(() => GeneratePopupPlatform(), System.Threading.LazyThreadSafetyMode.PublicationOnly);
private readonly IPopupPlatform PopupPlatform = lazyImplementation.Value;
private static IPopupPlatform GeneratePopupPlatform()
static IPopupPlatform PullPlatformImplementation()
{
return PullPlatformImplementation();
static IPopupPlatform PullPlatformImplementation()
{
#if ANDROID
return new Mopups.Droid.Implementation.AndroidMopups();
return new Mopups.Droid.Implementation.AndroidMopups();
#endif
throw new PlatformNotSupportedException();
}
throw new PlatformNotSupportedException();
}
}
private void OnInitialized(object? sender, EventArgs e)
private void OnInitialized(object? sender, EventArgs e)
{
if (_popupStack.Count > 0)
{
if (_popupStack.Count > 0)
PopAllAsync().SafeFireAndForget();
}
}
public Task PushAsync(PopupPage page)
{
Pushing?.Invoke(this, page);
_popupStack.Add(page);
return MainThread.IsMainThread
? PushPage()
: MainThread.InvokeOnMainThreadAsync(PushPage);
async Task PushPage()
{
await PopupPlatform.AddAsync(page);
Pushed?.Invoke(this, page);
};
}
public async Task PopAllAsync()
{
while (MopupService.Instance.PopupStack.Count > 0)
{
await PopAsync();
}
}
public Task PopAsync()
{
return _popupStack.Count <= 0
? throw new InvalidOperationException("PopupStack is empty")
: RemovePageAsync(PopupStack[PopupStack.Count - 1]);
}
public Task RemovePageAsync(PopupPage page)
{
if (page == null)
throw new InvalidOperationException("Page can not be null");
if (!_popupStack.Contains(page))
throw new InvalidOperationException("The page has not been pushed yet or has been removed already");
return (MainThread.IsMainThread
? RemovePage()
: MainThread.InvokeOnMainThreadAsync(RemovePage));
async Task RemovePage()
{
lock (_locker)
{
PopAllAsync().SafeFireAndForget();
}
}
public Task PushAsync(PopupPage page)
{
Pushing?.Invoke(this, page);
_popupStack.Add(page);
return MainThread.IsMainThread
? PushPage()
: MainThread.InvokeOnMainThreadAsync(PushPage);
async Task PushPage()
{
await PopupPlatform.AddAsync(page);
Pushed?.Invoke(this, page);
};
}
public async Task PopAllAsync()
{
while (MopupService.Instance.PopupStack.Count > 0)
{
await PopAsync();
}
}
public Task PopAsync()
{
return _popupStack.Count <= 0
? throw new InvalidOperationException("PopupStack is empty")
: RemovePageAsync(PopupStack[PopupStack.Count - 1]);
}
public Task RemovePageAsync(PopupPage page)
{
if (page == null)
throw new InvalidOperationException("Page can not be null");
if (!_popupStack.Contains(page))
throw new InvalidOperationException("The page has not been pushed yet or has been removed already");
return (MainThread.IsMainThread
? RemovePage()
: MainThread.InvokeOnMainThreadAsync(RemovePage));
async Task RemovePage()
{
lock (_locker)
if (!_popupStack.Contains(page))
{
if (!_popupStack.Contains(page))
{
return;
}
return;
}
Popping?.Invoke(this, page);
await PopupPlatform.RemoveAsync(page);
_popupStack.Remove(page);
Popped?.Invoke(this, page);
}
Popping?.Invoke(this, page);
await PopupPlatform.RemoveAsync(page);
_popupStack.Remove(page);
Popped?.Invoke(this, page);
}
}
}

View File

@@ -1,18 +1,13 @@
//using Demo.Pages;
//using Demo.Pages;
using SampleMaui.CSharpMarkup;
using Application = Microsoft.Maui.Controls.Application;
[assembly: XamlCompilation(XamlCompilationOptions.Skip)]
namespace SampleMaui
namespace SampleMaui;
public partial class App : Application
{
public partial class App : Application
public App()
{
public App()
{
MainPage = new MainPage();
}
MainPage = new MainPage();
}
}

View File

@@ -1,110 +1,100 @@
using Mopups.Pages;
using Mopups.Pages;
using Mopups.Services;
using ScrollView = Microsoft.Maui.Controls.ScrollView;
namespace SampleMaui.CSharpMarkup;
namespace SampleMaui.CSharpMarkup
public partial class LoginPage : PopupPage
{
public partial class LoginPage : PopupPage
public Frame FrameContainer { get; set; }
public Image DotNetBotImage { get; set; }
public Entry UsernameEntry { get; set; }
public Entry PasswordEntry { get; set; }
public Button LoginButton { get; set; }
protected void BuildContent()
{
public Frame FrameContainer { get; set; }
public Image DotNetBotImage { get; set; }
public Entry UsernameEntry { get; set; }
public Entry PasswordEntry { get; set; }
public Microsoft.Maui.Controls.Button LoginButton { get; set; }
protected void BuildContent()
try
{
try
this.Content = new ScrollView
{
this.Content = new ScrollView
{
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center,
BackgroundColor = Color.FromRgb(200.00, 0.00, 0.00),
Content = GenerateLoginView()
};
}
catch (Exception)
{
throw;
}
}
private Frame GenerateLoginView()
{
FrameContainer = new Frame
{
Margin = new Microsoft.Maui.Thickness(1),
Padding = new Microsoft.Maui.Thickness(0),
BackgroundColor = Microsoft.Maui.Graphics.Colors.Gray,
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center,
Content = GenerateFrameContainerContent()
BackgroundColor = Color.FromRgb(200.00, 0.00, 0.00),
Content = GenerateLoginView()
};
return FrameContainer;
}
private StackLayout GenerateFrameContainerContent()
catch (Exception)
{
var frameContainerContent = new StackLayout
{
Margin = new Microsoft.Maui.Thickness(1),
Padding = new Microsoft.Maui.Thickness(1, 1),
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
};
/*
DotNetBotImage = new Image
{
Margin = new Microsoft.Maui.Thickness(1),
BackgroundColor = Microsoft.Maui.Graphics.Colors.White,
Scale = 10,
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center,
Source = ImageSource.FromFile("fluent_balloon.svg")
};
*/
UsernameEntry = new Entry
{
HorizontalOptions = LayoutOptions.Center,
Placeholder = "Username",
PlaceholderColor = Microsoft.Maui.Graphics.Color.FromHex("#FF9CDAF1"),
TextColor = Microsoft.Maui.Graphics.Color.FromHex("#FF7DBBE6")
};
PasswordEntry = new Entry
{
HorizontalOptions = LayoutOptions.Center,
IsPassword = true,
Placeholder = "Password",
PlaceholderColor = Microsoft.Maui.Graphics.Color.FromHex("#FF9CDAF1"),
TextColor = Microsoft.Maui.Graphics.Color.FromHex("#FF7DBBE6")
};
LoginButton = new Button
{
Command = new Command(() => MopupService.Instance.PopAllAsync())
}
;
//frameContainerContent.Add(DotNetBotImage);
frameContainerContent.Add(UsernameEntry);
frameContainerContent.Add(PasswordEntry);
frameContainerContent.Add(LoginButton);
return frameContainerContent;
throw;
}
}
private Frame GenerateLoginView()
{
FrameContainer = new Frame
{
Margin = new Microsoft.Maui.Thickness(1),
Padding = new Microsoft.Maui.Thickness(0),
BackgroundColor = Microsoft.Maui.Graphics.Colors.Gray,
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center,
Content = GenerateFrameContainerContent()
};
return FrameContainer;
}
private StackLayout GenerateFrameContainerContent()
{
var frameContainerContent = new StackLayout
{
Margin = new Microsoft.Maui.Thickness(1),
Padding = new Microsoft.Maui.Thickness(1, 1),
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
};
/*
DotNetBotImage = new Image
{
Margin = new Microsoft.Maui.Thickness(1),
BackgroundColor = Microsoft.Maui.Graphics.Colors.White,
Scale = 10,
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center,
Source = ImageSource.FromFile("fluent_balloon.svg")
};
*/
UsernameEntry = new Entry
{
HorizontalOptions = LayoutOptions.Center,
Placeholder = "Username",
PlaceholderColor = Color.FromHex("#FF9CDAF1"),
TextColor = Color.FromHex("#FF7DBBE6")
};
PasswordEntry = new Entry
{
HorizontalOptions = LayoutOptions.Center,
IsPassword = true,
Placeholder = "Password",
PlaceholderColor = Color.FromHex("#FF9CDAF1"),
TextColor = Color.FromHex("#FF7DBBE6")
};
LoginButton = new Button
{
Command = new Command(() => MopupService.Instance.PopAllAsync())
};
//frameContainerContent.Add(DotNetBotImage);
frameContainerContent.Add(UsernameEntry);
frameContainerContent.Add(PasswordEntry);
frameContainerContent.Add(LoginButton);
return frameContainerContent;
}
}

View File

@@ -1,19 +1,16 @@
using Mopups.Pages;
using Mopups.Pages;
namespace SampleMaui.CSharpMarkup
namespace SampleMaui.CSharpMarkup;
public partial class LoginPage : PopupPage
{
public partial class LoginPage : PopupPage
public LoginPage()
{
public LoginPage()
{
BuildContent();
BuildContent();
}
}
protected override bool OnBackgroundClicked()
{
return base.OnBackgroundClicked();
}
protected override bool OnBackgroundClicked()
{
return base.OnBackgroundClicked();
}
}

View File

@@ -1,65 +1,60 @@
using AsyncAwaitBestPractices.MVVM;
using AsyncAwaitBestPractices.MVVM;
using Mopups.Pages;
using Mopups.Services;
using Button = Microsoft.Maui.Controls.Button;
using ScrollView = Microsoft.Maui.Controls.ScrollView;
namespace SampleMaui.CSharpMarkup
namespace SampleMaui.CSharpMarkup;
public partial class MainPage : ContentPage
{
public partial class MainPage : ContentPage
protected void BuildContent()
{
protected void BuildContent()
BackgroundColor = Color.FromRgb(255, 255, 255);
Title = "Popup Demo";
Content = new ScrollView
{
BackgroundColor = Color.FromRgb(255, 255, 255);
Title = "Popup Demo";
Content = new ScrollView
{
VerticalOptions = LayoutOptions.FillAndExpand,
Content = GenerateMainPageStackLayout()
};
}
VerticalOptions = LayoutOptions.FillAndExpand,
Content = GenerateMainPageStackLayout()
};
}
private StackLayout GenerateMainPageStackLayout()
private StackLayout GenerateMainPageStackLayout()
{
var mainStackLayout = new StackLayout
{
var mainStackLayout = new StackLayout
{
Spacing = 20,
Margin = new Microsoft.Maui.Thickness(10, 15)
};
mainStackLayout.Add(GeneratePopupButton("Open Popup", GenerateSimpleCommandForPopup<LoginPage>()));
return mainStackLayout;
}
Spacing = 20,
Margin = new Microsoft.Maui.Thickness(10, 15)
};
mainStackLayout.Add(GeneratePopupButton("Open Popup", GenerateSimpleCommandForPopup<LoginPage>()));
return mainStackLayout;
}
private static Button GeneratePopupButton(string buttonText, AsyncCommand buttonCommand)
private static Button GeneratePopupButton(string buttonText, AsyncCommand buttonCommand)
{
return new Button
{
return new Button
{
Text = buttonText,
BackgroundColor = Color.FromHex("#FF7DBBE6"),
TextColor = Color.FromRgb(255, 255, 255),
Command = buttonCommand,
};
}
Text = buttonText,
BackgroundColor = Color.FromHex("#FF7DBBE6"),
TextColor = Color.FromRgb(255, 255, 255),
Command = buttonCommand,
};
}
private static AsyncCommand GenerateSimpleCommandForPopup<TPopupPage>() where TPopupPage : PopupPage, new()
private static AsyncCommand GenerateSimpleCommandForPopup<TPopupPage>() where TPopupPage : PopupPage, new()
{
return new AsyncCommand(async () =>
{
return new AsyncCommand(async () =>
try
{
try
{
var page = new TPopupPage();
await MopupService.Instance.PushAsync(page);
}
catch (Exception)
{
throw;
}
});
}
var page = new TPopupPage();
await MopupService.Instance.PushAsync(page);
}
catch (Exception)
{
throw;
}
});
}
}

View File

@@ -1,23 +1,22 @@
using Mopups.Services;
using System.Diagnostics;
namespace SampleMaui.CSharpMarkup
namespace SampleMaui.CSharpMarkup;
public partial class MainPage : ContentPage
{
public partial class MainPage : ContentPage
public MainPage()
{
public MainPage()
{
BuildContent();
BuildContent();
MopupService.Instance.Pushing += (sender, e) => Debug.WriteLine($"[Popup] Pushing: {e.GetType().Name}");
MopupService.Instance.Pushed += (sender, e) => Debug.WriteLine($"[Popup] Pushed: {e.GetType().Name}");
MopupService.Instance.Popping += (sender, e) => Debug.WriteLine($"[Popup] Popping: {e.GetType().Name}");
MopupService.Instance.Popped += (sender, e) => Debug.WriteLine($"[Popup] Popped: {e.GetType().Name}");
}
MopupService.Instance.Pushing += (sender, e) => Debug.WriteLine($"[Popup] Pushing: {e.GetType().Name}");
MopupService.Instance.Pushed += (sender, e) => Debug.WriteLine($"[Popup] Pushed: {e.GetType().Name}");
MopupService.Instance.Popping += (sender, e) => Debug.WriteLine($"[Popup] Popping: {e.GetType().Name}");
MopupService.Instance.Popped += (sender, e) => Debug.WriteLine($"[Popup] Popped: {e.GetType().Name}");
}
protected override void OnAppearing()
{
protected override void OnAppearing()
{
}
}
}

View File

@@ -1,22 +1,21 @@
using Mopups.Hosting;
namespace SampleMaui
{
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
})
.ConfigureMopups();
namespace SampleMaui;
//Work out how to register this as a singleton
return builder.Build();
}
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
})
.ConfigureMopups();
//Work out how to register this as a singleton
return builder.Build();
}
}

View File

@@ -1,10 +1,9 @@
using Android.App;
using Android.Content.PM;
namespace SampleMaui
namespace SampleMaui;
[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize)]
public class MainActivity : MauiAppCompatActivity
{
[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize)]
public class MainActivity : MauiAppCompatActivity
{
}
}

View File

@@ -1,20 +1,19 @@
using Android.App;
using Android.Runtime;
namespace SampleMaui
{
[Application]
public class MainApplication : MauiApplication
{
public MainApplication(IntPtr handle, JniHandleOwnership ownership)
: base(handle, ownership)
{
Microsoft.Maui.Essentials.Platform.Init(Current);
}
namespace SampleMaui;
protected override MauiApp CreateMauiApp()
{
return MauiProgram.CreateMauiApp();
}
[Application]
public class MainApplication : MauiApplication
{
public MainApplication(IntPtr handle, JniHandleOwnership ownership)
: base(handle, ownership)
{
Microsoft.Maui.Essentials.Platform.Init(Current);
}
protected override MauiApp CreateMauiApp()
{
return MauiProgram.CreateMauiApp();
}
}