Browse Source

Add an event so that users can detect when an Application icon is clicked (#14106)

* Add an event so that users can detect when an Application icon is clicked.

* refactor to use Lifetime apis.

* use ActivationKind instead of reason to be consistent with other xaml platforms

* implement macos raise url events.

* add docs.

* add apis to programatically Activate and Deactivate the application.

This allows the dock icon to be kept in sync so its menu options there say "Hide" / "Show" correctly.

* fix api naming.

* Add Browser IActivatableApplicationLifetime impl

* Implement IActivatableApplicationLifetime on Android

* Add IActivatableApplicationLifetime iOS implementation

* Adjust android impl a little

---------

Co-authored-by: Max Katz <maxkatz6@outlook.com>
#Conflicts:
#	src/Browser/Avalonia.Browser/Interop/InputHelper.cs
#	src/Browser/Avalonia.Browser/webapp/modules/avalonia/input.ts
release/11.0.7
Dan Walmsley 2 years ago
committed by Max Katz
parent
commit
8bd49413f7
  1. 23
      native/Avalonia.Native/src/OSX/app.mm
  2. 1
      native/Avalonia.Native/src/OSX/common.h
  3. 7
      samples/ControlCatalog.Android/MainActivity.cs
  4. 8
      samples/ControlCatalog/App.xaml.cs
  5. 2
      src/Android/Avalonia.Android/AvaloniaMainActivity.App.cs
  6. 40
      src/Android/Avalonia.Android/AvaloniaMainActivity.cs
  7. 1
      src/Android/Avalonia.Android/IAndroidNavigationService.cs
  8. 10
      src/Android/Avalonia.Android/IAvaloniaActivity.cs
  9. 23
      src/Android/Avalonia.Android/SingleViewLifetime.cs
  10. 23
      src/Avalonia.Controls/ApplicationLifetimes/ActivatedEventArgs.cs
  11. 25
      src/Avalonia.Controls/ApplicationLifetimes/ActivationKind.cs
  12. 13
      src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
  13. 35
      src/Avalonia.Controls/ApplicationLifetimes/IActivatableApplicationLifetime.cs
  14. 13
      src/Avalonia.Controls/ApplicationLifetimes/ProtocolActivatedEventArgs.cs
  15. 1
      src/Avalonia.Controls/Platform/INativeApplicationCommands.cs
  16. 32
      src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs
  17. 4
      src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs
  18. 50
      src/Avalonia.Native/MacOSClassicDesktopStyleApplicationLifetime.cs
  19. 6
      src/Avalonia.Native/MacOSNativeMenuCommands.cs
  20. 4
      src/Avalonia.Native/avn.idl
  21. 3
      src/Browser/Avalonia.Browser/BrowserAppBuilder.cs
  22. 19
      src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs
  23. 9
      src/Browser/Avalonia.Browser/Interop/InputHelper.cs
  24. 26
      src/Browser/Avalonia.Browser/webapp/modules/avalonia/input.ts
  25. 58
      src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs
  26. 27
      src/iOS/Avalonia.iOS/SingleViewLifetime.cs

23
native/Avalonia.Native/src/OSX/app.mm

@ -43,6 +43,22 @@ ComPtr<IAvnApplicationEvents> _events;
[[NSRunningApplication currentApplication] activateWithOptions:NSApplicationActivateIgnoringOtherApps];
}
-(BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag
{
_events->OnReopen();
return YES;
}
- (void)applicationDidHide:(NSNotification *)notification
{
_events->OnHide();
}
- (void)applicationDidUnhide:(NSNotification *)notification
{
_events->OnUnhide();
}
- (void)application:(NSApplication *)sender openFiles:(NSArray<NSString *> *)filenames
{
auto array = CreateAvnStringArray(filenames);
@ -123,6 +139,13 @@ extern void ReleaseAvnAppEvents()
}
}
HRESULT AvnApplicationCommands::UnhideApp()
{
START_COM_CALL;
[[NSApplication sharedApplication] unhide:[NSApp delegate]];
return S_OK;
}
HRESULT AvnApplicationCommands::HideApp()
{
START_COM_CALL;

1
native/Avalonia.Native/src/OSX/common.h

@ -100,6 +100,7 @@ class AvnApplicationCommands : public ComSingleObject<IAvnApplicationCommands, &
public:
FORWARD_IUNKNOWN()
virtual HRESULT UnhideApp() override;
virtual HRESULT HideApp() override;
virtual HRESULT ShowAll() override;
virtual HRESULT HideOthers() override;

7
samples/ControlCatalog.Android/MainActivity.cs

@ -2,13 +2,16 @@
using Android.Content.PM;
using Avalonia;
using Avalonia.Android;
using static Android.Content.Intent;
namespace ControlCatalog.Android
{
[Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", MainLauncher = true, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)]
[Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", MainLauncher = true, Exported = true, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)]
// IntentFilter are here to test IActivatableApplicationLifetime
[IntentFilter(new [] { ActionView }, Categories = new [] { CategoryDefault, CategoryBrowsable }, DataScheme = "avln" )]
public class MainActivity : AvaloniaMainActivity<App>
{
protected override Avalonia.AppBuilder CustomizeAppBuilder(Avalonia.AppBuilder builder)
protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
{
return base.CustomizeAppBuilder(builder)
.AfterSetup(_ =>

8
samples/ControlCatalog/App.xaml.cs

@ -51,6 +51,14 @@ namespace ControlCatalog
singleViewLifetime.MainView = new MainView { DataContext = new MainWindowViewModel() };
}
if (ApplicationLifetime is IActivatableApplicationLifetime activatableApplicationLifetime)
{
activatableApplicationLifetime.Activated += (sender, args) =>
Console.WriteLine($"App activated: {args.Kind}");
activatableApplicationLifetime.Deactivated += (sender, args) =>
Console.WriteLine($"App deactivated: {args.Kind}");
}
base.OnFrameworkInitializationCompleted();
}

2
src/Android/Avalonia.Android/AvaloniaMainActivity.App.cs

@ -40,7 +40,7 @@ namespace Avalonia.Android
{
var builder = CreateAppBuilder();
builder.SetupWithLifetime(new SingleViewLifetime());
builder.SetupWithLifetime(new SingleViewLifetime(this));
s_appBuilder = builder;
}

40
src/Android/Avalonia.Android/AvaloniaMainActivity.cs

@ -8,15 +8,29 @@ using Android.OS;
using Android.Runtime;
using Android.Views;
using AndroidX.AppCompat.App;
using Avalonia.Controls.ApplicationLifetimes;
namespace Avalonia.Android
{
public class AvaloniaMainActivity : AppCompatActivity, IActivityResultHandler, IActivityNavigationService
public class AvaloniaMainActivity : AppCompatActivity, IAvaloniaActivity
{
private EventHandler<ActivatedEventArgs> _onActivated, _onDeactivated;
public Action<int, Result, Intent> ActivityResult { get; set; }
public Action<int, string[], Permission[]> RequestPermissionsResult { get; set; }
public event EventHandler<AndroidBackRequestedEventArgs> BackRequested;
event EventHandler<ActivatedEventArgs> IAvaloniaActivity.Activated
{
add { _onActivated += value; }
remove { _onActivated -= value; }
}
event EventHandler<ActivatedEventArgs> IAvaloniaActivity.Deactivated
{
add { _onDeactivated += value; }
remove { _onDeactivated -= value; }
}
public override void OnBackPressed()
{
@ -30,6 +44,30 @@ namespace Avalonia.Android
}
}
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
if (Intent?.Data is {} androidUri
&& androidUri.IsAbsolute
&& Uri.TryCreate(androidUri.ToString(), UriKind.Absolute, out var protocolUri))
{
_onActivated?.Invoke(this, new ProtocolActivatedEventArgs(ActivationKind.OpenUri, protocolUri));
}
}
protected override void OnStop()
{
_onDeactivated?.Invoke(this, new ActivatedEventArgs(ActivationKind.Background));
base.OnStop();
}
protected override void OnStart()
{
_onActivated?.Invoke(this, new ActivatedEventArgs(ActivationKind.Background));
base.OnStart();
}
protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);

1
src/Android/Avalonia.Android/IAndroidNavigationService.cs

@ -1,4 +1,5 @@
using System;
using Avalonia.Controls.ApplicationLifetimes;
namespace Avalonia.Android
{

10
src/Android/Avalonia.Android/IAvaloniaActivity.cs

@ -0,0 +1,10 @@
using System;
using Avalonia.Controls.ApplicationLifetimes;
namespace Avalonia.Android;
public interface IAvaloniaActivity : IActivityResultHandler, IActivityNavigationService
{
event EventHandler<ActivatedEventArgs> Activated;
event EventHandler<ActivatedEventArgs> Deactivated;
}

23
src/Android/Avalonia.Android/SingleViewLifetime.cs

@ -1,12 +1,26 @@
using Avalonia.Controls;
using System;
using Android.App;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
namespace Avalonia.Android
{
internal class SingleViewLifetime : ISingleViewApplicationLifetime
internal class SingleViewLifetime : ISingleViewApplicationLifetime, IActivatableApplicationLifetime
{
private readonly Activity _activity;
private AvaloniaView _view;
public SingleViewLifetime(Activity activity)
{
_activity = activity;
if (activity is IAvaloniaActivity activableActivity)
{
activableActivity.Activated += (_, args) => Activated?.Invoke(this, args);
activableActivity.Deactivated += (_, args) => Deactivated?.Invoke(this, args);
}
}
public AvaloniaView View
{
get => _view; internal set
@ -22,5 +36,10 @@ namespace Avalonia.Android
}
public Control MainView { get; set; }
public event EventHandler<ActivatedEventArgs> Activated;
public event EventHandler<ActivatedEventArgs> Deactivated;
public bool TryLeaveBackground() => _activity.MoveTaskToBack(true);
public bool TryEnterBackground() => false;
}
}

23
src/Avalonia.Controls/ApplicationLifetimes/ActivatedEventArgs.cs

@ -0,0 +1,23 @@
using System;
namespace Avalonia.Controls.ApplicationLifetimes;
/// <summary>
/// Event args for an Application Lifetime Activated or Deactivated events.
/// </summary>
public class ActivatedEventArgs : EventArgs
{
/// <summary>
/// Ctor for ActivatedEventArgs
/// </summary>
/// <param name="kind">The <see cref="ActivationKind"/> that this event represents</param>
public ActivatedEventArgs(ActivationKind kind)
{
Kind = kind;
}
/// <summary>
/// The <see cref="ActivationKind"/> that this event represents.
/// </summary>
public ActivationKind Kind { get; }
}

25
src/Avalonia.Controls/ApplicationLifetimes/ActivationKind.cs

@ -0,0 +1,25 @@
namespace Avalonia.Controls.ApplicationLifetimes;
public enum ActivationKind
{
/// <summary>
/// When the application is passed a URI to open.
/// </summary>
OpenUri = 20,
/// <summary>
/// When the application is asked to reopen.
/// An example of this is on MacOS when all the windows are closed,
/// application continues to run in the background and the user clicks
/// the application's dock icon.
/// </summary>
Reopen = 30,
/// <summary>
/// When the application enters or leaves a background state.
/// An example is when on MacOS the user hides or shows and application (not window),
/// or when a browser application switchs tabs or when a mobile applications goes into
/// the background.
/// </summary>
Background = 40
}

13
src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs

@ -211,11 +211,16 @@ namespace Avalonia
public static int StartWithClassicDesktopLifetime(
this AppBuilder builder, string[] args, ShutdownMode shutdownMode = ShutdownMode.OnLastWindowClose)
{
var lifetime = new ClassicDesktopStyleApplicationLifetime()
var lifetime = AvaloniaLocator.Current.GetService<ClassicDesktopStyleApplicationLifetime>();
if (lifetime == null)
{
Args = args,
ShutdownMode = shutdownMode
};
lifetime = new ClassicDesktopStyleApplicationLifetime();
}
lifetime.Args = args;
lifetime.ShutdownMode = shutdownMode;
builder.SetupWithLifetime(lifetime);
return lifetime.Start(args);
}

35
src/Avalonia.Controls/ApplicationLifetimes/IActivatableApplicationLifetime.cs

@ -0,0 +1,35 @@
using System;
namespace Avalonia.Controls.ApplicationLifetimes;
/// <summary>
/// An interface for ApplicationLifetimes where the application can be Activated and Deactivated.
/// </summary>
public interface IActivatableApplicationLifetime
{
/// <summary>
/// An event that is raised when the application is Activated for various reasons
/// as described by the <see cref="ActivationKind"/> enumeration.
/// </summary>
event EventHandler<ActivatedEventArgs> Activated;
/// <summary>
/// An event that is raised when the application is Deactivated for various reasons
/// as described by the <see cref="ActivationKind"/> enumeration.
/// </summary>
event EventHandler<ActivatedEventArgs> Deactivated;
/// <summary>
/// Tells the application that it should attempt to leave its background state.
/// For example on OSX this would be [NSApp unhide]
/// </summary>
/// <returns>true if it was possible and the platform supports this. false otherwise</returns>
public bool TryLeaveBackground();
/// <summary>
/// Tells the application that it should attempt to enter its background state.
/// For example on OSX this would be [NSApp hide]
/// </summary>
/// <returns>true if it was possible and the platform supports this. false otherwise</returns>
public bool TryEnterBackground();
}

13
src/Avalonia.Controls/ApplicationLifetimes/ProtocolActivatedEventArgs.cs

@ -0,0 +1,13 @@
using System;
namespace Avalonia.Controls.ApplicationLifetimes;
public class ProtocolActivatedEventArgs : ActivatedEventArgs
{
public ProtocolActivatedEventArgs(ActivationKind kind, Uri uri) : base(kind)
{
Uri = uri;
}
public Uri Uri { get; }
}

1
src/Avalonia.Controls/Platform/INativeApplicationCommands.cs

@ -5,6 +5,7 @@ namespace Avalonia.Controls.Platform
/// </summary>
internal interface INativeApplicationCommands
{
void ShowApp();
void HideApp();
void ShowAll();
void HideOthers();

32
src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs

@ -13,6 +13,38 @@ namespace Avalonia.Native
void IAvnApplicationEvents.FilesOpened(IAvnStringArray urls)
{
((IApplicationPlatformEvents)Application.Current).RaiseUrlsOpened(urls.ToStringArray());
if (Application.Current?.ApplicationLifetime is MacOSClassicDesktopStyleApplicationLifetime lifetime)
{
foreach (var url in urls.ToStringArray())
{
lifetime.RaiseUrl(new Uri(url));
}
}
}
void IAvnApplicationEvents.OnReopen()
{
if (Application.Current?.ApplicationLifetime is MacOSClassicDesktopStyleApplicationLifetime lifetime)
{
lifetime.RaiseActivated(ActivationKind.Reopen);
}
}
void IAvnApplicationEvents.OnHide()
{
if (Application.Current?.ApplicationLifetime is MacOSClassicDesktopStyleApplicationLifetime lifetime)
{
lifetime.RaiseDeactivated(ActivationKind.Background);
}
}
void IAvnApplicationEvents.OnUnhide()
{
if (Application.Current?.ApplicationLifetime is MacOSClassicDesktopStyleApplicationLifetime lifetime)
{
lifetime.RaiseActivated(ActivationKind.Background);
}
}
public int TryShutdown()

4
src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Native;
namespace Avalonia
@ -24,6 +25,9 @@ namespace Avalonia
});
});
AvaloniaLocator.CurrentMutable.Bind<ClassicDesktopStyleApplicationLifetime>()
.ToConstant(new MacOSClassicDesktopStyleApplicationLifetime());
return builder;
}
}

