From b9ec339cfa7de8921696074f9270cd049ee51f36 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 4 Mar 2024 17:10:50 -0800 Subject: [PATCH] Add Application.TryGetFeature, re-add new feature - IActivatableLifetime (#14556) * Implement IOptionalFeatureProvider on Application class * Use IActivatableLifetime from the TryGetFeature instead of a lifetime * Reimplement macOS IActivatableLifetime * Reimplement iOS IActivatableLifetime * Reimplement Browser IActivatableLifetime * Reimplement Android IActivatableLifetime * Make AndroidActivatableLifetime aware of activity re-creation * Don't crash control catalog dialogs page * Browser TryEnterBackground shouldn't return true --- Avalonia.sln.DotSettings | 1 + samples/ControlCatalog/App.xaml.cs | 3 +- .../ControlCatalog/Pages/DialogsPage.xaml.cs | 12 ++++- .../Avalonia.Android/AndroidPlatform.cs | 4 +- .../AvaloniaMainActivity.App.cs | 12 ++++- .../Platform/AndroidActivatableLifetime.cs | 47 +++++++++++++++++++ .../Avalonia.Android/SingleViewLifetime.cs | 23 +-------- .../Platform/IOptionalFeatureProvider.cs | 7 ++- src/Avalonia.Controls/AppBuilder.cs | 10 ++-- src/Avalonia.Controls/Application.cs | 35 ++++++++++++-- .../ClassicDesktopStyleApplicationLifetime.cs | 3 +- .../IActivatableApplicationLifetime.cs | 9 +++- .../AvaloniaNativeApplicationPlatform.cs | 16 +++---- src/Avalonia.Native/AvaloniaNativePlatform.cs | 4 +- .../AvaloniaNativePlatformExtensions.cs | 4 +- ...ifetime.cs => MacOSActivatableLifetime.cs} | 3 +- .../BrowserActivatableLifetime.cs | 36 ++++++++++++++ .../BrowserSingleViewLifetime.cs | 20 +------- .../Avalonia.Browser/WindowingPlatform.cs | 4 +- src/iOS/Avalonia.iOS/ActivatableLifetime.cs | 18 +++++++ src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs | 4 +- src/iOS/Avalonia.iOS/Platform.cs | 16 +++++-- src/iOS/Avalonia.iOS/SingleViewLifetime.cs | 15 +----- 23 files changed, 212 insertions(+), 94 deletions(-) create mode 100644 src/Android/Avalonia.Android/Platform/AndroidActivatableLifetime.cs rename src/Avalonia.Native/{MacOSClassicDesktopStyleApplicationLifetime.cs => MacOSActivatableLifetime.cs} (89%) create mode 100644 src/Browser/Avalonia.Browser/BrowserActivatableLifetime.cs create mode 100644 src/iOS/Avalonia.iOS/ActivatableLifetime.cs diff --git a/Avalonia.sln.DotSettings b/Avalonia.sln.DotSettings index b0692905e7..52f687ffab 100644 --- a/Avalonia.sln.DotSettings +++ b/Avalonia.sln.DotSettings @@ -37,5 +37,6 @@ <Policy Inspect="False" Prefix="T" Suffix="" Style="AaBb" /> <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> True + True True True diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index 6a941c21ae..2b55c5bad8 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -3,6 +3,7 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; +using Avalonia.Platform; using Avalonia.Styling; using Avalonia.Themes.Simple; using Avalonia.Themes.Fluent; @@ -51,7 +52,7 @@ namespace ControlCatalog singleViewLifetime.MainView = new MainView { DataContext = new MainWindowViewModel() }; } - if (ApplicationLifetime is IActivatableApplicationLifetime activatableApplicationLifetime) + if (this.TryGetFeature() is {} activatableApplicationLifetime) { activatableApplicationLifetime.Activated += (sender, args) => Console.WriteLine($"App activated: {args.Kind}"); diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs index b1ce10df24..39b00590f3 100644 --- a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs @@ -3,6 +3,7 @@ using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Security; using System.Threading.Tasks; using Avalonia; using Avalonia.Controls; @@ -43,7 +44,7 @@ namespace ControlCatalog.Pages { lastSelectedDirectory = await GetStorageProvider().TryGetWellKnownFolderAsync(folderEnum); } - else + else if (!string.IsNullOrWhiteSpace(currentFolderBox.Text)) { if (!Uri.TryCreate(currentFolderBox.Text, UriKind.Absolute, out var folderLink)) { @@ -52,7 +53,14 @@ namespace ControlCatalog.Pages if (folderLink is not null) { - lastSelectedDirectory = await GetStorageProvider().TryGetFolderFromPathAsync(folderLink); + try + { + lastSelectedDirectory = await GetStorageProvider().TryGetFolderFromPathAsync(folderLink); + } + catch (SecurityException) + { + + } } } }; diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index f3dcb3560c..4ed759c78b 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -5,6 +5,7 @@ using Avalonia.Controls; using Avalonia.Android; using Avalonia.Android.Platform; using Avalonia.Android.Platform.Input; +using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.OpenGL.Egl; @@ -81,7 +82,8 @@ namespace Avalonia.Android .Bind().ToConstant(new AndroidThreadingInterface()) .Bind().ToSingleton() .Bind().ToConstant(new ChoreographerTimer()) - .Bind().ToSingleton(); + .Bind().ToSingleton() + .Bind().ToConstant(new AndroidActivatableLifetime()); var graphics = InitializeGraphics(Options); if (graphics is not null) diff --git a/src/Android/Avalonia.Android/AvaloniaMainActivity.App.cs b/src/Android/Avalonia.Android/AvaloniaMainActivity.App.cs index cf258a5a1f..f203b6c544 100644 --- a/src/Android/Avalonia.Android/AvaloniaMainActivity.App.cs +++ b/src/Android/Avalonia.Android/AvaloniaMainActivity.App.cs @@ -1,5 +1,9 @@ #nullable enable +using Avalonia.Android.Platform; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Platform; + namespace Avalonia.Android { partial class AvaloniaMainActivity where TApp : Application, new() @@ -36,11 +40,17 @@ namespace Avalonia.Android { var builder = CreateAppBuilder(); - builder.SetupWithLifetime(new SingleViewLifetime(this)); + builder.SetupWithLifetime(new SingleViewLifetime()); s_appBuilder = builder; } + if (Avalonia.Application.Current?.TryGetFeature() + is AndroidActivatableLifetime activatableLifetime) + { + activatableLifetime.Activity = this; + } + View = new AvaloniaView(this); if (ViewContent != null) { diff --git a/src/Android/Avalonia.Android/Platform/AndroidActivatableLifetime.cs b/src/Android/Avalonia.Android/Platform/AndroidActivatableLifetime.cs new file mode 100644 index 0000000000..f8cf0d946a --- /dev/null +++ b/src/Android/Avalonia.Android/Platform/AndroidActivatableLifetime.cs @@ -0,0 +1,47 @@ +using System; +using Android.App; +using Avalonia.Controls.ApplicationLifetimes; + +namespace Avalonia.Android.Platform; + +internal class AndroidActivatableLifetime : IActivatableLifetime +{ + private IAvaloniaActivity _activity; + + public IAvaloniaActivity Activity + { + get => _activity; + set + { + if (_activity is not null) + { + _activity.Activated -= ActivityOnActivated; + _activity.Deactivated -= ActivityOnDeactivated; + } + + _activity = value; + + if (_activity is not null) + { + _activity.Activated += ActivityOnActivated; + _activity.Deactivated += ActivityOnDeactivated; + } + } + } + + public event EventHandler Activated; + public event EventHandler Deactivated; + + public bool TryLeaveBackground() => (_activity as Activity)?.MoveTaskToBack(true) == true; + public bool TryEnterBackground() => false; + + private void ActivityOnDeactivated(object sender, ActivatedEventArgs e) + { + Deactivated?.Invoke(this, e); + } + + private void ActivityOnActivated(object sender, ActivatedEventArgs e) + { + Activated?.Invoke(this, e); + } +} diff --git a/src/Android/Avalonia.Android/SingleViewLifetime.cs b/src/Android/Avalonia.Android/SingleViewLifetime.cs index 2b6b4d3359..c912d2f890 100644 --- a/src/Android/Avalonia.Android/SingleViewLifetime.cs +++ b/src/Android/Avalonia.Android/SingleViewLifetime.cs @@ -1,25 +1,11 @@ -using System; -using Android.App; -using Avalonia.Controls; +using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; namespace Avalonia.Android { - internal class SingleViewLifetime : ISingleViewApplicationLifetime, IActivatableApplicationLifetime + internal class SingleViewLifetime : ISingleViewApplicationLifetime { - 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 { @@ -36,10 +22,5 @@ namespace Avalonia.Android } public Control MainView { get; set; } - public event EventHandler Activated; - public event EventHandler Deactivated; - - public bool TryLeaveBackground() => _activity.MoveTaskToBack(true); - public bool TryEnterBackground() => false; } } diff --git a/src/Avalonia.Base/Platform/IOptionalFeatureProvider.cs b/src/Avalonia.Base/Platform/IOptionalFeatureProvider.cs index 1778031e5b..27c2243791 100644 --- a/src/Avalonia.Base/Platform/IOptionalFeatureProvider.cs +++ b/src/Avalonia.Base/Platform/IOptionalFeatureProvider.cs @@ -1,22 +1,25 @@ using System; using System.Diagnostics.CodeAnalysis; +// TODO12: move to Avalonia namespace. namespace Avalonia.Platform; public interface IOptionalFeatureProvider { /// - /// Queries for an optional feature + /// Queries for an optional feature. /// - /// Feature type + /// Feature type. public object? TryGetFeature(Type featureType); } public static class OptionalFeatureProviderExtensions { + /// public static T? TryGetFeature(this IOptionalFeatureProvider provider) where T : class => (T?)provider.TryGetFeature(typeof(T)); + /// public static bool TryGetFeature(this IOptionalFeatureProvider provider, [MaybeNullWhen(false)] out T rv) where T : class { diff --git a/src/Avalonia.Controls/AppBuilder.cs b/src/Avalonia.Controls/AppBuilder.cs index 3275f35989..b77ce7ebb2 100644 --- a/src/Avalonia.Controls/AppBuilder.cs +++ b/src/Avalonia.Controls/AppBuilder.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Linq; @@ -58,6 +59,8 @@ namespace Avalonia /// /// Gets a method to override a lifetime factory. /// + [Obsolete("This property has no effect", true)] + [DebuggerBrowsable(DebuggerBrowsableState.Never)] public Func? LifetimeOverride { get; private set; } /// @@ -244,13 +247,6 @@ namespace Avalonia return Self; } - [PrivateApi] - public AppBuilder UseLifetimeOverride(Func func) - { - LifetimeOverride = func; - return Self; - } - /// /// Configures platform-specific options /// diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 46b31bff3e..6b8ddc15ea 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -30,7 +30,7 @@ namespace Avalonia /// method. /// - Tracks the lifetime of the application. /// - public class Application : AvaloniaObject, IDataContextProvider, IGlobalDataTemplates, IGlobalStyles, IThemeVariantHost, IApplicationPlatformEvents + public class Application : AvaloniaObject, IDataContextProvider, IGlobalDataTemplates, IGlobalStyles, IThemeVariantHost, IApplicationPlatformEvents, IOptionalFeatureProvider { /// /// The application-global data templates. @@ -62,7 +62,7 @@ namespace Avalonia /// public event EventHandler? ResourcesChanged; - [Obsolete("Cast ApplicationLifetime to IActivatableApplicationLifetime instead.")] + [Obsolete("Use Application.Current.TryGetFeature() instead.")] public event EventHandler? UrlsOpened; /// @@ -204,7 +204,7 @@ namespace Avalonia /// which should always be preferred over a global one, /// as specific top levels might have different settings set-up. /// - public IPlatformSettings? PlatformSettings => AvaloniaLocator.Current.GetService(); + public IPlatformSettings? PlatformSettings => this.TryGetFeature(); event Action>? IGlobalStyles.GlobalStylesAdded { @@ -329,7 +329,34 @@ namespace Avalonia get => _name; set => SetAndRaise(NameProperty, ref _name, value); } - + + /// + /// Queries for an optional feature. + /// + /// Feature type. + /// + /// Features currently supported by : + /// + /// IPlatformSettings + /// IActivatableApplicationLifetime + /// + /// + public object? TryGetFeature(Type featureType) + { + if (featureType == typeof(IPlatformSettings)) + { + return AvaloniaLocator.Current.GetService(); + } + + if (featureType == typeof(IActivatableLifetime)) + { + return AvaloniaLocator.Current.GetService(); + } + + // Do not return just any service from AvaloniaLocator. + return null; + } + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs index 7b3de3306b..497be9d671 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs @@ -217,8 +217,7 @@ namespace Avalonia private static ClassicDesktopStyleApplicationLifetime PrepareLifetime(AppBuilder builder, string[] args, Action? lifetimeBuilder) { - var lifetime = builder.LifetimeOverride?.Invoke(typeof(ClassicDesktopStyleApplicationLifetime)) as ClassicDesktopStyleApplicationLifetime - ?? new ClassicDesktopStyleApplicationLifetime(); + var lifetime = new ClassicDesktopStyleApplicationLifetime(); lifetime.SubscribeGlobalEvents(); lifetime.Args = args; diff --git a/src/Avalonia.Controls/ApplicationLifetimes/IActivatableApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/IActivatableApplicationLifetime.cs index eb050e63d1..e07e8cd6a1 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/IActivatableApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/IActivatableApplicationLifetime.cs @@ -1,13 +1,20 @@ using System; +using System.Diagnostics; using Avalonia.Metadata; namespace Avalonia.Controls.ApplicationLifetimes; +[NotClientImplementable] +[Obsolete("This interface has no effect. Instead use Application.Current.TryGetFeature().", true)] +public interface IActivatableApplicationLifetime : IActivatableLifetime { + +} + /// /// An interface for ApplicationLifetimes where the application can be Activated and Deactivated. /// [NotClientImplementable] -public interface IActivatableApplicationLifetime +public interface IActivatableLifetime { /// /// An event that is raised when the application is Activated for various reasons diff --git a/src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs b/src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs index 212088d4cd..298ef5619a 100644 --- a/src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs @@ -9,18 +9,18 @@ namespace Avalonia.Native internal class AvaloniaNativeApplicationPlatform : NativeCallbackBase, IAvnApplicationEvents, IPlatformLifetimeEventsImpl { public event EventHandler ShutdownRequested; - + void IAvnApplicationEvents.FilesOpened(IAvnStringArray urls) { - ((IApplicationPlatformEvents)Application.Current).RaiseUrlsOpened(urls.ToStringArray()); + ((IApplicationPlatformEvents)Application.Current)?.RaiseUrlsOpened(urls.ToStringArray()); } - + void IAvnApplicationEvents.UrlsOpened(IAvnStringArray urls) { // Raise the urls opened event to be compatible with legacy behavior. - ((IApplicationPlatformEvents)Application.Current).RaiseUrlsOpened(urls.ToStringArray()); + ((IApplicationPlatformEvents)Application.Current)?.RaiseUrlsOpened(urls.ToStringArray()); - if (Application.Current?.ApplicationLifetime is MacOSClassicDesktopStyleApplicationLifetime lifetime) + if (AvaloniaLocator.Current.GetService() is MacOSActivatableLifetime lifetime) { foreach (var url in urls.ToStringArray()) { @@ -34,7 +34,7 @@ namespace Avalonia.Native void IAvnApplicationEvents.OnReopen() { - if (Application.Current?.ApplicationLifetime is MacOSClassicDesktopStyleApplicationLifetime lifetime) + if (AvaloniaLocator.Current.GetService() is MacOSActivatableLifetime lifetime) { lifetime.RaiseActivated(ActivationKind.Reopen); } @@ -42,7 +42,7 @@ namespace Avalonia.Native void IAvnApplicationEvents.OnHide() { - if (Application.Current?.ApplicationLifetime is MacOSClassicDesktopStyleApplicationLifetime lifetime) + if (AvaloniaLocator.Current.GetService() is MacOSActivatableLifetime lifetime) { lifetime.RaiseDeactivated(ActivationKind.Background); } @@ -50,7 +50,7 @@ namespace Avalonia.Native void IAvnApplicationEvents.OnUnhide() { - if (Application.Current?.ApplicationLifetime is MacOSClassicDesktopStyleApplicationLifetime lifetime) + if (AvaloniaLocator.Current.GetService() is MacOSActivatableLifetime lifetime) { lifetime.RaiseActivated(ActivationKind.Background); } diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index 85331bea6b..f218103b18 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -1,6 +1,7 @@ using System; using System.Runtime.InteropServices; using Avalonia.Compatibility; +using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.Platform; using Avalonia.Input; using Avalonia.Input.Platform; @@ -112,7 +113,8 @@ namespace Avalonia.Native .Bind().ToConstant(new MacOSMountedVolumeInfoProvider()) .Bind().ToConstant(new AvaloniaNativeDragSource(_factory)) .Bind().ToConstant(applicationPlatform) - .Bind().ToConstant(new MacOSNativeMenuCommands(_factory.CreateApplicationCommands())); + .Bind().ToConstant(new MacOSNativeMenuCommands(_factory.CreateApplicationCommands())) + .Bind().ToSingleton(); var hotkeys = new PlatformHotkeyConfiguration(KeyModifiers.Meta, wholeWordTextActionModifiers: KeyModifiers.Alt); hotkeys.MoveCursorToTheStartOfLine.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers)); diff --git a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs index 1956e214fb..d5fffbbff0 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs @@ -23,9 +23,7 @@ namespace Avalonia platform.SetupApplicationName(); platform.SetupApplicationMenuExporter(); }); - }) - .UseLifetimeOverride(type => type == typeof(ClassicDesktopStyleApplicationLifetime) - ? new MacOSClassicDesktopStyleApplicationLifetime() : null); + }); return builder; } diff --git a/src/Avalonia.Native/MacOSClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Native/MacOSActivatableLifetime.cs similarity index 89% rename from src/Avalonia.Native/MacOSClassicDesktopStyleApplicationLifetime.cs rename to src/Avalonia.Native/MacOSActivatableLifetime.cs index caf6e03ec8..54dfab6132 100644 --- a/src/Avalonia.Native/MacOSClassicDesktopStyleApplicationLifetime.cs +++ b/src/Avalonia.Native/MacOSActivatableLifetime.cs @@ -6,8 +6,7 @@ namespace Avalonia.Native; #nullable enable -internal class MacOSClassicDesktopStyleApplicationLifetime : ClassicDesktopStyleApplicationLifetime, - IActivatableApplicationLifetime +internal class MacOSActivatableLifetime : IActivatableLifetime { /// public event EventHandler? Activated; diff --git a/src/Browser/Avalonia.Browser/BrowserActivatableLifetime.cs b/src/Browser/Avalonia.Browser/BrowserActivatableLifetime.cs new file mode 100644 index 0000000000..30996d55df --- /dev/null +++ b/src/Browser/Avalonia.Browser/BrowserActivatableLifetime.cs @@ -0,0 +1,36 @@ +using System; +using Avalonia.Browser.Interop; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Threading; + +namespace Avalonia.Browser; + +internal class BrowserActivatableLifetime : IActivatableLifetime +{ + public BrowserActivatableLifetime() + { + bool? initiallyVisible = InputHelper.SubscribeVisibilityChange(visible => + { + initiallyVisible = null; + (visible ? Activated : Deactivated)?.Invoke(this, new ActivatedEventArgs(ActivationKind.Background)); + }); + + // Trigger Activated as an initial state, if web page is visible, and wasn't hidden during initialization. + if (initiallyVisible == true) + { + _ = Dispatcher.UIThread.InvokeAsync(() => + { + if (initiallyVisible == true) + { + Activated?.Invoke(this, new ActivatedEventArgs(ActivationKind.Background)); + } + }, DispatcherPriority.Background); + } + } + + public event EventHandler? Activated; + public event EventHandler? Deactivated; + + public bool TryLeaveBackground() => false; + public bool TryEnterBackground() => false; +} diff --git a/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs b/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs index e568067b74..ba404c2600 100644 --- a/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs +++ b/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs @@ -2,24 +2,12 @@ using System.Diagnostics.CodeAnalysis; 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, IActivatableApplicationLifetime +internal class BrowserSingleViewLifetime : ISingleViewApplicationLifetime { - public BrowserSingleViewLifetime() - { - bool? initiallyVisible = InputHelper.SubscribeVisibilityChange(visible => - { - initiallyVisible = null; - (visible ? Activated : Deactivated)?.Invoke(this, new ActivatedEventArgs(ActivationKind.Background)); - }); - } - public AvaloniaView? View; public Control? MainView @@ -44,10 +32,4 @@ internal class BrowserSingleViewLifetime : ISingleViewApplicationLifetime, IActi throw new InvalidOperationException("Browser lifetime was not initialized. Make sure AppBuilder.StartBrowserApp was called."); } } - - public event EventHandler? Activated; - public event EventHandler? Deactivated; - - public bool TryLeaveBackground() => false; - public bool TryEnterBackground() => false; } diff --git a/src/Browser/Avalonia.Browser/WindowingPlatform.cs b/src/Browser/Avalonia.Browser/WindowingPlatform.cs index 2db0e2aec3..27e5537431 100644 --- a/src/Browser/Avalonia.Browser/WindowingPlatform.cs +++ b/src/Browser/Avalonia.Browser/WindowingPlatform.cs @@ -1,6 +1,7 @@ using System; using Avalonia.Browser.Interop; using Avalonia.Browser.Skia; +using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Platform; @@ -43,7 +44,8 @@ internal class BrowserWindowingPlatform : IWindowingPlatform .Bind().ToConstant(instance) .Bind().ToConstant(new BrowserSkiaGraphics()) .Bind().ToSingleton() - .Bind().ToSingleton(); + .Bind().ToSingleton() + .Bind().ToSingleton(); if (AvaloniaLocator.Current.GetService() is { } options && options.RegisterAvaloniaServiceWorker) diff --git a/src/iOS/Avalonia.iOS/ActivatableLifetime.cs b/src/iOS/Avalonia.iOS/ActivatableLifetime.cs new file mode 100644 index 0000000000..c3e2ddedde --- /dev/null +++ b/src/iOS/Avalonia.iOS/ActivatableLifetime.cs @@ -0,0 +1,18 @@ +using System; +using Avalonia.Controls.ApplicationLifetimes; + +namespace Avalonia.iOS; + +internal class ActivatableLifetime : IActivatableLifetime +{ + public ActivatableLifetime(IAvaloniaAppDelegate avaloniaAppDelegate) + { + avaloniaAppDelegate.Activated += (_, args) => Activated?.Invoke(this, args); + avaloniaAppDelegate.Deactivated += (_, args) => Deactivated?.Invoke(this, args); + } + + public event EventHandler? Activated; + public event EventHandler? Deactivated; + public bool TryLeaveBackground() => false; + public bool TryEnterBackground() => false; +} diff --git a/src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs b/src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs index e4ec20bbbd..7a07fab2fe 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs @@ -42,9 +42,9 @@ namespace Avalonia.iOS [Export("application:didFinishLaunchingWithOptions:")] public bool FinishedLaunching(UIApplication application, NSDictionary launchOptions) { - var builder = AppBuilder.Configure().UseiOS(); + var builder = AppBuilder.Configure().UseiOS(this); - var lifetime = new SingleViewLifetime(this); + var lifetime = new SingleViewLifetime(); builder.AfterSetup(_ => { diff --git a/src/iOS/Avalonia.iOS/Platform.cs b/src/iOS/Avalonia.iOS/Platform.cs index 07c212a01a..57d31f773c 100644 --- a/src/iOS/Avalonia.iOS/Platform.cs +++ b/src/iOS/Avalonia.iOS/Platform.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using System.Linq; +using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Input; using Avalonia.Input.Platform; +using Avalonia.iOS; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Rendering.Composition; @@ -39,13 +41,15 @@ namespace Avalonia public static class IOSApplicationExtensions { - public static AppBuilder UseiOS(this AppBuilder builder) + public static AppBuilder UseiOS(this AppBuilder builder, IAvaloniaAppDelegate appDelegate) { return builder .UseStandardRuntimePlatformSubsystem() - .UseWindowingSubsystem(iOS.Platform.Register, "iOS") + .UseWindowingSubsystem(() => iOS.Platform.Register(appDelegate), "iOS") .UseSkia(); } + + public static AppBuilder UseiOS(this AppBuilder builder) => UseiOS(builder, null!); } } @@ -58,7 +62,7 @@ namespace Avalonia.iOS public static DisplayLinkTimer? Timer; internal static Compositor? Compositor { get; private set; } - public static void Register() + public static void Register(IAvaloniaAppDelegate? appDelegate) { Options = AvaloniaLocator.Current.GetService() ?? new iOSPlatformOptions(); @@ -77,6 +81,12 @@ namespace Avalonia.iOS .Bind().ToConstant(DispatcherImpl.Instance) .Bind().ToConstant(keyboard); + if (appDelegate is not null) + { + AvaloniaLocator.CurrentMutable + .Bind().ToConstant(new ActivatableLifetime(appDelegate)); + } + Compositor = new Compositor(AvaloniaLocator.Current.GetService()); AvaloniaLocator.CurrentMutable.Bind().ToConstant(Compositor); } diff --git a/src/iOS/Avalonia.iOS/SingleViewLifetime.cs b/src/iOS/Avalonia.iOS/SingleViewLifetime.cs index 9627700c7f..49b8e97769 100644 --- a/src/iOS/Avalonia.iOS/SingleViewLifetime.cs +++ b/src/iOS/Avalonia.iOS/SingleViewLifetime.cs @@ -4,14 +4,8 @@ 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); - } - +internal class SingleViewLifetime : ISingleViewApplicationLifetime +{ public AvaloniaView? View; public Control? MainView @@ -19,9 +13,4 @@ internal class SingleViewLifetime : ISingleViewApplicationLifetime, IActivatable get => View!.Content; set => View!.Content = value; } - - public event EventHandler? Activated; - public event EventHandler? Deactivated; - public bool TryLeaveBackground() => false; - public bool TryEnterBackground() => false; }