├── icon.png
├── Sample
├── Resources
│ ├── Images
│ │ └── dotnet_bot.png
│ ├── Fonts
│ │ ├── OpenSans-Regular.ttf
│ │ └── OpenSans-Semibold.ttf
│ ├── AppIcon
│ │ ├── appicon.svg
│ │ └── appiconfg.svg
│ ├── Raw
│ │ └── AboutAssets.txt
│ ├── Splash
│ │ └── splash.svg
│ └── Styles
│ │ ├── Colors.xaml
│ │ └── Styles.xaml
├── PopupTestPage.xaml.cs
├── Properties
│ └── launchSettings.json
├── App.xaml.cs
├── AppShell.xaml.cs
├── Platforms
│ ├── Android
│ │ ├── Resources
│ │ │ └── values
│ │ │ │ └── colors.xml
│ │ ├── AndroidManifest.xml
│ │ ├── MainApplication.cs
│ │ └── MainActivity.cs
│ ├── iOS
│ │ ├── AppDelegate.cs
│ │ ├── Program.cs
│ │ └── Info.plist
│ ├── MacCatalyst
│ │ ├── AppDelegate.cs
│ │ ├── Program.cs
│ │ ├── Entitlements.plist
│ │ └── Info.plist
│ └── Windows
│ │ ├── App.xaml
│ │ ├── app.manifest
│ │ ├── App.xaml.cs
│ │ └── Package.appxmanifest
├── AppShell.xaml
├── MauiProgram.cs
├── App.xaml
├── MainPage.xaml.cs
├── PopupTestPage.xaml
├── MainPage.xaml
└── Sample.csproj
├── MPowerKit.Popups
├── Animations
│ ├── MoveAnimationOptions.cs
│ ├── FadeAnimation.cs
│ ├── Base
│ │ └── BaseAnimation.cs
│ ├── FadeBackgroundAnimation.cs
│ ├── MoveAnimation.cs
│ └── ScaleAnimation.cs
├── DisposableAction.cs
├── PopupService.net.cs
├── Interfaces
│ ├── IPopupAnimation.cs
│ └── IPopupService.cs
├── Platforms
│ ├── Android
│ │ ├── AndroidExtensions.cs
│ │ ├── KeyboardListener.cs
│ │ ├── FragmentLifecycleCallback.cs
│ │ ├── ParentLayout.cs
│ │ └── PopupService.cs
│ └── Windows
│ │ └── PopupService.cs
├── DisposableActionAttached.cs
├── BuilderExtensions.cs
├── MPowerKit.Popups.csproj
├── PopupService.cs
├── MaciOS
│ └── PopupService.cs
└── PopupPage.cs
├── LICENSE
├── MPowerKit.Popups.sln
├── readme.md
└── .gitignore
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MPowerKit/Popups/HEAD/icon.png
--------------------------------------------------------------------------------
/Sample/Resources/Images/dotnet_bot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MPowerKit/Popups/HEAD/Sample/Resources/Images/dotnet_bot.png
--------------------------------------------------------------------------------
/Sample/Resources/Fonts/OpenSans-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MPowerKit/Popups/HEAD/Sample/Resources/Fonts/OpenSans-Regular.ttf
--------------------------------------------------------------------------------
/Sample/Resources/Fonts/OpenSans-Semibold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MPowerKit/Popups/HEAD/Sample/Resources/Fonts/OpenSans-Semibold.ttf
--------------------------------------------------------------------------------
/Sample/PopupTestPage.xaml.cs:
--------------------------------------------------------------------------------
1 | namespace Sample;
2 |
3 | public partial class PopupTestPage
4 | {
5 | public PopupTestPage()
6 | {
7 | InitializeComponent();
8 | }
9 | }
--------------------------------------------------------------------------------
/Sample/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "Windows Machine": {
4 | "commandName": "MsixPackage",
5 | "nativeDebugging": false
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/Sample/App.xaml.cs:
--------------------------------------------------------------------------------
1 | namespace Sample;
2 |
3 | public partial class App : Application
4 | {
5 | public App()
6 | {
7 | InitializeComponent();
8 |
9 | MainPage = new MainPage();
10 | }
11 | }
--------------------------------------------------------------------------------
/Sample/AppShell.xaml.cs:
--------------------------------------------------------------------------------
1 | namespace Sample
2 | {
3 | public partial class AppShell : Shell
4 | {
5 | public AppShell()
6 | {
7 | InitializeComponent();
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/MPowerKit.Popups/Animations/MoveAnimationOptions.cs:
--------------------------------------------------------------------------------
1 | namespace MPowerKit.Popups.Animations;
2 |
3 | [Flags]
4 | public enum MoveAnimationOptions
5 | {
6 | Center = 1,
7 | Left = 2,
8 | Right = 4,
9 | Top = 8,
10 | Bottom = 16
11 | }
--------------------------------------------------------------------------------
/Sample/Resources/AppIcon/appicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Sample/Platforms/Android/Resources/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #512BD4
4 | #2B0B98
5 | #2B0B98
6 |
--------------------------------------------------------------------------------
/Sample/Platforms/iOS/AppDelegate.cs:
--------------------------------------------------------------------------------
1 | using Foundation;
2 |
3 | namespace Sample
4 | {
5 | [Register("AppDelegate")]
6 | public class AppDelegate : MauiUIApplicationDelegate
7 | {
8 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/Sample/Platforms/MacCatalyst/AppDelegate.cs:
--------------------------------------------------------------------------------
1 | using Foundation;
2 |
3 | namespace Sample
4 | {
5 | [Register("AppDelegate")]
6 | public class AppDelegate : MauiUIApplicationDelegate
7 | {
8 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/Sample/Platforms/Windows/App.xaml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/MPowerKit.Popups/DisposableAction.cs:
--------------------------------------------------------------------------------
1 | namespace MPowerKit.Popups;
2 |
3 | public class DisposableAction : IDisposable
4 | {
5 | private readonly Action _action;
6 |
7 | public DisposableAction(Action action)
8 | {
9 | _action = action ?? throw new ArgumentNullException(nameof(action));
10 | }
11 |
12 | public void Dispose()
13 | {
14 | _action();
15 | }
16 | }
--------------------------------------------------------------------------------
/MPowerKit.Popups/PopupService.net.cs:
--------------------------------------------------------------------------------
1 | namespace MPowerKit.Popups;
2 |
3 | public partial class PopupService
4 | {
5 | protected virtual partial void AttachToWindow(PopupPage page, IViewHandler pageHandler, Window parentWindow)
6 | {
7 |
8 | }
9 |
10 | protected virtual partial void DetachFromWindow(PopupPage page, IViewHandler pageHandler, Window parentWindow)
11 | {
12 |
13 | }
14 | }
--------------------------------------------------------------------------------
/MPowerKit.Popups/Interfaces/IPopupAnimation.cs:
--------------------------------------------------------------------------------
1 | namespace MPowerKit.Popups.Interfaces;
2 |
3 | public interface IPopupAnimation
4 | {
5 | public TimeSpan DurationIn { get; set; }
6 | public TimeSpan DurationOut { get; set; }
7 | void Preparing(View? content, PopupPage page);
8 | void Disposing(View? content, PopupPage page);
9 | Task Appearing(View? content, PopupPage page);
10 | Task Disappearing(View? content, PopupPage page);
11 | }
--------------------------------------------------------------------------------
/Sample/Platforms/Android/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Sample/Platforms/Android/MainApplication.cs:
--------------------------------------------------------------------------------
1 | using Android.App;
2 | using Android.Runtime;
3 |
4 | namespace Sample
5 | {
6 | [Application]
7 | public class MainApplication : MauiApplication
8 | {
9 | public MainApplication(IntPtr handle, JniHandleOwnership ownership)
10 | : base(handle, ownership)
11 | {
12 | }
13 |
14 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/MPowerKit.Popups/Interfaces/IPopupService.cs:
--------------------------------------------------------------------------------
1 | namespace MPowerKit.Popups.Interfaces;
2 |
3 | public interface IPopupService
4 | {
5 | IReadOnlyList PopupStack { get; }
6 |
7 | ValueTask ShowPopupAsync(PopupPage page, bool animated = true);
8 | ValueTask ShowPopupAsync(PopupPage page, Window? attachToWindow, bool animated = true);
9 | ValueTask HidePopupAsync(bool animated = true);
10 | ValueTask HidePopupAsync(PopupPage page, bool animated = true);
11 | }
--------------------------------------------------------------------------------
/Sample/Platforms/Android/MainActivity.cs:
--------------------------------------------------------------------------------
1 | using Android.App;
2 | using Android.Content.PM;
3 | using Android.OS;
4 |
5 | namespace Sample
6 | {
7 | [Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
8 | public class MainActivity : MauiAppCompatActivity
9 | {
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Sample/Platforms/iOS/Program.cs:
--------------------------------------------------------------------------------
1 | using ObjCRuntime;
2 |
3 | using UIKit;
4 |
5 | namespace Sample
6 | {
7 | public class Program
8 | {
9 | // This is the main entry point of the application.
10 | static void Main(string[] args)
11 | {
12 | // if you want to use a different Application Delegate class from "AppDelegate"
13 | // you can specify it here.
14 | UIApplication.Main(args, null, typeof(AppDelegate));
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Sample/AppShell.xaml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Sample/Platforms/MacCatalyst/Program.cs:
--------------------------------------------------------------------------------
1 | using ObjCRuntime;
2 |
3 | using UIKit;
4 |
5 | namespace Sample
6 | {
7 | public class Program
8 | {
9 | // This is the main entry point of the application.
10 | static void Main(string[] args)
11 | {
12 | // if you want to use a different Application Delegate class from "AppDelegate"
13 | // you can specify it here.
14 | UIApplication.Main(args, null, typeof(AppDelegate));
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/MPowerKit.Popups/Platforms/Android/AndroidExtensions.cs:
--------------------------------------------------------------------------------
1 | using AndroidX.Fragment.App;
2 |
3 | using Microsoft.Maui.Platform;
4 |
5 | namespace MPowerKit.Popups;
6 |
7 | public static class AndroidExtensions
8 | {
9 | public static FragmentManager GetFragmentManager(IMauiContext mauiContext)
10 | {
11 | var fragmentManager = mauiContext.Services.GetService();
12 |
13 | return fragmentManager
14 | ?? mauiContext.Context?.GetFragmentManager()
15 | ?? throw new InvalidOperationException("FragmentManager Not Found");
16 | }
17 | }
--------------------------------------------------------------------------------
/Sample/MauiProgram.cs:
--------------------------------------------------------------------------------
1 | using MPowerKit.Popups;
2 |
3 | namespace Sample
4 | {
5 | public static class MauiProgram
6 | {
7 | public static MauiApp CreateMauiApp()
8 | {
9 | var builder = MauiApp.CreateBuilder();
10 | builder
11 | .UseMauiApp()
12 | .UseMPowerKitPopups()
13 | .ConfigureFonts(fonts =>
14 | {
15 | fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
16 | fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
17 | });
18 |
19 | return builder.Build();
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/Sample/App.xaml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Sample/MainPage.xaml.cs:
--------------------------------------------------------------------------------
1 | using MPowerKit.Popups.Interfaces;
2 |
3 | namespace Sample;
4 |
5 | public partial class MainPage
6 | {
7 | int count;
8 |
9 | public IPopupService PopupService { get; }
10 |
11 | public MainPage()
12 | {
13 | InitializeComponent();
14 |
15 | PopupService = MPowerKit.Popups.PopupService.Current;
16 | }
17 |
18 | private void OnCounterClicked(object sender, EventArgs e)
19 | {
20 | count++;
21 |
22 | if (count == 1)
23 | CounterBtn.Text = $"Clicked {count} time";
24 | else
25 | CounterBtn.Text = $"Clicked {count} times";
26 |
27 | PopupService.ShowPopupAsync(new PopupTestPage());
28 | }
29 | }
--------------------------------------------------------------------------------
/Sample/Resources/Raw/AboutAssets.txt:
--------------------------------------------------------------------------------
1 | Any raw assets you want to be deployed with your application can be placed in
2 | this directory (and child directories). Deployment of the asset to your application
3 | is automatically handled by the following `MauiAsset` Build Action within your `.csproj`.
4 |
5 |
6 |
7 | These files will be deployed with you package and will be accessible using Essentials:
8 |
9 | async Task LoadMauiAsset()
10 | {
11 | using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt");
12 | using var reader = new StreamReader(stream);
13 |
14 | var contents = reader.ReadToEnd();
15 | }
16 |
--------------------------------------------------------------------------------
/MPowerKit.Popups/DisposableActionAttached.cs:
--------------------------------------------------------------------------------
1 | namespace MPowerKit.Popups;
2 |
3 | public class DisposableActionAttached
4 | {
5 | #region DisposableAction
6 | public static readonly BindableProperty DisposableActionProperty =
7 | BindableProperty.CreateAttached(
8 | "DisposableAction",
9 | typeof(DisposableAction),
10 | typeof(DisposableActionAttached),
11 | null);
12 |
13 | public static DisposableAction GetDisposableAction(BindableObject view) => (DisposableAction)view.GetValue(DisposableActionProperty);
14 |
15 | public static void SetDisposableAction(BindableObject view, DisposableAction value) => view.SetValue(DisposableActionProperty, value);
16 | #endregion
17 | }
18 |
--------------------------------------------------------------------------------
/Sample/Platforms/MacCatalyst/Entitlements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | com.apple.security.app-sandbox
8 |
9 |
10 | com.apple.security.network.client
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Sample/Platforms/Windows/app.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 | true/PM
12 | PerMonitorV2, PerMonitor
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/Sample/Platforms/Windows/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.UI.Xaml;
2 |
3 | // To learn more about WinUI, the WinUI project structure,
4 | // and more about our project templates, see: http://aka.ms/winui-project-info.
5 |
6 | namespace Sample.WinUI
7 | {
8 | ///
9 | /// Provides application-specific behavior to supplement the default Application class.
10 | ///
11 | public partial class App : MauiWinUIApplication
12 | {
13 | ///
14 | /// Initializes the singleton application object. This is the first line of authored code
15 | /// executed, and as such is the logical equivalent of main() or WinMain().
16 | ///
17 | public App()
18 | {
19 | this.InitializeComponent();
20 | }
21 |
22 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/Sample/PopupTestPage.xaml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
17 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 MPowerKit
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Sample/Platforms/iOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | LSRequiresIPhoneOS
6 |
7 | UIDeviceFamily
8 |
9 | 1
10 | 2
11 |
12 | UIRequiredDeviceCapabilities
13 |
14 | arm64
15 |
16 | UISupportedInterfaceOrientations
17 |
18 | UIInterfaceOrientationPortrait
19 | UIInterfaceOrientationLandscapeLeft
20 | UIInterfaceOrientationLandscapeRight
21 |
22 | UISupportedInterfaceOrientations~ipad
23 |
24 | UIInterfaceOrientationPortrait
25 | UIInterfaceOrientationPortraitUpsideDown
26 | UIInterfaceOrientationLandscapeLeft
27 | UIInterfaceOrientationLandscapeRight
28 |
29 | XSAppIconAssets
30 | Assets.xcassets/appicon.appiconset
31 |
32 |
33 |
--------------------------------------------------------------------------------
/MPowerKit.Popups/Platforms/Android/KeyboardListener.cs:
--------------------------------------------------------------------------------
1 | using Android.Views;
2 |
3 | namespace MPowerKit.Popups;
4 |
5 | public class KeyboardListener : Java.Lang.Object, ViewTreeObserver.IOnGlobalLayoutListener
6 | {
7 | private readonly ViewGroup _decorView;
8 |
9 | public bool KeyboardVisible { get; private set; }
10 |
11 | public KeyboardListener(ViewGroup decorView)
12 | {
13 | _decorView = decorView;
14 | }
15 |
16 | public void OnGlobalLayout()
17 | {
18 | var view = _decorView.FindViewById(Android.Resource.Id.Content);
19 | if (view is null) return;
20 |
21 | var r = new Android.Graphics.Rect();
22 | view!.GetWindowVisibleDisplayFrame(r);
23 | int screenHeight = view.RootView!.Height;
24 |
25 | // r.bottom is the position above soft keypad or device button.
26 | // if keypad is shown, the r.bottom is smaller than that before.
27 | int keypadHeight = screenHeight - r.Bottom;
28 |
29 | if (keypadHeight > screenHeight * 0.15)
30 | {
31 | if (!KeyboardVisible)
32 | {
33 | KeyboardVisible = true;
34 | }
35 | }
36 | else if (KeyboardVisible)
37 | {
38 | KeyboardVisible = false;
39 | }
40 | }
41 | }
--------------------------------------------------------------------------------
/Sample/MainPage.xaml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
10 |
14 |
15 |
18 |
19 |
23 |
24 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/MPowerKit.Popups/BuilderExtensions.cs:
--------------------------------------------------------------------------------
1 | #if ANDROID
2 | using Microsoft.Maui.LifecycleEvents;
3 |
4 | using MPowerKit.Popups.Interfaces;
5 | #endif
6 |
7 | namespace MPowerKit.Popups;
8 |
9 | public static class BuilderExtensions
10 | {
11 | public static MauiAppBuilder UseMPowerKitPopups(this MauiAppBuilder builder)
12 | {
13 | builder.Services.AddSingleton(_ => PopupService.Current);
14 | #if ANDROID
15 | builder
16 | .ConfigureLifecycleEvents(static lifecycle =>
17 | {
18 | lifecycle.AddAndroid(static d =>
19 | {
20 | d.OnBackPressed(static activity =>
21 | {
22 | var popupService = IPlatformApplication.Current!.Services.GetService();
23 |
24 | if (popupService is null || popupService.PopupStack.Count == 0) return false;
25 |
26 | try
27 | {
28 | if (popupService.PopupStack[^1].SendBackButtonPressed()) return true;
29 |
30 | popupService.HidePopupAsync(popupService.PopupStack[^1], true);
31 | return true;
32 | }
33 | catch
34 | {
35 | return false;
36 | }
37 | });
38 | });
39 | });
40 | #endif
41 | return builder;
42 | }
43 | }
--------------------------------------------------------------------------------
/Sample/Platforms/MacCatalyst/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | UIDeviceFamily
15 |
16 | 2
17 |
18 | UIRequiredDeviceCapabilities
19 |
20 | arm64
21 |
22 | UISupportedInterfaceOrientations
23 |
24 | UIInterfaceOrientationPortrait
25 | UIInterfaceOrientationLandscapeLeft
26 | UIInterfaceOrientationLandscapeRight
27 |
28 | UISupportedInterfaceOrientations~ipad
29 |
30 | UIInterfaceOrientationPortrait
31 | UIInterfaceOrientationPortraitUpsideDown
32 | UIInterfaceOrientationLandscapeLeft
33 | UIInterfaceOrientationLandscapeRight
34 |
35 | XSAppIconAssets
36 | Assets.xcassets/appicon.appiconset
37 |
38 |
39 |
--------------------------------------------------------------------------------
/Sample/Resources/Splash/splash.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/Sample/Resources/AppIcon/appiconfg.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/MPowerKit.Popups/Animations/FadeAnimation.cs:
--------------------------------------------------------------------------------
1 | namespace MPowerKit.Popups.Animations;
2 |
3 | public class FadeAnimation : FadeBackgroundAnimation
4 | {
5 | private double _defaultOpacity;
6 |
7 | public override void Preparing(View? content, PopupPage page)
8 | {
9 | base.Preparing(content, page);
10 |
11 | if (content is null) return;
12 |
13 | _defaultOpacity = content.Opacity;
14 | content.Opacity = 0;
15 | }
16 |
17 | public override void Disposing(View? content, PopupPage page)
18 | {
19 | base.Disposing(content, page);
20 |
21 | if (content is null) return;
22 |
23 | content.Opacity = _defaultOpacity;
24 | }
25 |
26 | public override Task Appearing(View? content, PopupPage page)
27 | {
28 | List tasks = [base.Appearing(content, page)];
29 |
30 | if (double.IsNaN(_defaultOpacity)) _defaultOpacity = 1;
31 |
32 | if (content is not null)
33 | {
34 | #if NET10_0_OR_GREATER
35 | tasks.Add(content.FadeToAsync(_defaultOpacity, (uint)DurationIn.TotalMilliseconds, EasingIn));
36 | #else
37 | tasks.Add(content.FadeTo(_defaultOpacity, (uint)DurationIn.TotalMilliseconds, EasingIn));
38 | #endif
39 | }
40 |
41 | return Task.WhenAll(tasks);
42 | }
43 |
44 | public override Task Disappearing(View? content, PopupPage page)
45 | {
46 | List tasks = [base.Disappearing(content, page)];
47 |
48 | if (content is not null)
49 | {
50 | #if NET10_0_OR_GREATER
51 | tasks.Add(content.FadeToAsync(0, (uint)DurationOut.TotalMilliseconds, EasingOut));
52 | #else
53 | tasks.Add(content.FadeTo(0, (uint)DurationOut.TotalMilliseconds, EasingOut));
54 | #endif
55 | }
56 |
57 | return Task.WhenAll(tasks);
58 | }
59 | }
--------------------------------------------------------------------------------
/MPowerKit.Popups.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.8.34330.188
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MPowerKit.Popups", "MPowerKit.Popups\MPowerKit.Popups.csproj", "{5E8AB8DB-43EC-41C8-8152-E6CB7C4E6655}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample", "Sample\Sample.csproj", "{6A838102-6E0F-4967-B2DB-78B3F069497F}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {5E8AB8DB-43EC-41C8-8152-E6CB7C4E6655}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {5E8AB8DB-43EC-41C8-8152-E6CB7C4E6655}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {5E8AB8DB-43EC-41C8-8152-E6CB7C4E6655}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {5E8AB8DB-43EC-41C8-8152-E6CB7C4E6655}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {6A838102-6E0F-4967-B2DB-78B3F069497F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {6A838102-6E0F-4967-B2DB-78B3F069497F}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {6A838102-6E0F-4967-B2DB-78B3F069497F}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
23 | {6A838102-6E0F-4967-B2DB-78B3F069497F}.Release|Any CPU.ActiveCfg = Release|Any CPU
24 | {6A838102-6E0F-4967-B2DB-78B3F069497F}.Release|Any CPU.Build.0 = Release|Any CPU
25 | {6A838102-6E0F-4967-B2DB-78B3F069497F}.Release|Any CPU.Deploy.0 = Release|Any CPU
26 | EndGlobalSection
27 | GlobalSection(SolutionProperties) = preSolution
28 | HideSolutionNode = FALSE
29 | EndGlobalSection
30 | GlobalSection(ExtensibilityGlobals) = postSolution
31 | SolutionGuid = {8474A50F-0FD0-4EDC-AB2F-50604F79E6CA}
32 | EndGlobalSection
33 | EndGlobal
34 |
--------------------------------------------------------------------------------
/Sample/Platforms/Windows/Package.appxmanifest:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | $placeholder$
15 | User Name
16 | $placeholder$.png
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/MPowerKit.Popups/Animations/Base/BaseAnimation.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using System.Globalization;
3 | using System.Reflection;
4 |
5 | using MPowerKit.Popups.Interfaces;
6 |
7 | namespace MPowerKit.Popups.Animations.Base;
8 |
9 | public class EasingTypeConverter : TypeConverter
10 | {
11 | public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
12 | {
13 | if (value is not null)
14 | {
15 | var fieldInfo = typeof(Easing).GetRuntimeFields()?
16 | .FirstOrDefault(fi => fi.IsStatic && fi.Name == value.ToString());
17 |
18 | var fieldValue = fieldInfo?.GetValue(null);
19 | if (fieldValue is not null) return (Easing)fieldValue;
20 | }
21 |
22 | throw new InvalidOperationException($"Cannot convert \"{value}\" into {typeof(Easing)}");
23 | }
24 | }
25 |
26 | public abstract class BaseAnimation : IPopupAnimation
27 | {
28 | private static readonly TimeSpan _defaultDuration = TimeSpan.FromMilliseconds(200);
29 |
30 | public TimeSpan DurationIn { get; set; } = _defaultDuration;
31 |
32 | public TimeSpan DurationOut { get; set; } = _defaultDuration;
33 |
34 | [TypeConverter(typeof(EasingTypeConverter))]
35 | public Easing EasingIn { get; set; } = Easing.Linear;
36 |
37 | [TypeConverter(typeof(EasingTypeConverter))]
38 | public Easing EasingOut { get; set; } = Easing.Linear;
39 |
40 | public abstract void Preparing(View? content, PopupPage page);
41 |
42 | public abstract void Disposing(View? content, PopupPage page);
43 |
44 | public abstract Task Appearing(View? content, PopupPage page);
45 |
46 | public abstract Task Disappearing(View? content, PopupPage page);
47 |
48 | protected virtual double GetTopOffset(View content, Page page)
49 | {
50 | return (content.Height + page.Height) / 2.0;
51 | }
52 |
53 | protected virtual double GetLeftOffset(View content, Page page)
54 | {
55 | return (content.Width + page.Width) / 2.0;
56 | }
57 | }
--------------------------------------------------------------------------------
/MPowerKit.Popups/Animations/FadeBackgroundAnimation.cs:
--------------------------------------------------------------------------------
1 | using MPowerKit.Popups.Animations.Base;
2 |
3 | namespace MPowerKit.Popups.Animations;
4 |
5 | public class FadeBackgroundAnimation : BaseAnimation
6 | {
7 | private Color? _backgroundColor;
8 |
9 | public bool HasBackgroundAnimation { get; set; } = true;
10 |
11 | public override void Preparing(View? content, PopupPage page)
12 | {
13 | if (!HasBackgroundAnimation || page.BackgroundColor is null) return;
14 |
15 | _backgroundColor = page.BackgroundColor;
16 |
17 | page.BackgroundColor = GetColor(0);
18 | }
19 |
20 | public override void Disposing(View? content, PopupPage page)
21 | {
22 | if (!HasBackgroundAnimation || _backgroundColor is null) return;
23 |
24 | page.BackgroundColor = _backgroundColor;
25 | }
26 |
27 | public override Task Appearing(View? content, PopupPage page)
28 | {
29 | if (!HasBackgroundAnimation || _backgroundColor is null) return Task.CompletedTask;
30 |
31 | var tcs = new TaskCompletionSource();
32 |
33 | page.Animate("backgroundFadeIn",
34 | d =>
35 | {
36 | page.BackgroundColor = GetColor((float)d);
37 | }, 0, _backgroundColor.Alpha, length: (uint)DurationIn.TotalMilliseconds, easing: EasingIn,
38 | finished: (d, b) =>
39 | {
40 | tcs.SetResult();
41 | });
42 |
43 | return tcs.Task;
44 | }
45 |
46 | public override Task Disappearing(View? content, PopupPage page)
47 | {
48 | if (!HasBackgroundAnimation || page.BackgroundColor is null) return Task.CompletedTask;
49 |
50 | var tcs = new TaskCompletionSource();
51 |
52 | _backgroundColor = page.BackgroundColor;
53 |
54 | page.Animate("backgroundFadeOut",
55 | d =>
56 | {
57 | page.BackgroundColor = GetColor((float)d);
58 | }, _backgroundColor.Alpha, 0, length: (uint)DurationOut.TotalMilliseconds, easing: EasingOut,
59 | finished: (d, b) =>
60 | {
61 | tcs.SetResult();
62 | });
63 |
64 | return tcs.Task;
65 | }
66 |
67 | private Color? GetColor(float transparent)
68 | {
69 | return _backgroundColor?.WithAlpha(transparent);
70 | }
71 | }
--------------------------------------------------------------------------------
/Sample/Resources/Styles/Colors.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 | #512BD4
10 | #ac99ea
11 | #242424
12 | #DFD8F7
13 | #9880e5
14 | #2B0B98
15 |
16 | White
17 | Black
18 | #D600AA
19 | #190649
20 | #1f1f1f
21 |
22 | #E1E1E1
23 | #C8C8C8
24 | #ACACAC
25 | #919191
26 | #6E6E6E
27 | #404040
28 | #212121
29 | #141414
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/MPowerKit.Popups/Platforms/Android/FragmentLifecycleCallback.cs:
--------------------------------------------------------------------------------
1 | using Android.OS;
2 |
3 | using Microsoft.Maui.Platform;
4 |
5 | using DialogFragment = AndroidX.Fragment.App.DialogFragment;
6 | using Fragment = AndroidX.Fragment.App.Fragment;
7 | using FragmentManager = AndroidX.Fragment.App.FragmentManager;
8 | using View = Android.Views.View;
9 | using ViewGroup = Android.Views.ViewGroup;
10 |
11 | namespace MPowerKit.Popups;
12 |
13 | public class FragmentLifecycleCallback : FragmentManager.FragmentLifecycleCallbacks
14 | {
15 | private readonly List _attachedViews = [];
16 | private readonly Func _getFragmentManager;
17 |
18 | public FragmentLifecycleCallback(Func getFragmentManager)
19 | {
20 | _getFragmentManager = getFragmentManager;
21 | }
22 |
23 | public virtual void AttachView(View view)
24 | {
25 | var dv = GetTopFragmentDecorView();
26 | if (dv is null) return;
27 |
28 | dv.AddView(view);
29 | _attachedViews.Add(view);
30 | }
31 |
32 | public virtual void DetachView(View view)
33 | {
34 | _attachedViews.Remove(view);
35 | view.RemoveFromParent();
36 | }
37 |
38 | public override void OnFragmentCreated(FragmentManager fm, Fragment f, Bundle? savedInstanceState)
39 | {
40 | base.OnFragmentCreated(fm, f, savedInstanceState);
41 |
42 | var dv = GetTopFragmentDecorView();
43 | if (dv is null) return;
44 |
45 | foreach (var view in _attachedViews)
46 | {
47 | view.RemoveFromParent();
48 | dv.AddView(view);
49 | }
50 | }
51 |
52 | public override void OnFragmentDestroyed(FragmentManager fm, Fragment f)
53 | {
54 | base.OnFragmentDestroyed(fm, f);
55 |
56 | var dv = GetTopFragmentDecorView();
57 | if (dv is null) return;
58 |
59 | foreach (var view in _attachedViews)
60 | {
61 | view.RemoveFromParent();
62 | dv.AddView(view);
63 | }
64 | }
65 |
66 | protected virtual ViewGroup? GetTopFragmentDecorView()
67 | {
68 | var fragments = _getFragmentManager?.Invoke()?.Fragments;
69 |
70 | Fragment? topFragment = null;
71 | if (fragments?.Count > 0)
72 | {
73 | topFragment = fragments[^1];
74 |
75 | if (topFragment is DialogFragment dialogFragment)
76 | {
77 | return dialogFragment.Dialog?.Window?.DecorView as ViewGroup;
78 | }
79 | }
80 |
81 | var activity = topFragment?.Activity ?? Platform.CurrentActivity;
82 |
83 | return activity?.Window?.DecorView as ViewGroup;
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # MPowerKit.Popups
2 |
3 | .NET MAUI popup library which allows you to open MAUI pages as a popup. Also the library allows you to use very simple and flexible animations for showing popup pages.
4 |
5 | [](https://www.nuget.org/packages/MPowerKit.Popups)
6 |
7 | [](https://www.buymeacoffee.com/alexdobrynin)
8 |
9 | Inspired by [Rg.Plugins.Popup](https://github.com/rotorgames/Rg.Plugins.Popup) and [Mopups](https://github.com/LuckyDucko/Mopups), but implementation is completely different.
10 |
11 | - It has almost the same PopupPage API as packages above, but improved animations, removed redundant properties as ```KeyboardOffset```, changed names of some properties.
12 |
13 | - Improved code and fixed some known bugs, eg Android window insets (system padding) or animation flickering.
14 |
15 | - Changed API of ```PopupService```, now you have an ability to choose a window to show/hide popup on.
16 |
17 | - Under the hood platform specific code does not use custom renderers for ```PopupPage```.
18 |
19 | - Hiding keyboard when tapping anywhere on popup except entry field
20 |
21 | - ```PopupStack``` is not static from now.
22 |
23 | - All API's are public or protected from now, so you can easily override and change implementation as you want
24 |
25 | ## Supported Platforms
26 |
27 | * .NET8
28 | * .NET8 for Android (min 24)
29 | * .NET8 for iOS (min 15)
30 | * .NET8 for MacCatalyst (min 15)
31 | * .NET8 for Windows (min 10.0.17763.0)
32 | * .NET9
33 | * .NET9 for Android (min 24)
34 | * .NET9 for iOS (min 15)
35 | * .NET9 for MacCatalyst (min 15)
36 | * .NET9 for Windows (min 10.0.17763.0)
37 |
38 | Note: .NET8/.NET9 for Tizen is not supported, but your PRs are welcome.
39 |
40 | ## Setup
41 |
42 | Add ```UseMPowerKitPopups()``` to your MauiProgram.cs file as next
43 |
44 | ```csharp
45 | builder
46 | .UseMauiApp()
47 | .UseMPowerKitPopups();
48 | ```
49 |
50 | ## Usage
51 |
52 | You can use both registered ```IPopupService``` or static singletone ```PopupService.Current```
53 |
54 | Inherit your popup page from ```PopupPage```:
55 |
56 | ```csharp
57 | public class YourCustomPopup : PopupPage...
58 | ```
59 |
60 | Show popup:
61 |
62 | ```csharp
63 | IPopupService _popupService;
64 |
65 | YourCustomPopup _popup;
66 |
67 | await _popupService.ShowPopupAsync(_popup, animated);
68 | ```
69 |
70 | It has overload for showing popup which accepts a window of type ```Window```, on which the popup will be shown:
71 |
72 | ```csharp
73 | IPopupService _popupService;
74 |
75 | YourCustomPopup _popup;
76 |
77 | await _popupService.ShowPopupAsync(_popup, desiredWindow, animated);
78 | ```
79 |
80 | Hide popup (the last one from ```PopupStack```):
81 |
82 | ```csharp
83 | IPopupService _popupService;
84 |
85 | await _popupService.HidePopupAsync(animated);
86 | ```
87 |
88 | And overload for hiding desired popup:
89 |
90 | ```csharp
91 | IPopupService _popupService;
92 |
93 | YourCustomPopup _popup;
94 |
95 | await _popupService.HidePopupAsync(_popup, animated);
96 | ```
97 |
98 | Note: Don't forget to catch informative exceptions;
99 |
--------------------------------------------------------------------------------
/MPowerKit.Popups/MPowerKit.Popups.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0;net9.0-android;net9.0-ios;net9.0-maccatalyst;net10.0;net10.0-android;net10.0-ios;net10.0-maccatalyst
5 | $(TargetFrameworks);net9.0-windows10.0.26100.0;net10.0-windows10.0.26100.0
6 |
7 | true
8 | true
9 | enable
10 | enable
11 |
12 | true
13 |
14 | 15.0
15 | 15.0
16 | 24.0
17 | 10.0.17763.0
18 | 10.0.17763.0
19 |
20 | True
21 | MPowerKit.Popups
22 | 1.6.0
23 | MPowerKit,Alex Dobrynin
24 | .NET MAUI popup library which allows you to open MAUI pages as a popup. Also the library allows you to use very simple and flexible animations for showing popup pages.
25 | MPowerKit
26 | https://github.com/MPowerKit/Popups
27 | https://github.com/MPowerKit/Popups
28 | maui;mvvm;prism;lightmvvm;rg;popups;mopups
29 | LICENSE
30 | readme.md
31 | icon.png
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | True
49 | \
50 |
51 |
52 | True
53 | \
54 |
55 |
56 | True
57 | \
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/Sample/Sample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0-android;net9.0-ios;net9.0-maccatalyst
5 | $(TargetFrameworks);net9.0-windows10.0.26100.0
6 |
7 |
12 |
13 |
14 | Exe
15 | Sample
16 | true
17 | true
18 | enable
19 | enable
20 |
21 |
22 | Sample
23 |
24 |
25 | com.companyname.sample
26 |
27 |
28 | 1.0
29 | 1
30 |
31 | 15.0
32 | 15.0
33 | 24.0
34 | 10.0.17763.0
35 | 10.0.17763.0
36 |
37 |
38 |
39 | android-arm;android-arm64;android-x86;android-x64
40 | 3G
41 | d8
42 | true
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/MPowerKit.Popups/PopupService.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Maui.Platform;
2 |
3 | using MPowerKit.Popups.Interfaces;
4 |
5 | namespace MPowerKit.Popups;
6 |
7 | public partial class PopupService : IPopupService
8 | {
9 | private static PopupService? _instance;
10 | public static IPopupService Current => _instance ??= new PopupService();
11 |
12 | protected List InternalPopupStack { get; } = [];
13 | public IReadOnlyList PopupStack => InternalPopupStack;
14 |
15 | public virtual ValueTask ShowPopupAsync(PopupPage page, bool animated = true)
16 | {
17 | if (PopupStack.Contains(page))
18 | {
19 | throw new InvalidOperationException("This popup already presented");
20 | }
21 |
22 | var window = Application.Current?.Windows[^1];
23 |
24 | return ShowPopupAsync(page, window, animated);
25 | }
26 |
27 | public virtual async ValueTask ShowPopupAsync(PopupPage page, Window? attachToWindow, bool animated = true)
28 | {
29 | if (PopupStack.Contains(page))
30 | {
31 | throw new InvalidOperationException("This popup already presented");
32 | }
33 | if (attachToWindow is null)
34 | {
35 | throw new InvalidOperationException("Parent window not found");
36 | }
37 |
38 | animated = animated && AnimationHelper.SystemAnimationsEnabled;
39 |
40 | var pageHandler = page.ToHandler(attachToWindow.Handler.MauiContext!);
41 |
42 | attachToWindow.AddLogicalChild(page);
43 |
44 | page.BackgroundClicked += async (s, e) =>
45 | {
46 | if (!page.CloseOnBackgroundClick) return;
47 |
48 | try
49 | {
50 | if (e.Handled) return;
51 | await HidePopupAsync(page, animated);
52 | }
53 | catch { }
54 | };
55 |
56 | if (animated)
57 | {
58 | page.PreparingAnimation();
59 | }
60 |
61 | AttachToWindow(page, pageHandler, attachToWindow!);
62 | page.SendAppearing();
63 |
64 | InternalPopupStack.Add(page);
65 |
66 | if (animated)
67 | {
68 | //HACK: animation needs dispatcher to get page size ready
69 | await page.Dispatcher.DispatchAsync(page.AppearingAnimation);
70 | }
71 | }
72 |
73 | public virtual ValueTask HidePopupAsync(bool animated = true)
74 | {
75 | if (PopupStack.Count == 0)
76 | {
77 | throw new InvalidOperationException("Popup stack is empty");
78 | }
79 |
80 | var page = PopupStack[^1];
81 |
82 | return HidePopupAsync(page, animated);
83 | }
84 |
85 | public virtual ValueTask HidePopupAsync(PopupPage page, bool animated = true)
86 | {
87 | return HidePopupAsync(page, page.Window, animated);
88 | }
89 |
90 | protected virtual async ValueTask HidePopupAsync(PopupPage page, Window parentWindow, bool animated = true)
91 | {
92 | if (page.IsClosing) return;
93 |
94 | if (!PopupStack.Contains(page))
95 | {
96 | throw new InvalidOperationException("Popup stack does not contain chosen page");
97 | }
98 | if (parentWindow is null)
99 | {
100 | throw new InvalidOperationException("Parent window not found");
101 | }
102 |
103 | page.IsClosing = true;
104 |
105 | animated = animated && AnimationHelper.SystemAnimationsEnabled;
106 |
107 | if (animated)
108 | {
109 | await page.DisappearingAnimation();
110 | }
111 |
112 | page.SendDisappearing();
113 | DetachFromWindow(page, page.Handler!, parentWindow);
114 |
115 | parentWindow.RemoveLogicalChild(page);
116 | InternalPopupStack.Remove(page);
117 |
118 | if (animated)
119 | {
120 | page.DisposingAnimation();
121 | }
122 |
123 | page.BindingContext = null;
124 | page.Behaviors.Clear();
125 | #if NET9_0_OR_GREATER
126 | page.DisconnectHandlers();
127 | #endif
128 | }
129 |
130 | protected virtual partial void AttachToWindow(PopupPage page, IViewHandler pageHandler, Window parentWindow);
131 | protected virtual partial void DetachFromWindow(PopupPage page, IViewHandler pageHandler, Window parentWindow);
132 | }
--------------------------------------------------------------------------------
/MPowerKit.Popups/Animations/MoveAnimation.cs:
--------------------------------------------------------------------------------
1 | namespace MPowerKit.Popups.Animations;
2 |
3 | public class MoveAnimation : FadeBackgroundAnimation
4 | {
5 | private double _defaultTranslationX;
6 | private double _defaultTranslationY;
7 |
8 | public MoveAnimationOptions PositionIn { get; set; }
9 | public MoveAnimationOptions PositionOut { get; set; }
10 |
11 | public MoveAnimation() : this(MoveAnimationOptions.Bottom, MoveAnimationOptions.Bottom) { }
12 |
13 | public MoveAnimation(MoveAnimationOptions positionIn, MoveAnimationOptions positionOut)
14 | {
15 | PositionIn = positionIn;
16 | PositionOut = positionOut;
17 |
18 | DurationIn = DurationOut = TimeSpan.FromMilliseconds(300);
19 | EasingIn = Easing.SinOut;
20 | EasingOut = Easing.SinIn;
21 | }
22 |
23 | public override void Preparing(View? content, PopupPage page)
24 | {
25 | base.Preparing(content, page);
26 |
27 | if (content is null) return;
28 |
29 | UpdateDefaultTranslations(content);
30 |
31 | //HACK: this removes flickering on appearing
32 | content.TranslationX = content.TranslationY = -1000000d;
33 | }
34 |
35 | public override void Disposing(View? content, PopupPage page)
36 | {
37 | base.Disposing(content, page);
38 |
39 | if (content is null) return;
40 |
41 | content.TranslationX = _defaultTranslationX;
42 | content.TranslationY = _defaultTranslationY;
43 | }
44 |
45 | public override Task Appearing(View? content, PopupPage page)
46 | {
47 | List taskList = [base.Appearing(content, page)];
48 |
49 | if (content is not null)
50 | {
51 | //HACK: restore default translations
52 | content.TranslationX = _defaultTranslationX;
53 | content.TranslationY = _defaultTranslationY;
54 |
55 | var topOffset = GetTopOffset(content, page);
56 | var leftOffset = GetLeftOffset(content, page);
57 |
58 | if (PositionIn.HasFlag(MoveAnimationOptions.Top))
59 | {
60 | content.TranslationY = -topOffset;
61 | }
62 | else if (PositionIn.HasFlag(MoveAnimationOptions.Bottom))
63 | {
64 | content.TranslationY = topOffset;
65 | }
66 |
67 | if (PositionIn.HasFlag(MoveAnimationOptions.Left))
68 | {
69 | content.TranslationX = -leftOffset;
70 | }
71 | else if (PositionIn.HasFlag(MoveAnimationOptions.Right))
72 | {
73 | content.TranslationX = leftOffset;
74 | }
75 | #if NET10_0_OR_GREATER
76 | taskList.Add(content.TranslateToAsync(_defaultTranslationX, _defaultTranslationY, (uint)DurationIn.TotalMilliseconds, EasingIn));
77 | #else
78 | taskList.Add(content.TranslateTo(_defaultTranslationX, _defaultTranslationY, (uint)DurationIn.TotalMilliseconds, EasingIn));
79 | #endif
80 | }
81 |
82 | return Task.WhenAll(taskList);
83 | }
84 |
85 | public override Task Disappearing(View? content, PopupPage page)
86 | {
87 | List taskList = [base.Disappearing(content, page)];
88 |
89 | if (content is not null)
90 | {
91 | UpdateDefaultTranslations(content);
92 |
93 | var topOffset = GetTopOffset(content, page);
94 | var leftOffset = GetLeftOffset(content, page);
95 |
96 | var translationX = _defaultTranslationX;
97 | var translationY = _defaultTranslationY;
98 |
99 | if (PositionOut.HasFlag(MoveAnimationOptions.Top))
100 | {
101 | translationY = -topOffset;
102 | }
103 | else if (PositionOut.HasFlag(MoveAnimationOptions.Bottom))
104 | {
105 | translationY = topOffset;
106 | }
107 |
108 | if (PositionOut == MoveAnimationOptions.Left)
109 | {
110 | translationX = -leftOffset;
111 | }
112 | else if (PositionOut == MoveAnimationOptions.Right)
113 | {
114 | translationX = leftOffset;
115 | }
116 |
117 | #if NET10_0_OR_GREATER
118 | taskList.Add(content.TranslateToAsync(translationX, translationY, (uint)DurationOut.TotalMilliseconds, EasingOut));
119 | #else
120 | taskList.Add(content.TranslateTo(translationX, translationY, (uint)DurationOut.TotalMilliseconds, EasingOut));
121 | #endif
122 | }
123 |
124 | return Task.WhenAll(taskList);
125 | }
126 |
127 | private void UpdateDefaultTranslations(View content)
128 | {
129 | _defaultTranslationX = content.TranslationX;
130 | _defaultTranslationY = content.TranslationY;
131 | }
132 | }
--------------------------------------------------------------------------------
/MPowerKit.Popups/Platforms/Windows/PopupService.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.UI.Xaml.Controls;
2 | using Microsoft.UI.Xaml.Input;
3 |
4 | using Windows.UI.ViewManagement;
5 |
6 | namespace MPowerKit.Popups;
7 |
8 | public partial class PopupService
9 | {
10 | protected virtual partial void AttachToWindow(PopupPage page, IViewHandler pageHandler, Window parentWindow)
11 | {
12 | var uiWindow = parentWindow.Handler.PlatformView as MauiWinUIWindow;
13 |
14 | var content = uiWindow?.Content as Panel
15 | ?? throw new InvalidOperationException("Window not found");
16 |
17 | var handler = (pageHandler as IPlatformViewHandler)!;
18 |
19 | var inputPane = InputPaneInterop.GetForWindow(uiWindow!.WindowHandle);
20 |
21 | void pointerEventHandler(object s, PointerRoutedEventArgs e)
22 | {
23 | if (inputPane.Visible)
24 | {
25 | inputPane.TryHide();
26 | var element = FocusManager.GetFocusedElement(handler.PlatformView!.XamlRoot) as Control;
27 | element?.Focus(Microsoft.UI.Xaml.FocusState.Programmatic);
28 | return;
29 | }
30 |
31 | if ((e.OriginalSource as Microsoft.UI.Xaml.FrameworkElement) != handler.PlatformView)
32 | {
33 | e.Handled = true;
34 | return;
35 | }
36 |
37 | page.SendBackgroundClick();
38 |
39 | e.Handled = !page.BackgroundInputTransparent;
40 | }
41 | handler.PlatformView!.PointerPressed += pointerEventHandler;
42 |
43 | var platform = handler.PlatformView!;
44 | if (page.BackgroundInputTransparent)
45 | {
46 | platform.Margin = new Microsoft.UI.Xaml.Thickness(
47 | page.Content.Margin.Left,
48 | page.Content.Margin.Top,
49 | page.Content.Margin.Right,
50 | page.Content.Margin.Bottom);
51 | page.Content.Margin = new Thickness(0);
52 |
53 | PageContentSizeChanged(page, EventArgs.Empty);
54 |
55 | page.Content.SizeChanged += PageContentSizeChanged;
56 | }
57 | void PageContentSizeChanged(object? sender, EventArgs args)
58 | {
59 | var measured = (page.Content as IView).Measure(double.PositiveInfinity, double.PositiveInfinity);
60 |
61 | platform.HorizontalAlignment = page.Content.HorizontalOptions.ToPlatformHorizontal();
62 | platform.VerticalAlignment = page.Content.VerticalOptions.ToPlatformVertical();
63 |
64 | if (platform.HorizontalAlignment is not Microsoft.UI.Xaml.HorizontalAlignment.Stretch)
65 | {
66 | platform.Width = measured.Width;
67 | }
68 | if (platform.VerticalAlignment is not Microsoft.UI.Xaml.VerticalAlignment.Stretch)
69 | {
70 | platform.Height = measured.Height;
71 | }
72 | }
73 |
74 | var action = new DisposableAction(() =>
75 | {
76 | handler.PlatformView!.PointerPressed -= pointerEventHandler;
77 | if (page.BackgroundInputTransparent)
78 | {
79 | page.Content.SizeChanged -= PageContentSizeChanged;
80 | }
81 | });
82 | page.SetValue(DisposableActionAttached.DisposableActionProperty, action);
83 |
84 | AddToVisualTree(page, handler, content);
85 | }
86 |
87 | protected virtual void AddToVisualTree(PopupPage page, IPlatformViewHandler handler, Panel windowContent)
88 | {
89 | var platform = handler.PlatformView!;
90 | windowContent.Children.Add(platform);
91 | }
92 |
93 | protected virtual partial void DetachFromWindow(PopupPage page, IViewHandler pageHandler, Window parentWindow)
94 | {
95 | var uiWindow = parentWindow.Handler.PlatformView as MauiWinUIWindow;
96 |
97 | var content = uiWindow?.Content as Panel
98 | ?? throw new InvalidOperationException("Window not found");
99 |
100 | var handler = (pageHandler as IPlatformViewHandler)!;
101 |
102 | var action = page.GetValue(DisposableActionAttached.DisposableActionProperty) as DisposableAction;
103 | action?.Dispose();
104 |
105 | RemoveFromVisualTree(page, handler, content);
106 | }
107 |
108 | protected virtual void RemoveFromVisualTree(PopupPage page, IPlatformViewHandler handler, Panel windowContent)
109 | {
110 | windowContent.Children.Remove(handler.PlatformView);
111 | }
112 | }
113 |
114 | public static class WinUIExtensions
115 | {
116 | public static Microsoft.UI.Xaml.HorizontalAlignment ToPlatformHorizontal(this LayoutOptions options)
117 | {
118 | int i = 0;
119 | return i switch
120 | {
121 | _ when options == LayoutOptions.Start => Microsoft.UI.Xaml.HorizontalAlignment.Left,
122 | _ when options == LayoutOptions.Center => Microsoft.UI.Xaml.HorizontalAlignment.Center,
123 | _ when options == LayoutOptions.End => Microsoft.UI.Xaml.HorizontalAlignment.Right,
124 | _ when options == LayoutOptions.Fill => Microsoft.UI.Xaml.HorizontalAlignment.Stretch,
125 | _ => throw new ArgumentOutOfRangeException(nameof(options), options, null)
126 | };
127 | }
128 |
129 | public static Microsoft.UI.Xaml.VerticalAlignment ToPlatformVertical(this LayoutOptions options)
130 | {
131 | int i = 0;
132 | return i switch
133 | {
134 | _ when options == LayoutOptions.Start => Microsoft.UI.Xaml.VerticalAlignment.Top,
135 | _ when options == LayoutOptions.Center => Microsoft.UI.Xaml.VerticalAlignment.Center,
136 | _ when options == LayoutOptions.End => Microsoft.UI.Xaml.VerticalAlignment.Bottom,
137 | _ when options == LayoutOptions.Fill => Microsoft.UI.Xaml.VerticalAlignment.Stretch,
138 | _ => throw new ArgumentOutOfRangeException(nameof(options), options, null)
139 | };
140 | }
141 | }
--------------------------------------------------------------------------------
/MPowerKit.Popups/Animations/ScaleAnimation.cs:
--------------------------------------------------------------------------------
1 | namespace MPowerKit.Popups.Animations;
2 |
3 | public class ScaleAnimation : FadeAnimation
4 | {
5 | private double _defaultScale;
6 | private double _defaultTranslationX;
7 | private double _defaultTranslationY;
8 |
9 | public double ScaleIn { get; set; } = 0.8;
10 | public double ScaleOut { get; set; } = 0.8;
11 |
12 | public MoveAnimationOptions PositionIn { get; set; }
13 | public MoveAnimationOptions PositionOut { get; set; }
14 |
15 | public ScaleAnimation() : this(MoveAnimationOptions.Center, MoveAnimationOptions.Center) { }
16 |
17 | public ScaleAnimation(MoveAnimationOptions positionIn, MoveAnimationOptions positionOut)
18 | {
19 | PositionIn = positionIn;
20 | PositionOut = positionOut;
21 | EasingIn = Easing.SinOut;
22 | EasingOut = Easing.SinIn;
23 |
24 | if (PositionIn is not MoveAnimationOptions.Center) DurationIn = TimeSpan.FromMilliseconds(400);
25 | if (PositionOut is not MoveAnimationOptions.Center) DurationOut = TimeSpan.FromMilliseconds(400);
26 | }
27 |
28 | public override void Preparing(View? content, PopupPage page)
29 | {
30 | base.Preparing(content, page);
31 |
32 | if (content == null) return;
33 |
34 | UpdateDefaultProperties(content);
35 |
36 | //HACK: this removes flickering on appearing
37 | content.TranslationX = content.TranslationY = -1000000d;
38 | }
39 |
40 | public override void Disposing(View? content, PopupPage page)
41 | {
42 | base.Disposing(content, page);
43 |
44 | if (content == null) return;
45 |
46 | content.Scale = _defaultScale;
47 | content.TranslationX = _defaultTranslationX;
48 | content.TranslationY = _defaultTranslationY;
49 | }
50 |
51 | public override Task Appearing(View? content, PopupPage page)
52 | {
53 | List taskList = [base.Appearing(content, page)];
54 |
55 | if (content is not null)
56 | {
57 | //HACK: restore default translations
58 | content.TranslationX = _defaultTranslationX;
59 | content.TranslationY = _defaultTranslationY;
60 |
61 | var topOffset = GetTopOffset(content, page) * ScaleIn;
62 | var leftOffset = GetLeftOffset(content, page) * ScaleIn;
63 |
64 | taskList.Add(Scale(content, EasingIn, ScaleIn, _defaultScale, true));
65 |
66 | if (PositionIn.HasFlag(MoveAnimationOptions.Top))
67 | {
68 | content.TranslationY = -topOffset;
69 | }
70 | else if (PositionIn.HasFlag(MoveAnimationOptions.Bottom))
71 | {
72 | content.TranslationY = topOffset;
73 | }
74 |
75 | if (PositionIn.HasFlag(MoveAnimationOptions.Left))
76 | {
77 | content.TranslationX = -leftOffset;
78 | }
79 | else if (PositionIn.HasFlag(MoveAnimationOptions.Right))
80 | {
81 | content.TranslationX = leftOffset;
82 | }
83 |
84 | if (PositionIn is not MoveAnimationOptions.Center)
85 | {
86 | #if NET10_0_OR_GREATER
87 | taskList.Add(content.TranslateToAsync(_defaultTranslationX, _defaultTranslationY, (uint)DurationIn.TotalMilliseconds, EasingIn));
88 | #else
89 | taskList.Add(content.TranslateTo(_defaultTranslationX, _defaultTranslationY, (uint)DurationIn.TotalMilliseconds, EasingIn));
90 | #endif
91 | }
92 | }
93 |
94 | return Task.WhenAll(taskList);
95 | }
96 |
97 | public override Task Disappearing(View? content, PopupPage page)
98 | {
99 | List taskList = [base.Disappearing(content, page)];
100 |
101 | if (content is not null)
102 | {
103 | UpdateDefaultProperties(content);
104 |
105 | var topOffset = GetTopOffset(content, page) * ScaleOut;
106 | var leftOffset = GetLeftOffset(content, page) * ScaleOut;
107 |
108 | var translationX = _defaultTranslationX;
109 | var translationY = _defaultTranslationY;
110 |
111 | taskList.Add(Scale(content, EasingOut, _defaultScale, ScaleOut, false));
112 |
113 | if (PositionOut.HasFlag(MoveAnimationOptions.Top))
114 | {
115 | translationY = -topOffset;
116 | }
117 | else if (PositionOut.HasFlag(MoveAnimationOptions.Bottom))
118 | {
119 | translationY = topOffset;
120 | }
121 |
122 | if (PositionOut == MoveAnimationOptions.Left)
123 | {
124 | translationX = -leftOffset;
125 | }
126 | else if (PositionOut == MoveAnimationOptions.Right)
127 | {
128 | translationX = leftOffset;
129 | }
130 | #if NET10_0_OR_GREATER
131 | taskList.Add(content.TranslateToAsync(translationX, translationY, (uint)DurationOut.TotalMilliseconds, EasingOut));
132 | #else
133 | taskList.Add(content.TranslateTo(translationX, translationY, (uint)DurationOut.TotalMilliseconds, EasingOut));
134 | #endif
135 | }
136 |
137 | return Task.WhenAll(taskList);
138 | }
139 |
140 | private Task Scale(View content, Easing easing, double start, double end, bool isAppearing)
141 | {
142 | var task = new TaskCompletionSource();
143 |
144 | var duration = (uint)(isAppearing ? DurationIn.TotalMilliseconds : DurationOut.TotalMilliseconds);
145 |
146 | content.Animate("popIn",
147 | d =>
148 | {
149 | content.Scale = double.IsNaN(d) ? 1 : d;
150 | }, start, end, easing: easing, length: duration,
151 | finished: (d, b) =>
152 | {
153 | task.SetResult();
154 | });
155 |
156 | return task.Task;
157 | }
158 |
159 | private void UpdateDefaultProperties(View content)
160 | {
161 | _defaultScale = content.Scale;
162 | _defaultTranslationX = content.TranslationX;
163 | _defaultTranslationY = content.TranslationY;
164 | }
165 | }
--------------------------------------------------------------------------------
/MPowerKit.Popups/MaciOS/PopupService.cs:
--------------------------------------------------------------------------------
1 | using CoreGraphics;
2 |
3 | using Microsoft.Maui.Handlers;
4 | using Microsoft.Maui.Platform;
5 |
6 | using UIKit;
7 |
8 | namespace MPowerKit.Popups;
9 |
10 | public partial class PopupService
11 | {
12 | protected static readonly List PrevKeyWindows = [];
13 |
14 | protected readonly List Windows = [];
15 |
16 | protected virtual partial void AttachToWindow(PopupPage page, IViewHandler pageHandler, Window parentWindow)
17 | {
18 | var window = parentWindow.Handler.PlatformView as UIWindow
19 | ?? throw new InvalidOperationException("Window not found");
20 |
21 | var handler = (pageHandler as IPlatformViewHandler)!;
22 |
23 | var gr = new UITapGestureRecognizer(e =>
24 | {
25 | var loc = e.LocationOfTouch(0, e.View);
26 | var view = e.View.HitTest(loc, null);
27 |
28 | if (handler.ViewController is PageViewController pc && pc.CurrentPlatformView == view)
29 | {
30 | page.SendBackgroundClick();
31 | }
32 | })
33 | { CancelsTouchesInView = false };
34 |
35 | handler.ViewController!.View!.AddGestureRecognizer(gr);
36 |
37 | var prevKeyWindow = GetKeyWindow();
38 |
39 | var action = new DisposableAction(() =>
40 | {
41 | handler.ViewController!.View!.RemoveGestureRecognizer(gr);
42 | });
43 | page.SetValue(DisposableActionAttached.DisposableActionProperty, action);
44 |
45 | AddToVisualTree(handler);
46 |
47 | (prevKeyWindow ?? window).WindowLevel = -1;
48 |
49 | StorePrevKeyWindow(prevKeyWindow);
50 | }
51 |
52 | protected virtual void AddToVisualTree(IPlatformViewHandler handler)
53 | {
54 | var connectedScene = GetActiveScene();
55 | PopupWindow popupWindow = connectedScene is not null
56 | ? new(connectedScene)
57 | : new();
58 |
59 | Windows.Add(popupWindow);
60 |
61 | popupWindow.BackgroundColor = UIColor.Clear;
62 |
63 | popupWindow.RootViewController = handler.ViewController;
64 |
65 | popupWindow.MakeKeyAndVisible();
66 | }
67 |
68 | protected virtual partial void DetachFromWindow(PopupPage page, IViewHandler pageHandler, Window parentWindow)
69 | {
70 | var window = parentWindow.Handler.PlatformView as UIWindow
71 | ?? throw new InvalidOperationException("Window not found");
72 |
73 | var handler = (pageHandler as PageHandler)!;
74 |
75 | var action = page.GetValue(DisposableActionAttached.DisposableActionProperty) as DisposableAction;
76 | action?.Dispose();
77 |
78 | RemoveFromVisualTree(handler);
79 |
80 | var wnd = RestorePrevKeyWindow() ?? window;
81 |
82 | wnd.WindowLevel = UIWindowLevel.Normal;
83 |
84 | wnd.MakeKeyWindow();
85 | }
86 |
87 | protected virtual void RemoveFromVisualTree(IPlatformViewHandler handler)
88 | {
89 | var view = handler.ViewController!.View!;
90 |
91 | var popupWindow = view.Window!;
92 |
93 | popupWindow.RootViewController!.DismissViewController(false, null);
94 | popupWindow.RootViewController.Dispose();
95 | popupWindow.Hidden = true;
96 | popupWindow.WindowScene = null;
97 | popupWindow.Dispose();
98 |
99 | Windows.Remove(popupWindow);
100 | }
101 |
102 | protected virtual void StorePrevKeyWindow(UIWindow? window)
103 | {
104 | if (window is null) return;
105 |
106 | PrevKeyWindows.Remove(window);
107 |
108 | PrevKeyWindows.Add(window);
109 | }
110 |
111 | protected virtual UIWindow? RestorePrevKeyWindow()
112 | {
113 | if (PrevKeyWindows.Count == 0) return null;
114 |
115 | var window = PrevKeyWindows[^1];
116 |
117 | PrevKeyWindows.Remove(window);
118 |
119 | return window;
120 | }
121 |
122 | public class PopupWindow : UIWindow
123 | {
124 | public PopupWindow(IntPtr handle) : base(handle)
125 | {
126 | }
127 |
128 | public PopupWindow()
129 | {
130 | }
131 |
132 | public PopupWindow(UIWindowScene uiWindowScene) : base(uiWindowScene)
133 | {
134 | }
135 |
136 | public override UIView? HitTest(CGPoint point, UIEvent? uiEvent)
137 | {
138 | var viewController = (RootViewController as PageViewController)!;
139 | var page = (viewController.CurrentView as PopupPage)!;
140 |
141 | var hitTestResult = base.HitTest(point, uiEvent);
142 |
143 | if (uiEvent?.Type is not UIEventType.Hover && hitTestResult is not (UITextField or UITextView))
144 | {
145 | var responder = viewController.View!.FindFirstResponder();
146 | if (responder is not (null or MauiDatePicker))
147 | {
148 | responder.EndEditing(true);
149 | }
150 | }
151 |
152 | return hitTestResult == viewController.CurrentPlatformView
153 | && page.BackgroundInputTransparent
154 | ? null
155 | : hitTestResult;
156 | }
157 | }
158 |
159 | public static UIWindow? GetKeyWindow()
160 | {
161 | var window = GetActiveScene()?.Windows.FirstOrDefault(w => w.IsKeyWindow);
162 |
163 | return window;
164 | }
165 |
166 | public static UIWindowScene? GetActiveScene()
167 | {
168 | var connectedScene = UIApplication.SharedApplication.ConnectedScenes
169 | .OfType()
170 | .FirstOrDefault(x => x.ActivationState == UISceneActivationState.ForegroundActive);
171 |
172 | return connectedScene;
173 | }
174 | }
175 |
176 | public static class PopupServiceExtensions
177 | {
178 | public static UIView? FindFirstResponder(this UIView view)
179 | {
180 | if (view.IsFirstResponder) return view;
181 |
182 | foreach (var subView in view.Subviews)
183 | {
184 | var responder = subView.FindFirstResponder();
185 | if (responder is not null) return responder;
186 | }
187 |
188 | return null;
189 | }
190 | }
--------------------------------------------------------------------------------
/MPowerKit.Popups/Platforms/Android/ParentLayout.cs:
--------------------------------------------------------------------------------
1 | using Android.Content;
2 | using Android.Views;
3 |
4 | using AndroidX.ConstraintLayout.Widget;
5 |
6 | using Microsoft.Maui.Platform;
7 |
8 | using View = Android.Views.View;
9 | using ViewGroup = Android.Views.ViewGroup;
10 |
11 | namespace MPowerKit.Popups;
12 |
13 | public class ParentLayout : ConstraintLayout, ViewTreeObserver.IOnGlobalLayoutListener
14 | {
15 | private readonly ViewGroup _decorView;
16 | private readonly PopupPage _page;
17 | private readonly ViewGroup _platformView;
18 | private View? _top;
19 | private View? _bottom;
20 | private View? _left;
21 | private View? _right;
22 | private Android.Graphics.Rect? _prevInsets;
23 |
24 | public ParentLayout(Context context, ViewGroup decorView, PopupPage page) : base(context)
25 | {
26 | _decorView = decorView;
27 | _page = page;
28 | _platformView = (page.Handler!.PlatformView as ViewGroup)!;
29 |
30 | InitContent();
31 |
32 | _page.PropertyChanged += Page_PropertyChanged;
33 |
34 | _decorView.ViewTreeObserver!.AddOnGlobalLayoutListener(this);
35 | }
36 |
37 | public void RemoveGlobalLayoutListener()
38 | {
39 | _page.PropertyChanged -= Page_PropertyChanged;
40 | _decorView.ViewTreeObserver!.RemoveOnGlobalLayoutListener(this);
41 | }
42 |
43 | private void InitContent()
44 | {
45 | _top = new View(Context) { Id = View.GenerateViewId() };
46 | _bottom = new View(Context) { Id = View.GenerateViewId() };
47 | _left = new View(Context) { Id = View.GenerateViewId() };
48 | _right = new View(Context) { Id = View.GenerateViewId() };
49 | _platformView.Id = View.GenerateViewId();
50 |
51 | var color = _page.BackgroundColor.ToPlatform();
52 |
53 | _top.SetBackgroundColor(color);
54 | _bottom.SetBackgroundColor(color);
55 | _left.SetBackgroundColor(color);
56 | _right.SetBackgroundColor(color);
57 |
58 | var alpha = (float)_page.Opacity;
59 |
60 | _top.Alpha = alpha;
61 | _bottom.Alpha = alpha;
62 | _left.Alpha = alpha;
63 | _right.Alpha = alpha;
64 |
65 | Android.Graphics.Rect insets;
66 | if (OperatingSystem.IsAndroidVersionAtLeast(30))
67 | {
68 | var ins = _decorView.RootWindowInsets!.GetInsetsIgnoringVisibility(Android.Views.WindowInsets.Type.SystemBars());
69 | insets = new Android.Graphics.Rect(ins.Left, ins.Top, ins.Right, ins.Bottom);
70 | }
71 | else
72 | {
73 | var ins = _decorView.RootWindowInsets!;
74 | insets = new Android.Graphics.Rect(ins.StableInsetLeft, ins.StableInsetTop, ins.StableInsetRight, ins.StableInsetBottom);
75 | }
76 |
77 | _prevInsets = insets;
78 |
79 | var topParams = new LayoutParams(LayoutParams.MatchParent, insets.Top);
80 | _top.LayoutParameters = topParams;
81 |
82 | var bottomParams = new LayoutParams(LayoutParams.MatchParent, insets.Bottom);
83 | _bottom.LayoutParameters = bottomParams;
84 |
85 | var leftParams = new LayoutParams(insets.Left, LayoutParams.MatchConstraint);
86 | _left.LayoutParameters = leftParams;
87 |
88 | var rightParams = new LayoutParams(insets.Right, LayoutParams.MatchConstraint);
89 | _right.LayoutParameters = rightParams;
90 |
91 | var centerParams = new LayoutParams(LayoutParams.MatchConstraint, LayoutParams.MatchConstraint);
92 | _platformView.LayoutParameters = centerParams;
93 |
94 | ParentLayout.ToggleViewVisibility(_top, insets.Top);
95 | ParentLayout.ToggleViewVisibility(_right, insets.Right);
96 | ParentLayout.ToggleViewVisibility(_bottom, insets.Bottom);
97 | ParentLayout.ToggleViewVisibility(_left, insets.Left);
98 |
99 | this.AddView(_top);
100 | this.AddView(_bottom);
101 | this.AddView(_left);
102 | this.AddView(_right);
103 | this.AddView(_platformView);
104 |
105 | var set = new ConstraintSet();
106 | set.Clone(this);
107 |
108 | set.Connect(_top.Id, ConstraintSet.Top, ConstraintSet.ParentId, ConstraintSet.Top);
109 |
110 | set.Connect(_bottom.Id, ConstraintSet.Bottom, ConstraintSet.ParentId, ConstraintSet.Bottom);
111 |
112 | set.Connect(_left.Id, ConstraintSet.Left, ConstraintSet.ParentId, ConstraintSet.Left);
113 | set.Connect(_left.Id, ConstraintSet.Top, _top.Id, ConstraintSet.Bottom);
114 | set.Connect(_left.Id, ConstraintSet.Bottom, _bottom.Id, ConstraintSet.Top);
115 |
116 | set.Connect(_right.Id, ConstraintSet.Right, ConstraintSet.ParentId, ConstraintSet.Right);
117 | set.Connect(_right.Id, ConstraintSet.Top, _top.Id, ConstraintSet.Bottom);
118 | set.Connect(_right.Id, ConstraintSet.Bottom, _bottom.Id, ConstraintSet.Top);
119 |
120 | set.Connect(_platformView.Id, ConstraintSet.Left, _left.Id, ConstraintSet.Right);
121 | set.Connect(_platformView.Id, ConstraintSet.Top, _top.Id, ConstraintSet.Bottom);
122 | set.Connect(_platformView.Id, ConstraintSet.Bottom, _bottom.Id, ConstraintSet.Top);
123 | set.Connect(_platformView.Id, ConstraintSet.Right, _right.Id, ConstraintSet.Left);
124 |
125 | set.ApplyTo(this);
126 | }
127 |
128 | private void Page_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
129 | {
130 | if (e.PropertyName == Page.BackgroundColorProperty.PropertyName)
131 | {
132 | var color = _page.BackgroundColor.ToPlatform();
133 |
134 | _top!.SetBackgroundColor(color);
135 | _bottom!.SetBackgroundColor(color);
136 | _left!.SetBackgroundColor(color);
137 | _right!.SetBackgroundColor(color);
138 | }
139 | else if (e.PropertyName == Page.OpacityProperty.PropertyName)
140 | {
141 | var alpha = (float)_page.Opacity;
142 |
143 | _top!.Alpha = alpha;
144 | _bottom!.Alpha = alpha;
145 | _left!.Alpha = alpha;
146 | _right!.Alpha = alpha;
147 | }
148 | }
149 |
150 | public void OnGlobalLayout()
151 | {
152 | Android.Graphics.Rect insets;
153 | if (OperatingSystem.IsAndroidVersionAtLeast(30))
154 | {
155 | var ins = _decorView.RootWindowInsets!.GetInsetsIgnoringVisibility(Android.Views.WindowInsets.Type.SystemBars());
156 | insets = new Android.Graphics.Rect(ins.Left, ins.Top, ins.Right, ins.Bottom);
157 | }
158 | else
159 | {
160 | var ins = _decorView.RootWindowInsets!;
161 | insets = new Android.Graphics.Rect(ins.StableInsetLeft, ins.StableInsetTop, ins.StableInsetRight, ins.StableInsetBottom);
162 | }
163 |
164 | if (_prevInsets!.Top == insets.Top && _prevInsets.Bottom == insets.Bottom
165 | && _prevInsets.Left == insets.Left && _prevInsets.Right == insets.Right) return;
166 |
167 | _prevInsets = insets;
168 |
169 | var topParams = _top!.LayoutParameters!;
170 | topParams.Height = insets.Top;
171 | _top.LayoutParameters = topParams;
172 |
173 | var bottomParams = _bottom!.LayoutParameters!;
174 | bottomParams.Height = insets.Bottom;
175 | _bottom.LayoutParameters = bottomParams;
176 |
177 | var leftParams = _left!.LayoutParameters!;
178 | leftParams.Width = insets.Left;
179 | _left.LayoutParameters = leftParams;
180 |
181 | var rightParams = _right!.LayoutParameters!;
182 | rightParams.Width = insets.Right;
183 | _right.LayoutParameters = rightParams;
184 |
185 | ToggleViewVisibility(_top, insets.Top);
186 | ToggleViewVisibility(_right, insets.Right);
187 | ToggleViewVisibility(_bottom, insets.Bottom);
188 | ToggleViewVisibility(_left, insets.Left);
189 | }
190 |
191 | private static void ToggleViewVisibility(View view, int size)
192 | {
193 | view.Visibility = size > 0 ? ViewStates.Visible : ViewStates.Gone;
194 | }
195 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # globs
2 | Makefile.in
3 | *.userprefs
4 | *.usertasks
5 | config.make
6 | config.status
7 | aclocal.m4
8 | install-sh
9 | autom4te.cache/
10 | *.tar.gz
11 | tarballs/
12 | test-results/
13 |
14 | # Mac bundle stuff
15 | *.dmg
16 | *.app
17 |
18 | # content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
19 | # General
20 | .DS_Store
21 | .AppleDouble
22 | .LSOverride
23 |
24 | # Icon must end with two \r
25 | Icon
26 |
27 |
28 | # Thumbnails
29 | ._*
30 |
31 | # Files that might appear in the root of a volume
32 | .DocumentRevisions-V100
33 | .fseventsd
34 | .Spotlight-V100
35 | .TemporaryItems
36 | .Trashes
37 | .VolumeIcon.icns
38 | .com.apple.timemachine.donotpresent
39 |
40 | # Directories potentially created on remote AFP share
41 | .AppleDB
42 | .AppleDesktop
43 | Network Trash Folder
44 | Temporary Items
45 | .apdisk
46 |
47 | # content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
48 | # Windows thumbnail cache files
49 | Thumbs.db
50 | ehthumbs.db
51 | ehthumbs_vista.db
52 |
53 | # Dump file
54 | *.stackdump
55 |
56 | # Folder config file
57 | [Dd]esktop.ini
58 |
59 | # Recycle Bin used on file shares
60 | $RECYCLE.BIN/
61 |
62 | # Windows Installer files
63 | *.cab
64 | *.msi
65 | *.msix
66 | *.msm
67 | *.msp
68 |
69 | # Windows shortcuts
70 | *.lnk
71 |
72 | # content below from: https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
73 | ## Ignore Visual Studio temporary files, build results, and
74 | ## files generated by popular Visual Studio add-ons.
75 | ##
76 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
77 |
78 | # User-specific files
79 | *.suo
80 | *.user
81 | *.userosscache
82 | *.sln.docstates
83 |
84 | # User-specific files (MonoDevelop/Xamarin Studio)
85 | *.userprefs
86 |
87 | # Build results
88 | [Dd]ebug/
89 | [Dd]ebugPublic/
90 | [Rr]elease/
91 | [Rr]eleases/
92 | x64/
93 | x86/
94 | bld/
95 | [Bb]in/
96 | [Oo]bj/
97 | [Ll]og/
98 |
99 | # Visual Studio 2015/2017 cache/options directory
100 | .vs/
101 | # Uncomment if you have tasks that create the project's static files in wwwroot
102 | #wwwroot/
103 |
104 | # Visual Studio 2017 auto generated files
105 | Generated\ Files/
106 |
107 | # MSTest test Results
108 | [Tt]est[Rr]esult*/
109 | [Bb]uild[Ll]og.*
110 |
111 | # NUNIT
112 | *.VisualState.xml
113 | TestResult.xml
114 |
115 | # Build Results of an ATL Project
116 | [Dd]ebugPS/
117 | [Rr]eleasePS/
118 | dlldata.c
119 |
120 | # Benchmark Results
121 | BenchmarkDotNet.Artifacts/
122 |
123 | # .NET Core
124 | project.lock.json
125 | project.fragment.lock.json
126 | artifacts/
127 |
128 | # StyleCop
129 | StyleCopReport.xml
130 |
131 | # Files built by Visual Studio
132 | *_i.c
133 | *_p.c
134 | *_h.h
135 | *.ilk
136 | *.meta
137 | *.obj
138 | *.iobj
139 | *.pch
140 | *.pdb
141 | *.ipdb
142 | *.pgc
143 | *.pgd
144 | *.rsp
145 | *.sbr
146 | *.tlb
147 | *.tli
148 | *.tlh
149 | *.tmp
150 | *.tmp_proj
151 | *_wpftmp.csproj
152 | *.log
153 | *.vspscc
154 | *.vssscc
155 | .builds
156 | *.pidb
157 | *.svclog
158 | *.scc
159 |
160 | # Chutzpah Test files
161 | _Chutzpah*
162 |
163 | # Visual C++ cache files
164 | ipch/
165 | *.aps
166 | *.ncb
167 | *.opendb
168 | *.opensdf
169 | *.sdf
170 | *.cachefile
171 | *.VC.db
172 | *.VC.VC.opendb
173 |
174 | # Visual Studio profiler
175 | *.psess
176 | *.vsp
177 | *.vspx
178 | *.sap
179 |
180 | # Visual Studio Trace Files
181 | *.e2e
182 |
183 | # TFS 2012 Local Workspace
184 | $tf/
185 |
186 | # Guidance Automation Toolkit
187 | *.gpState
188 |
189 | # ReSharper is a .NET coding add-in
190 | _ReSharper*/
191 | *.[Rr]e[Ss]harper
192 | *.DotSettings.user
193 |
194 | # JustCode is a .NET coding add-in
195 | .JustCode
196 |
197 | # TeamCity is a build add-in
198 | _TeamCity*
199 |
200 | # DotCover is a Code Coverage Tool
201 | *.dotCover
202 |
203 | # AxoCover is a Code Coverage Tool
204 | .axoCover/*
205 | !.axoCover/settings.json
206 |
207 | # Visual Studio code coverage results
208 | *.coverage
209 | *.coveragexml
210 |
211 | # NCrunch
212 | _NCrunch_*
213 | .*crunch*.local.xml
214 | nCrunchTemp_*
215 |
216 | # MightyMoose
217 | *.mm.*
218 | AutoTest.Net/
219 |
220 | # Web workbench (sass)
221 | .sass-cache/
222 |
223 | # Installshield output folder
224 | [Ee]xpress/
225 |
226 | # DocProject is a documentation generator add-in
227 | DocProject/buildhelp/
228 | DocProject/Help/*.HxT
229 | DocProject/Help/*.HxC
230 | DocProject/Help/*.hhc
231 | DocProject/Help/*.hhk
232 | DocProject/Help/*.hhp
233 | DocProject/Help/Html2
234 | DocProject/Help/html
235 |
236 | # Click-Once directory
237 | publish/
238 |
239 | # Publish Web Output
240 | *.[Pp]ublish.xml
241 | *.azurePubxml
242 | # Note: Comment the next line if you want to checkin your web deploy settings,
243 | # but database connection strings (with potential passwords) will be unencrypted
244 | *.pubxml
245 | *.publishproj
246 |
247 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
248 | # checkin your Azure Web App publish settings, but sensitive information contained
249 | # in these scripts will be unencrypted
250 | PublishScripts/
251 |
252 | # NuGet Packages
253 | *.nupkg
254 | # The packages folder can be ignored because of Package Restore
255 | **/[Pp]ackages/*
256 | # except build/, which is used as an MSBuild target.
257 | !**/[Pp]ackages/build/
258 | # Uncomment if necessary however generally it will be regenerated when needed
259 | #!**/[Pp]ackages/repositories.config
260 | # NuGet v3's project.json files produces more ignorable files
261 | *.nuget.props
262 | *.nuget.targets
263 |
264 | # Microsoft Azure Build Output
265 | csx/
266 | *.build.csdef
267 |
268 | # Microsoft Azure Emulator
269 | ecf/
270 | rcf/
271 |
272 | # Windows Store app package directories and files
273 | AppPackages/
274 | BundleArtifacts/
275 | Package.StoreAssociation.xml
276 | _pkginfo.txt
277 | *.appx
278 |
279 | # Visual Studio cache files
280 | # files ending in .cache can be ignored
281 | *.[Cc]ache
282 | # but keep track of directories ending in .cache
283 | !*.[Cc]ache/
284 |
285 | # Others
286 | ClientBin/
287 | ~$*
288 | *~
289 | *.dbmdl
290 | *.dbproj.schemaview
291 | *.jfm
292 | *.pfx
293 | *.publishsettings
294 | orleans.codegen.cs
295 |
296 | # Including strong name files can present a security risk
297 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
298 | #*.snk
299 |
300 | # Since there are multiple workflows, uncomment next line to ignore bower_components
301 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
302 | #bower_components/
303 |
304 | # RIA/Silverlight projects
305 | Generated_Code/
306 |
307 | # Backup & report files from converting an old project file
308 | # to a newer Visual Studio version. Backup files are not needed,
309 | # because we have git ;-)
310 | _UpgradeReport_Files/
311 | Backup*/
312 | UpgradeLog*.XML
313 | UpgradeLog*.htm
314 | ServiceFabricBackup/
315 | *.rptproj.bak
316 |
317 | # SQL Server files
318 | *.mdf
319 | *.ldf
320 | *.ndf
321 |
322 | # Business Intelligence projects
323 | *.rdl.data
324 | *.bim.layout
325 | *.bim_*.settings
326 | *.rptproj.rsuser
327 |
328 | # Microsoft Fakes
329 | FakesAssemblies/
330 |
331 | # GhostDoc plugin setting file
332 | *.GhostDoc.xml
333 |
334 | # Node.js Tools for Visual Studio
335 | .ntvs_analysis.dat
336 | node_modules/
337 |
338 | # Visual Studio 6 build log
339 | *.plg
340 |
341 | # Visual Studio 6 workspace options file
342 | *.opt
343 |
344 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
345 | *.vbw
346 |
347 | # Visual Studio LightSwitch build output
348 | **/*.HTMLClient/GeneratedArtifacts
349 | **/*.DesktopClient/GeneratedArtifacts
350 | **/*.DesktopClient/ModelManifest.xml
351 | **/*.Server/GeneratedArtifacts
352 | **/*.Server/ModelManifest.xml
353 | _Pvt_Extensions
354 |
355 | # Paket dependency manager
356 | .paket/paket.exe
357 | paket-files/
358 |
359 | # FAKE - F# Make
360 | .fake/
361 |
362 | # JetBrains Rider
363 | .idea/
364 | *.sln.iml
365 |
366 | # CodeRush personal settings
367 | .cr/personal
368 |
369 | # Python Tools for Visual Studio (PTVS)
370 | __pycache__/
371 | *.pyc
372 |
373 | # Cake - Uncomment if you are using it
374 | # tools/**
375 | # !tools/packages.config
376 |
377 | # Tabs Studio
378 | *.tss
379 |
380 | # Telerik's JustMock configuration file
381 | *.jmconfig
382 |
383 | # BizTalk build output
384 | *.btp.cs
385 | *.btm.cs
386 | *.odx.cs
387 | *.xsd.cs
388 |
389 | # OpenCover UI analysis results
390 | OpenCover/
391 |
392 | # Azure Stream Analytics local run output
393 | ASALocalRun/
394 |
395 | # MSBuild Binary and Structured Log
396 | *.binlog
397 |
398 | # NVidia Nsight GPU debugger configuration file
399 | *.nvuser
400 |
401 | # MFractors (Xamarin productivity tool) working folder
402 | .mfractor/
403 |
404 | # Local History for Visual Studio
405 | .localhistory/
--------------------------------------------------------------------------------
/MPowerKit.Popups/Platforms/Android/PopupService.cs:
--------------------------------------------------------------------------------
1 | using Android.App;
2 | using Android.Views;
3 |
4 | using Microsoft.Maui.Platform;
5 |
6 | using static Android.Views.View;
7 |
8 | using FragmentManager = AndroidX.Fragment.App.FragmentManager;
9 | using View = Android.Views.View;
10 | using ViewGroup = Android.Views.ViewGroup;
11 | using Window = Microsoft.Maui.Controls.Window;
12 |
13 | namespace MPowerKit.Popups;
14 |
15 | public partial class PopupService
16 | {
17 | protected FragmentLifecycleCallback? Observer { get; set; }
18 | private WeakReference? _fmReference;
19 |
20 | protected FragmentManager? FragmentManager
21 | {
22 | get
23 | {
24 | if (_fmReference is null) return null;
25 |
26 | _fmReference.TryGetTarget(out var fm);
27 | return fm;
28 | }
29 | set
30 | {
31 | var oldFm = FragmentManager;
32 |
33 | if (value == oldFm) return;
34 |
35 | if (value != oldFm && oldFm != null)
36 | {
37 | oldFm.UnregisterFragmentLifecycleCallbacks(Observer!);
38 | Observer = null;
39 | }
40 |
41 | if (value is null)
42 | {
43 | _fmReference = null;
44 | return;
45 | }
46 |
47 | _fmReference = new(value);
48 | var observer = new FragmentLifecycleCallback(() => value);
49 | Observer = observer;
50 | value.RegisterFragmentLifecycleCallbacks(observer, true);
51 | }
52 | }
53 |
54 | protected virtual partial void AttachToWindow(PopupPage page, IViewHandler pageHandler, Window parentWindow)
55 | {
56 | HandleAccessibility(true, page, parentWindow.Page);
57 |
58 | var activity = parentWindow.Handler.PlatformView as Activity
59 | ?? throw new InvalidOperationException("Activity not found");
60 |
61 | var dv = activity.Window?.DecorView as ViewGroup
62 | ?? throw new InvalidOperationException("DecorView of Activity not found");
63 |
64 | var handler = (pageHandler as IPlatformViewHandler)!;
65 |
66 | var pv = handler.PlatformView!;
67 |
68 | void attachedHandler(object? s, ViewAttachedToWindowEventArgs e)
69 | {
70 | dv.Context!.HideKeyboard(dv);
71 | }
72 | pv.ViewAttachedToWindow += attachedHandler;
73 |
74 | void detachedHandler(object? s, ViewDetachedFromWindowEventArgs e)
75 | {
76 | dv.Context!.HideKeyboard(dv);
77 | }
78 | pv.ViewDetachedFromWindow += detachedHandler;
79 |
80 | var keyboardListener = new KeyboardListener(dv);
81 | pv.ViewTreeObserver?.AddOnGlobalLayoutListener(keyboardListener);
82 |
83 | void touchHandler(object? s, View.TouchEventArgs e)
84 | {
85 | var view = (s as ViewGroup)!;
86 |
87 | if (page.Content is not null && view.ChildCount > 0)
88 | {
89 | var child = view.GetChildAt(0)!;
90 |
91 | var rawX = e.Event!.GetX();
92 | var rawY = e.Event.GetY();
93 | var childX = child.GetX();
94 | var childY = child.GetY();
95 |
96 | if (rawX >= childX && rawX <= (child.Width + childX)
97 | && rawY >= childY && rawY <= (child.Height + childY))
98 | {
99 | if (keyboardListener.KeyboardVisible)
100 | {
101 | view.Context!.HideKeyboard(view);
102 | view.FindFocus()?.ClearFocus();
103 | }
104 |
105 | e.Handled = true;
106 | return;
107 | }
108 | }
109 |
110 | if (e.Event!.Action is MotionEventActions.Down)
111 | {
112 | if (!page.BackgroundInputTransparent && keyboardListener.KeyboardVisible)
113 | {
114 | view.Context!.HideKeyboard(view);
115 | view.FindFocus()?.ClearFocus();
116 | e.Handled = true;
117 | return;
118 | }
119 | }
120 |
121 | if (e.Event!.Action is MotionEventActions.Up)
122 | {
123 | page.SendBackgroundClick();
124 | }
125 |
126 | e.Handled = !page.BackgroundInputTransparent;
127 | }
128 | pv.Touch += touchHandler;
129 |
130 | var action = new DisposableAction(() =>
131 | {
132 | pv.ViewAttachedToWindow -= attachedHandler;
133 | pv.ViewDetachedFromWindow -= detachedHandler;
134 | pv.Touch -= touchHandler;
135 |
136 | if (pv.ViewTreeObserver?.IsAlive is true)
137 | pv.ViewTreeObserver.RemoveOnGlobalLayoutListener(keyboardListener);
138 | });
139 | page.SetValue(DisposableActionAttached.DisposableActionProperty, action);
140 |
141 | AddToVisualTree(page, handler, dv, 10000);
142 | }
143 |
144 | protected virtual void AddToVisualTree(PopupPage page, IPlatformViewHandler handler, ViewGroup decorView, float elevation)
145 | {
146 | var view = !page.HasSystemPadding
147 | ? handler.PlatformView!
148 | : new ParentLayout(decorView.Context!, decorView, page);
149 | view.Elevation = elevation;
150 |
151 | #if NET9_0_OR_GREATER
152 | FragmentManager = AndroidExtensions.GetFragmentManager(handler.MauiContext!);
153 | Observer!.AttachView(view);
154 | #else
155 | decorView.AddView(view);
156 | #endif
157 | }
158 |
159 | protected virtual partial void DetachFromWindow(PopupPage page, IViewHandler pageHandler, Window parentWindow)
160 | {
161 | var handler = (pageHandler as IPlatformViewHandler)!;
162 |
163 | HandleAccessibility(false, page, parentWindow.Page);
164 |
165 | var action = page.GetValue(DisposableActionAttached.DisposableActionProperty) as DisposableAction;
166 | action?.Dispose();
167 |
168 | RemoveFromVisualTree(page, handler);
169 | }
170 |
171 | protected virtual void RemoveFromVisualTree(PopupPage page, IPlatformViewHandler handler)
172 | {
173 | View view;
174 | if (page.HasSystemPadding)
175 | {
176 | var layout = (handler.PlatformView!.Parent as ParentLayout)!;
177 | layout.RemoveGlobalLayoutListener();
178 |
179 | view = layout;
180 | }
181 | else
182 | {
183 | view = handler.PlatformView!;
184 | }
185 | #if NET9_0_OR_GREATER
186 | Observer?.DetachView(view);
187 | #else
188 | view.RemoveFromParent();
189 | #endif
190 | }
191 |
192 | //! important keeps reference to pages that accessibility has applied to. This is so accessibility can be removed properly when popup is removed. #https://github.com/LuckyDucko/Mopups/issues/93
193 | protected Dictionary> AccessibilityViews = [];
194 | protected virtual void HandleAccessibility(bool showPopup, PopupPage popupPage, Page? mainPage)
195 | {
196 | if (!popupPage.EnableAndroidAccessibilityHandling || mainPage is null) return;
197 |
198 | if (showPopup)
199 | {
200 | Dictionary accessViews = [];
201 |
202 | // store previous accessibility settings
203 | if (mainPage.Handler?.PlatformView is ViewGroup pageView
204 | && pageView.ImportantForAccessibility is not ImportantForAccessibility.NoHideDescendants)
205 | {
206 | accessViews[pageView] = (pageView.ImportantForAccessibility, pageView.DescendantFocusability);
207 | }
208 |
209 | if (mainPage.Navigation.NavigationStack.Count > 0
210 | && mainPage.Navigation?.NavigationStack[^1]?.Handler?.PlatformView is ViewGroup navStackView
211 | && navStackView.ImportantForAccessibility is not ImportantForAccessibility.NoHideDescendants)
212 | {
213 | accessViews[navStackView] = (navStackView.ImportantForAccessibility, navStackView.DescendantFocusability);
214 | }
215 |
216 | if (mainPage.Navigation!.ModalStack.Count > 0
217 | && mainPage.Navigation?.ModalStack[^1]?.Handler?.PlatformView is ViewGroup modalStackView
218 | && modalStackView.ImportantForAccessibility is not ImportantForAccessibility.NoHideDescendants)
219 | {
220 | accessViews[modalStackView] = (modalStackView.ImportantForAccessibility, modalStackView.DescendantFocusability);
221 | }
222 |
223 | if (accessViews.Count > 0)
224 | {
225 | AccessibilityViews[popupPage] = accessViews;
226 | }
227 | }
228 |
229 | if (AccessibilityViews.TryGetValue(popupPage, out var views))
230 | {
231 | foreach (var view in views)
232 | {
233 | if (showPopup)
234 | {
235 | view.Key.ImportantForAccessibility = ImportantForAccessibility.NoHideDescendants;
236 | view.Key.DescendantFocusability = DescendantFocusability.BlockDescendants;
237 | view.Key.ClearFocus();
238 | }
239 | else
240 | {
241 | view.Key.ImportantForAccessibility = view.Value.Item1;
242 | view.Key.DescendantFocusability = view.Value.Item2;
243 | }
244 | }
245 |
246 | if (!showPopup)
247 | {
248 | AccessibilityViews.Remove(popupPage);
249 | }
250 | }
251 | }
252 | }
--------------------------------------------------------------------------------
/MPowerKit.Popups/PopupPage.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 | using System.Windows.Input;
3 |
4 | using MPowerKit.Popups.Animations;
5 | using MPowerKit.Popups.Interfaces;
6 |
7 | namespace MPowerKit.Popups;
8 |
9 | public static class AnimationHelper
10 | {
11 | public static bool SystemAnimationsEnabled
12 | {
13 | get
14 | {
15 | #if ANDROID
16 | if (OperatingSystem.IsAndroidVersionAtLeast(26))
17 | {
18 | return Android.Animation.ValueAnimator.AreAnimatorsEnabled();
19 | }
20 | #endif
21 |
22 | return true;
23 | }
24 | }
25 | }
26 |
27 | public class RoutedEventArgs : EventArgs
28 | {
29 | public bool Handled { get; set; }
30 | }
31 |
32 | public class PopupPage : ContentPage
33 | {
34 | public event EventHandler? BackgroundClicked;
35 |
36 | public bool IsClosing { get; set; }
37 |
38 | public PopupPage()
39 | {
40 | App_Dict_ValuesChanged(null, EventArgs.Empty);
41 |
42 | var dict = Application.Current!.Resources as Microsoft.Maui.Controls.Internals.IResourceDictionary;
43 | dict.ValuesChanged += App_Dict_ValuesChanged;
44 |
45 | BackgroundColor = Color.FromArgb("#50000000");
46 |
47 | ToggleSystemPadding();
48 |
49 | this.Unloaded += PopupPage_Unloaded;
50 | }
51 |
52 | protected virtual void PopupPage_Unloaded(object? sender, EventArgs e)
53 | {
54 | var dict = Application.Current!.Resources as Microsoft.Maui.Controls.Internals.IResourceDictionary;
55 | dict.ValuesChanged -= App_Dict_ValuesChanged;
56 |
57 | Resources.MergedDictionaries.Clear();
58 | }
59 |
60 | protected virtual void App_Dict_ValuesChanged(object? sender, EventArgs e)
61 | {
62 | foreach (var dictionary in Application.Current!.Resources.MergedDictionaries)
63 | {
64 | if (this.Resources.MergedDictionaries.Contains(dictionary)) continue;
65 |
66 | this.Resources.MergedDictionaries.Add(dictionary);
67 | }
68 | }
69 |
70 | protected override void OnPropertyChanged([CallerMemberName] string? propertyName = null)
71 | {
72 | base.OnPropertyChanged(propertyName);
73 |
74 | if (propertyName == HasSystemPaddingProperty.PropertyName)
75 | {
76 | ToggleSystemPadding();
77 | }
78 | }
79 |
80 | protected virtual void ToggleSystemPadding()
81 | {
82 | #if NET10_0_OR_GREATER
83 | #if IOS
84 | SafeAreaEdges = HasSystemPadding
85 | ? new(SafeAreaRegions.Container)
86 | : SafeAreaEdges.None;
87 | #endif
88 | #else
89 | Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific.Page.SetUseSafeArea(this, HasSystemPadding);
90 | #endif
91 | }
92 |
93 | protected override bool OnBackButtonPressed()
94 | {
95 | return false;
96 | }
97 |
98 | public virtual void PreparingAnimation()
99 | {
100 | if (!IsAnimationEnabled) return;
101 |
102 | Animation?.Preparing(InternalChildren.OfType().FirstOrDefault(), this);
103 | }
104 |
105 | public virtual void DisposingAnimation()
106 | {
107 | if (!IsAnimationEnabled) return;
108 |
109 | Animation?.Disposing(InternalChildren.OfType().FirstOrDefault(), this);
110 | }
111 |
112 | public virtual async Task AppearingAnimation()
113 | {
114 | OnAppearingAnimationBegin();
115 | await OnAppearingAnimationBeginAsync();
116 |
117 | if (IsAnimationEnabled && Animation is not null)
118 | {
119 | await Animation.Appearing(InternalChildren.OfType().FirstOrDefault(), this);
120 | }
121 |
122 | OnAppearingAnimationEnd();
123 | await OnAppearingAnimationEndAsync();
124 | }
125 |
126 | public virtual async Task DisappearingAnimation()
127 | {
128 | OnDisappearingAnimationBegin();
129 | await OnDisappearingAnimationBeginAsync();
130 |
131 | if (IsAnimationEnabled && Animation is not null)
132 | {
133 | await Animation.Disappearing(InternalChildren.OfType().FirstOrDefault(), this);
134 | }
135 |
136 | OnDisappearingAnimationEnd();
137 | await OnDisappearingAnimationEndAsync();
138 | }
139 |
140 | protected virtual void OnAppearingAnimationBegin()
141 | {
142 | }
143 |
144 | protected virtual void OnAppearingAnimationEnd()
145 | {
146 | }
147 |
148 | protected virtual void OnDisappearingAnimationBegin()
149 | {
150 | }
151 |
152 | protected virtual void OnDisappearingAnimationEnd()
153 | {
154 | }
155 |
156 | protected virtual Task OnAppearingAnimationBeginAsync()
157 | {
158 | return Task.CompletedTask;
159 | }
160 |
161 | protected virtual Task OnAppearingAnimationEndAsync()
162 | {
163 | return Task.CompletedTask;
164 | }
165 |
166 | protected virtual Task OnDisappearingAnimationBeginAsync()
167 | {
168 | return Task.CompletedTask;
169 | }
170 |
171 | protected virtual Task OnDisappearingAnimationEndAsync()
172 | {
173 | return Task.CompletedTask;
174 | }
175 |
176 | public virtual void OnBackgroundClicked()
177 | {
178 |
179 | }
180 |
181 | public virtual void SendBackgroundClick()
182 | {
183 | OnBackgroundClicked();
184 |
185 | BackgroundClicked?.Invoke(this, new RoutedEventArgs());
186 |
187 | if (BackgroundClickedCommand?.CanExecute(BackgroundClickedCommandParameter) is true)
188 | {
189 | BackgroundClickedCommand.Execute(BackgroundClickedCommandParameter);
190 | }
191 | }
192 |
193 | #region IsAnimationEnabled
194 | public bool IsAnimationEnabled
195 | {
196 | get { return (bool)GetValue(IsAnimationEnabledProperty); }
197 | set { SetValue(IsAnimationEnabledProperty, value); }
198 | }
199 |
200 | public static readonly BindableProperty IsAnimationEnabledProperty =
201 | BindableProperty.Create(
202 | nameof(IsAnimationEnabled),
203 | typeof(bool),
204 | typeof(PopupPage),
205 | true);
206 | #endregion
207 |
208 | #region HasSystemPadding
209 | public bool HasSystemPadding
210 | {
211 | get { return (bool)GetValue(HasSystemPaddingProperty); }
212 | set { SetValue(HasSystemPaddingProperty, value); }
213 | }
214 |
215 | public static readonly BindableProperty HasSystemPaddingProperty =
216 | BindableProperty.Create(
217 | nameof(HasSystemPadding),
218 | typeof(bool),
219 | typeof(PopupPage),
220 | true
221 | );
222 | #endregion
223 |
224 | #region Animation
225 | public IPopupAnimation Animation
226 | {
227 | get { return (IPopupAnimation)GetValue(AnimationProperty); }
228 | set { SetValue(AnimationProperty, value); }
229 | }
230 |
231 | public static readonly BindableProperty AnimationProperty =
232 | BindableProperty.Create(
233 | nameof(Animation),
234 | typeof(IPopupAnimation),
235 | typeof(PopupPage),
236 | new ScaleAnimation()
237 | );
238 | #endregion
239 |
240 | #region CloseOnBackgroundClick
241 | public bool CloseOnBackgroundClick
242 | {
243 | get { return (bool)GetValue(CloseOnBackgroundClickProperty); }
244 | set { SetValue(CloseOnBackgroundClickProperty, value); }
245 | }
246 |
247 | public static readonly BindableProperty CloseOnBackgroundClickProperty =
248 | BindableProperty.Create(
249 | nameof(CloseOnBackgroundClick),
250 | typeof(bool),
251 | typeof(PopupPage)
252 | );
253 | #endregion
254 |
255 | #region BackgroundInputTransparent
256 | public bool BackgroundInputTransparent
257 | {
258 | get { return (bool)GetValue(BackgroundInputTransparentProperty); }
259 | set { SetValue(BackgroundInputTransparentProperty, value); }
260 | }
261 |
262 | public static readonly BindableProperty BackgroundInputTransparentProperty =
263 | BindableProperty.Create(
264 | nameof(BackgroundInputTransparent),
265 | typeof(bool),
266 | typeof(PopupPage)
267 | );
268 | #endregion
269 |
270 | #region BackgroundClickedCommand
271 | public ICommand BackgroundClickedCommand
272 | {
273 | get { return (ICommand)GetValue(BackgroundClickedCommandProperty); }
274 | set { SetValue(BackgroundClickedCommandProperty, value); }
275 | }
276 |
277 | public static readonly BindableProperty BackgroundClickedCommandProperty =
278 | BindableProperty.Create(
279 | nameof(BackgroundClickedCommand),
280 | typeof(ICommand),
281 | typeof(PopupPage)
282 | );
283 | #endregion
284 |
285 | #region BackgroundClickedCommandParameter
286 | public object BackgroundClickedCommandParameter
287 | {
288 | get { return GetValue(BackgroundClickedCommandParameterProperty); }
289 | set { SetValue(BackgroundClickedCommandParameterProperty, value); }
290 | }
291 |
292 | public static readonly BindableProperty BackgroundClickedCommandParameterProperty =
293 | BindableProperty.Create(
294 | nameof(BackgroundClickedCommandParameter),
295 | typeof(object),
296 | typeof(PopupPage)
297 | );
298 | #endregion
299 |
300 | #region EnableAndroidAccessibilityHandling
301 | public bool EnableAndroidAccessibilityHandling
302 | {
303 | get { return (bool)GetValue(EnableAndroidAccessibilityHandlingProperty); }
304 | set { SetValue(EnableAndroidAccessibilityHandlingProperty, value); }
305 | }
306 |
307 | public static readonly BindableProperty EnableAndroidAccessibilityHandlingProperty =
308 | BindableProperty.Create(
309 | nameof(EnableAndroidAccessibilityHandling),
310 | typeof(bool),
311 | typeof(PopupPage)
312 | );
313 | #endregion
314 | }
--------------------------------------------------------------------------------
/Sample/Resources/Styles/Styles.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
10 |
11 |
15 |
16 |
21 |
22 |
25 |
26 |
51 |
52 |
69 |
70 |
90 |
91 |
112 |
113 |
134 |
135 |
140 |
141 |
162 |
163 |
181 |
182 |
185 |
186 |
192 |
193 |
199 |
200 |
204 |
205 |
227 |
228 |
243 |
244 |
264 |
265 |
268 |
269 |
292 |
293 |
313 |
314 |
320 |
321 |
340 |
341 |
344 |
345 |
373 |
374 |
394 |
395 |
399 |
400 |
412 |
413 |
418 |
419 |
425 |
426 |
427 |
--------------------------------------------------------------------------------