50
src/Avalonia.Native/MacOSClassicDesktopStyleApplicationLifetime.cs

@ -0,0 +1,50 @@
using System;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Controls.Platform;
namespace Avalonia.Native;
#nullable enable
internal class MacOSClassicDesktopStyleApplicationLifetime : ClassicDesktopStyleApplicationLifetime,
IActivatableApplicationLifetime
{
/// <inheritdoc />
public event EventHandler<ActivatedEventArgs>? Activated;
/// <inheritdoc />
public event EventHandler<ActivatedEventArgs>? Deactivated;
/// <inheritdoc />
public bool TryLeaveBackground()
{
var nativeApplicationCommands = AvaloniaLocator.Current.GetService<INativeApplicationCommands>();
nativeApplicationCommands?.ShowApp();
return true;
}
/// <inheritdoc />
public bool TryEnterBackground()
{
var nativeApplicationCommands = AvaloniaLocator.Current.GetService<INativeApplicationCommands>();
nativeApplicationCommands?.HideApp();
return true;
}
internal void RaiseUrl(Uri uri)
{
Activated?.Invoke(this, new ProtocolActivatedEventArgs(ActivationKind.OpenUri, uri));
}
internal void RaiseActivated(ActivationKind kind)
{
Activated?.Invoke(this, new ActivatedEventArgs(kind));
}
internal void RaiseDeactivated(ActivationKind kind)
{
Deactivated?.Invoke(this, new ActivatedEventArgs(kind));
}
}

6
src/Avalonia.Native/MacOSNativeMenuCommands.cs

@ -13,6 +13,11 @@ namespace Avalonia.Native
_commands = commands;
}
public void ShowApp()
{
_commands.UnhideApp();
}
public void HideApp()
{
_commands.HideApp();
@ -28,7 +33,6 @@ namespace Avalonia.Native
_commands.HideOthers();
}
public static readonly AttachedProperty<bool> IsServicesSubmenuProperty =
AvaloniaProperty.RegisterAttached<MacOSNativeMenuCommands, NativeMenu, bool>("IsServicesSubmenu", false);
}

4
src/Avalonia.Native/avn.idl

@ -1086,11 +1086,15 @@ interface IAvnApplicationEvents : IUnknown
{
void FilesOpened (IAvnStringArray* urls);
bool TryShutdown();
void OnReopen ();
void OnHide ();
void OnUnhide ();
}
[uuid(b4284791-055b-4313-8c2e-50f0a8c72ce9)]
interface IAvnApplicationCommands : IUnknown
{
HRESULT UnhideApp();
HRESULT HideApp();
HRESULT ShowAll();
HRESULT HideOthers();

3
src/Browser/Avalonia.Browser/BrowserAppBuilder.cs

@ -73,8 +73,9 @@ public static class BrowserAppBuilder
{
builder = await PreSetupBrowser(builder, options);
var lifetime = new BrowserSingleViewLifetime();
builder
.SetupWithoutStarting();
.SetupWithLifetime(lifetime);
}
internal static async Task<AppBuilder> PreSetupBrowser(AppBuilder builder, BrowserPlatformOptions? options)

19
src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs

@ -4,11 +4,22 @@ using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using System.Runtime.Versioning;
using Avalonia.Browser;
using Avalonia.Browser.Interop;
using Avalonia.Threading;
namespace Avalonia;
internal class BrowserSingleViewLifetime : ISingleViewApplicationLifetime
internal class BrowserSingleViewLifetime : ISingleViewApplicationLifetime, IActivatableApplicationLifetime
{
public BrowserSingleViewLifetime()
{
bool? initiallyVisible = InputHelper.SubscribeVisibilityChange(visible =>
{
initiallyVisible = null;
(visible ? Activated : Deactivated)?.Invoke(this, new ActivatedEventArgs(ActivationKind.Background));
});
}
public AvaloniaView? View;
public Control? MainView
@ -33,4 +44,10 @@ internal class BrowserSingleViewLifetime : ISingleViewApplicationLifetime
throw new InvalidOperationException("Browser lifetime was not initialized. Make sure AppBuilder.StartBrowserApp was called.");
}
}
public event EventHandler<ActivatedEventArgs>? Activated;
public event EventHandler<ActivatedEventArgs>? Deactivated;
public bool TryLeaveBackground() => false;
public bool TryEnterBackground() => true;
}

9
src/Browser/Avalonia.Browser/Interop/InputHelper.cs

@ -50,7 +50,14 @@ internal static partial class InputHelper
[JSImport("InputHelper.subscribeDropEvents", AvaloniaModule.MainModuleName)]
public static partial void SubscribeDropEvents(JSObject containerElement,
[JSMarshalAs<JSType.Function<JSType.Object, JSType.Boolean>>] Func<JSObject, bool> dragEvent);
[JSImport("InputHelper.subscribeKeyboardGeometryChange", AvaloniaModule.MainModuleName)]
public static partial void SubscribeKeyboardGeometryChange(JSObject containerElement,
[JSMarshalAs<JSType.Function<JSType.Object, JSType.Boolean>>] Func<JSObject, bool> handler);
[JSImport("InputHelper.subscribeVisibilityChange", AvaloniaModule.MainModuleName)]
public static partial bool SubscribeVisibilityChange([JSMarshalAs<JSType.Function<JSType.Boolean>>] Action<bool> handler);
[JSImport("InputHelper.getCoalescedEvents", AvaloniaModule.MainModuleName)]
[return: JSMarshalAs<JSType.Array<JSType.Object>>]
public static partial JSObject[] GetCoalescedEvents(JSObject pointerEvent);

26
src/Browser/Avalonia.Browser/webapp/modules/avalonia/input.ts

@ -245,6 +245,32 @@ export class InputHelper {
return pointerEvent.getCoalescedEvents();
}
public static subscribeKeyboardGeometryChange(
element: HTMLInputElement,
handler: (args: any) => boolean) {
if ("virtualKeyboard" in navigator) {
// (navigator as any).virtualKeyboard.overlaysContent = true;
(navigator as any).virtualKeyboard.addEventListener("geometrychange", (event: any) => {
const elementRect = element.getBoundingClientRect();
const keyboardRect = event.target.boundingRect as DOMRect;
handler({
x: keyboardRect.x - elementRect.x,
y: keyboardRect.y - elementRect.y,
width: keyboardRect.width,
height: keyboardRect.height
});
});
}
}
public static subscribeVisibilityChange(
handler: (state: boolean) => void): boolean {
document.addEventListener("visibilitychange", () => {
handler(document.visibilityState === "visible");
});
return document.visibilityState === "visible";
}
public static clearInput(inputElement: HTMLInputElement) {
inputElement.value = "";
}

58
src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs

@ -1,25 +1,39 @@
using System;
using Foundation;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using UIKit;
namespace Avalonia.iOS
{
public class AvaloniaAppDelegate<TApp> : UIResponder, IUIApplicationDelegate
public interface IAvaloniaAppDelegate
{
event EventHandler<ActivatedEventArgs> Activated;
event EventHandler<ActivatedEventArgs> Deactivated;
}
public class AvaloniaAppDelegate<TApp> : UIResponder, IUIApplicationDelegate, IAvaloniaAppDelegate
where TApp : Application, new()
{
class SingleViewLifetime : ISingleViewApplicationLifetime
{
public AvaloniaView View;
private EventHandler<ActivatedEventArgs> _onActivated, _onDeactivated;
public Control MainView
{
get => View.Content;
set => View.Content = value;
}
public AvaloniaAppDelegate()
{
NSNotificationCenter.DefaultCenter.AddObserver(UIApplication.DidEnterBackgroundNotification, OnEnteredBackground);
NSNotificationCenter.DefaultCenter.AddObserver(UIApplication.WillEnterForegroundNotification, OnLeavingBackground);
}
event EventHandler<ActivatedEventArgs> IAvaloniaAppDelegate.Activated
{
add { _onActivated += value; }
remove { _onActivated -= value; }
}
event EventHandler<ActivatedEventArgs> IAvaloniaAppDelegate.Deactivated
{
add { _onDeactivated += value; }
remove { _onDeactivated -= value; }
}
protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder;
[Export("window")]
@ -30,7 +44,7 @@ namespace Avalonia.iOS
{
var builder = AppBuilder.Configure<TApp>().UseiOS();
var lifetime = new SingleViewLifetime();
var lifetime = new SingleViewLifetime(this);
builder.AfterSetup(_ =>
{
@ -54,5 +68,27 @@ namespace Avalonia.iOS
return true;
}
[Export("application:openURL:options:")]
public bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options)
{
if (Uri.TryCreate(url.ToString(), UriKind.Absolute, out var uri))
{
_onActivated?.Invoke(this, new ProtocolActivatedEventArgs(ActivationKind.OpenUri, uri));
return true;
}
return false;
}
private void OnEnteredBackground(NSNotification notification)
{
_onDeactivated?.Invoke(this, new ActivatedEventArgs(ActivationKind.Background));
}
private void OnLeavingBackground(NSNotification notification)
{
_onActivated?.Invoke(this, new ActivatedEventArgs(ActivationKind.Background));
}
}
}

27
src/iOS/Avalonia.iOS/SingleViewLifetime.cs

@ -0,0 +1,27 @@
using System;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
namespace Avalonia.iOS;
internal class SingleViewLifetime : ISingleViewApplicationLifetime, IActivatableApplicationLifetime
{
public SingleViewLifetime(IAvaloniaAppDelegate avaloniaAppDelegate)
{
avaloniaAppDelegate.Activated += (_, args) => Activated?.Invoke(this, args);
avaloniaAppDelegate.Deactivated += (_, args) => Deactivated?.Invoke(this, args);
}
public AvaloniaView View;
public Control MainView
{
get => View.Content;
set => View.Content = value;
}
public event EventHandler<ActivatedEventArgs> Activated;
public event EventHandler<ActivatedEventArgs> Deactivated;
public bool TryLeaveBackground() => false;
public bool TryEnterBackground() => false;
}
Loading…
Cancel
Save