Browse Source

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
pull/14826/head
Max Katz 2 years ago
committed by GitHub
parent
commit
b9ec339cfa
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      Avalonia.sln.DotSettings
  2. 3
      samples/ControlCatalog/App.xaml.cs
  3. 12
      samples/ControlCatalog/Pages/DialogsPage.xaml.cs
  4. 4
      src/Android/Avalonia.Android/AndroidPlatform.cs
  5. 12
      src/Android/Avalonia.Android/AvaloniaMainActivity.App.cs
  6. 47
      src/Android/Avalonia.Android/Platform/AndroidActivatableLifetime.cs
  7. 23
      src/Android/Avalonia.Android/SingleViewLifetime.cs
  8. 7
      src/Avalonia.Base/Platform/IOptionalFeatureProvider.cs
  9. 10
      src/Avalonia.Controls/AppBuilder.cs
  10. 35
      src/Avalonia.Controls/Application.cs
  11. 3
      src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
  12. 9
      src/Avalonia.Controls/ApplicationLifetimes/IActivatableApplicationLifetime.cs
  13. 16
      src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs
  14. 4
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  15. 4
      src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs
  16. 3
      src/Avalonia.Native/MacOSActivatableLifetime.cs
  17. 36
      src/Browser/Avalonia.Browser/BrowserActivatableLifetime.cs
  18. 20
      src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs
  19. 4
      src/Browser/Avalonia.Browser/WindowingPlatform.cs
  20. 18
      src/iOS/Avalonia.iOS/ActivatableLifetime.cs
  21. 4
      src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs
  22. 16
      src/iOS/Avalonia.iOS/Platform.cs
  23. 15
      src/iOS/Avalonia.iOS/SingleViewLifetime.cs

1
Avalonia.sln.DotSettings

@ -37,5 +37,6 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypeParameters/@EntryIndexedValue">&lt;Policy Inspect="False" Prefix="T" Suffix="" Style="AaBb" /&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypeParameters/@EntryIndexedValue">&lt;Policy Inspect="False" Prefix="T" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypesAndNamespaces/@EntryIndexedValue">&lt;Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypesAndNamespaces/@EntryIndexedValue">&lt;Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002EDaemon_002ESettings_002EMigration_002ESwaWarningsModeSettingsMigrate/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002EDaemon_002ESettings_002EMigration_002ESwaWarningsModeSettingsMigrate/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Activatable/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Avalonia/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Avalonia/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Fcitx/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> <s:Boolean x:Key="/Default/UserDictionary/Words/=Fcitx/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

3
samples/ControlCatalog/App.xaml.cs

@ -3,6 +3,7 @@ using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Platform;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.Themes.Simple; using Avalonia.Themes.Simple;
using Avalonia.Themes.Fluent; using Avalonia.Themes.Fluent;
@ -51,7 +52,7 @@ namespace ControlCatalog
singleViewLifetime.MainView = new MainView { DataContext = new MainWindowViewModel() }; singleViewLifetime.MainView = new MainView { DataContext = new MainWindowViewModel() };
} }
if (ApplicationLifetime is IActivatableApplicationLifetime activatableApplicationLifetime) if (this.TryGetFeature<IActivatableLifetime>() is {} activatableApplicationLifetime)
{ {
activatableApplicationLifetime.Activated += (sender, args) => activatableApplicationLifetime.Activated += (sender, args) =>
Console.WriteLine($"App activated: {args.Kind}"); Console.WriteLine($"App activated: {args.Kind}");

12
samples/ControlCatalog/Pages/DialogsPage.xaml.cs

@ -3,6 +3,7 @@ using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Security;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
@ -43,7 +44,7 @@ namespace ControlCatalog.Pages
{ {
lastSelectedDirectory = await GetStorageProvider().TryGetWellKnownFolderAsync(folderEnum); lastSelectedDirectory = await GetStorageProvider().TryGetWellKnownFolderAsync(folderEnum);
} }
else else if (!string.IsNullOrWhiteSpace(currentFolderBox.Text))
{ {
if (!Uri.TryCreate(currentFolderBox.Text, UriKind.Absolute, out var folderLink)) if (!Uri.TryCreate(currentFolderBox.Text, UriKind.Absolute, out var folderLink))
{ {
@ -52,7 +53,14 @@ namespace ControlCatalog.Pages
if (folderLink is not null) if (folderLink is not null)
{ {
lastSelectedDirectory = await GetStorageProvider().TryGetFolderFromPathAsync(folderLink); try
{
lastSelectedDirectory = await GetStorageProvider().TryGetFolderFromPathAsync(folderLink);
}
catch (SecurityException)
{
}
} }
} }
}; };

4
src/Android/Avalonia.Android/AndroidPlatform.cs

@ -5,6 +5,7 @@ using Avalonia.Controls;
using Avalonia.Android; using Avalonia.Android;
using Avalonia.Android.Platform; using Avalonia.Android.Platform;
using Avalonia.Android.Platform.Input; using Avalonia.Android.Platform.Input;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
using Avalonia.OpenGL.Egl; using Avalonia.OpenGL.Egl;
@ -81,7 +82,8 @@ namespace Avalonia.Android
.Bind<IPlatformThreadingInterface>().ToConstant(new AndroidThreadingInterface()) .Bind<IPlatformThreadingInterface>().ToConstant(new AndroidThreadingInterface())
.Bind<IPlatformIconLoader>().ToSingleton<PlatformIconLoaderStub>() .Bind<IPlatformIconLoader>().ToSingleton<PlatformIconLoaderStub>()
.Bind<IRenderTimer>().ToConstant(new ChoreographerTimer()) .Bind<IRenderTimer>().ToConstant(new ChoreographerTimer())
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>(); .Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
.Bind<IActivatableLifetime>().ToConstant(new AndroidActivatableLifetime());
var graphics = InitializeGraphics(Options); var graphics = InitializeGraphics(Options);
if (graphics is not null) if (graphics is not null)

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

@ -1,5 +1,9 @@
#nullable enable #nullable enable
using Avalonia.Android.Platform;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Platform;
namespace Avalonia.Android namespace Avalonia.Android
{ {
partial class AvaloniaMainActivity<TApp> where TApp : Application, new() partial class AvaloniaMainActivity<TApp> where TApp : Application, new()
@ -36,11 +40,17 @@ namespace Avalonia.Android
{ {
var builder = CreateAppBuilder(); var builder = CreateAppBuilder();
builder.SetupWithLifetime(new SingleViewLifetime(this)); builder.SetupWithLifetime(new SingleViewLifetime());
s_appBuilder = builder; s_appBuilder = builder;
} }
if (Avalonia.Application.Current?.TryGetFeature<IActivatableLifetime>()
is AndroidActivatableLifetime activatableLifetime)
{
activatableLifetime.Activity = this;
}
View = new AvaloniaView(this); View = new AvaloniaView(this);
if (ViewContent != null) if (ViewContent != null)
{ {

47
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<ActivatedEventArgs> Activated;
public event EventHandler<ActivatedEventArgs> 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);
}
}

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

@ -1,25 +1,11 @@
using System; using Avalonia.Controls;
using Android.App;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
namespace Avalonia.Android namespace Avalonia.Android
{ {
internal class SingleViewLifetime : ISingleViewApplicationLifetime, IActivatableApplicationLifetime internal class SingleViewLifetime : ISingleViewApplicationLifetime
{ {
private readonly Activity _activity;
private AvaloniaView _view; 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 public AvaloniaView View
{ {
@ -36,10 +22,5 @@ namespace Avalonia.Android
} }
public Control MainView { get; set; } 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;
} }
} }

7
src/Avalonia.Base/Platform/IOptionalFeatureProvider.cs

@ -1,22 +1,25 @@
using System; using System;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
// TODO12: move to Avalonia namespace.
namespace Avalonia.Platform; namespace Avalonia.Platform;
public interface IOptionalFeatureProvider public interface IOptionalFeatureProvider
{ {
/// <summary> /// <summary>
/// Queries for an optional feature /// Queries for an optional feature.
/// </summary> /// </summary>
/// <param name="featureType">Feature type</param> /// <param name="featureType">Feature type.</param>
public object? TryGetFeature(Type featureType); public object? TryGetFeature(Type featureType);
} }
public static class OptionalFeatureProviderExtensions public static class OptionalFeatureProviderExtensions
{ {
/// <inheritdoc cref="IOptionalFeatureProvider.TryGetFeature"/>
public static T? TryGetFeature<T>(this IOptionalFeatureProvider provider) where T : class => public static T? TryGetFeature<T>(this IOptionalFeatureProvider provider) where T : class =>
(T?)provider.TryGetFeature(typeof(T)); (T?)provider.TryGetFeature(typeof(T));
/// <inheritdoc cref="IOptionalFeatureProvider.TryGetFeature"/>
public static bool TryGetFeature<T>(this IOptionalFeatureProvider provider, [MaybeNullWhen(false)] out T rv) public static bool TryGetFeature<T>(this IOptionalFeatureProvider provider, [MaybeNullWhen(false)] out T rv)
where T : class where T : class
{ {

10
src/Avalonia.Controls/AppBuilder.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Reflection; using System.Reflection;
using System.Linq; using System.Linq;
@ -58,6 +59,8 @@ namespace Avalonia
/// <summary> /// <summary>
/// Gets a method to override a lifetime factory. /// Gets a method to override a lifetime factory.
/// </summary> /// </summary>
[Obsolete("This property has no effect", true)]
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
public Func<Type, IApplicationLifetime?>? LifetimeOverride { get; private set; } public Func<Type, IApplicationLifetime?>? LifetimeOverride { get; private set; }
/// <summary> /// <summary>
@ -244,13 +247,6 @@ namespace Avalonia
return Self; return Self;
} }
[PrivateApi]
public AppBuilder UseLifetimeOverride(Func<Type, IApplicationLifetime?> func)
{
LifetimeOverride = func;
return Self;
}
/// <summary> /// <summary>
/// Configures platform-specific options /// Configures platform-specific options
/// </summary> /// </summary>

35
src/Avalonia.Controls/Application.cs

@ -30,7 +30,7 @@ namespace Avalonia
/// method. /// method.
/// - Tracks the lifetime of the application. /// - Tracks the lifetime of the application.
/// </remarks> /// </remarks>
public class Application : AvaloniaObject, IDataContextProvider, IGlobalDataTemplates, IGlobalStyles, IThemeVariantHost, IApplicationPlatformEvents public class Application : AvaloniaObject, IDataContextProvider, IGlobalDataTemplates, IGlobalStyles, IThemeVariantHost, IApplicationPlatformEvents, IOptionalFeatureProvider
{ {
/// <summary> /// <summary>
/// The application-global data templates. /// The application-global data templates.
@ -62,7 +62,7 @@ namespace Avalonia
/// <inheritdoc/> /// <inheritdoc/>
public event EventHandler<ResourcesChangedEventArgs>? ResourcesChanged; public event EventHandler<ResourcesChangedEventArgs>? ResourcesChanged;
[Obsolete("Cast ApplicationLifetime to IActivatableApplicationLifetime instead.")] [Obsolete("Use Application.Current.TryGetFeature<IActivatableLifetime>() instead.")]
public event EventHandler<UrlOpenedEventArgs>? UrlsOpened; public event EventHandler<UrlOpenedEventArgs>? UrlsOpened;
/// <inheritdoc/> /// <inheritdoc/>
@ -204,7 +204,7 @@ namespace Avalonia
/// which should always be preferred over a global one, /// which should always be preferred over a global one,
/// as specific top levels might have different settings set-up. /// as specific top levels might have different settings set-up.
/// </remarks> /// </remarks>
public IPlatformSettings? PlatformSettings => AvaloniaLocator.Current.GetService<IPlatformSettings>(); public IPlatformSettings? PlatformSettings => this.TryGetFeature<IPlatformSettings>();
event Action<IReadOnlyList<IStyle>>? IGlobalStyles.GlobalStylesAdded event Action<IReadOnlyList<IStyle>>? IGlobalStyles.GlobalStylesAdded
{ {
@ -329,7 +329,34 @@ namespace Avalonia
get => _name; get => _name;
set => SetAndRaise(NameProperty, ref _name, value); set => SetAndRaise(NameProperty, ref _name, value);
} }
/// <summary>
/// Queries for an optional feature.
/// </summary>
/// <param name="featureType">Feature type.</param>
/// <remarks>
/// Features currently supported by <see cref="Application.TryGetFeature"/>:
/// <list type="bullet">
/// <item>IPlatformSettings</item>
/// <item>IActivatableApplicationLifetime</item>
/// </list>
/// </remarks>
public object? TryGetFeature(Type featureType)
{
if (featureType == typeof(IPlatformSettings))
{
return AvaloniaLocator.Current.GetService<IPlatformSettings>();
}
if (featureType == typeof(IActivatableLifetime))
{
return AvaloniaLocator.Current.GetService<IActivatableLifetime>();
}
// Do not return just any service from AvaloniaLocator.
return null;
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{ {
base.OnPropertyChanged(change); base.OnPropertyChanged(change);

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

@ -217,8 +217,7 @@ namespace Avalonia
private static ClassicDesktopStyleApplicationLifetime PrepareLifetime(AppBuilder builder, string[] args, private static ClassicDesktopStyleApplicationLifetime PrepareLifetime(AppBuilder builder, string[] args,
Action<IClassicDesktopStyleApplicationLifetime>? lifetimeBuilder) Action<IClassicDesktopStyleApplicationLifetime>? lifetimeBuilder)
{ {
var lifetime = builder.LifetimeOverride?.Invoke(typeof(ClassicDesktopStyleApplicationLifetime)) as ClassicDesktopStyleApplicationLifetime var lifetime = new ClassicDesktopStyleApplicationLifetime();
?? new ClassicDesktopStyleApplicationLifetime();
lifetime.SubscribeGlobalEvents(); lifetime.SubscribeGlobalEvents();
lifetime.Args = args; lifetime.Args = args;

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

@ -1,13 +1,20 @@
using System; using System;
using System.Diagnostics;
using Avalonia.Metadata; using Avalonia.Metadata;
namespace Avalonia.Controls.ApplicationLifetimes; namespace Avalonia.Controls.ApplicationLifetimes;
[NotClientImplementable]
[Obsolete("This interface has no effect. Instead use Application.Current.TryGetFeature<IActivatableLifetime>().", true)]
public interface IActivatableApplicationLifetime : IActivatableLifetime {
}
/// <summary> /// <summary>
/// An interface for ApplicationLifetimes where the application can be Activated and Deactivated. /// An interface for ApplicationLifetimes where the application can be Activated and Deactivated.
/// </summary> /// </summary>
[NotClientImplementable] [NotClientImplementable]
public interface IActivatableApplicationLifetime public interface IActivatableLifetime
{ {
/// <summary> /// <summary>
/// An event that is raised when the application is Activated for various reasons /// An event that is raised when the application is Activated for various reasons

16
src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs

@ -9,18 +9,18 @@ namespace Avalonia.Native
internal class AvaloniaNativeApplicationPlatform : NativeCallbackBase, IAvnApplicationEvents, IPlatformLifetimeEventsImpl internal class AvaloniaNativeApplicationPlatform : NativeCallbackBase, IAvnApplicationEvents, IPlatformLifetimeEventsImpl
{ {
public event EventHandler<ShutdownRequestedEventArgs> ShutdownRequested; public event EventHandler<ShutdownRequestedEventArgs> ShutdownRequested;
void IAvnApplicationEvents.FilesOpened(IAvnStringArray urls) void IAvnApplicationEvents.FilesOpened(IAvnStringArray urls)
{ {
((IApplicationPlatformEvents)Application.Current).RaiseUrlsOpened(urls.ToStringArray()); ((IApplicationPlatformEvents)Application.Current)?.RaiseUrlsOpened(urls.ToStringArray());
} }
void IAvnApplicationEvents.UrlsOpened(IAvnStringArray urls) void IAvnApplicationEvents.UrlsOpened(IAvnStringArray urls)
{ {
// Raise the urls opened event to be compatible with legacy behavior. // 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<IActivatableLifetime>() is MacOSActivatableLifetime lifetime)
{ {
foreach (var url in urls.ToStringArray()) foreach (var url in urls.ToStringArray())
{ {
@ -34,7 +34,7 @@ namespace Avalonia.Native
void IAvnApplicationEvents.OnReopen() void IAvnApplicationEvents.OnReopen()
{ {
if (Application.Current?.ApplicationLifetime is MacOSClassicDesktopStyleApplicationLifetime lifetime) if (AvaloniaLocator.Current.GetService<IActivatableLifetime>() is MacOSActivatableLifetime lifetime)
{ {
lifetime.RaiseActivated(ActivationKind.Reopen); lifetime.RaiseActivated(ActivationKind.Reopen);
} }
@ -42,7 +42,7 @@ namespace Avalonia.Native
void IAvnApplicationEvents.OnHide() void IAvnApplicationEvents.OnHide()
{ {
if (Application.Current?.ApplicationLifetime is MacOSClassicDesktopStyleApplicationLifetime lifetime) if (AvaloniaLocator.Current.GetService<IActivatableLifetime>() is MacOSActivatableLifetime lifetime)
{ {
lifetime.RaiseDeactivated(ActivationKind.Background); lifetime.RaiseDeactivated(ActivationKind.Background);
} }
@ -50,7 +50,7 @@ namespace Avalonia.Native
void IAvnApplicationEvents.OnUnhide() void IAvnApplicationEvents.OnUnhide()
{ {
if (Application.Current?.ApplicationLifetime is MacOSClassicDesktopStyleApplicationLifetime lifetime) if (AvaloniaLocator.Current.GetService<IActivatableLifetime>() is MacOSActivatableLifetime lifetime)
{ {
lifetime.RaiseActivated(ActivationKind.Background); lifetime.RaiseActivated(ActivationKind.Background);
} }

4
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -1,6 +1,7 @@
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Avalonia.Compatibility; using Avalonia.Compatibility;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
@ -112,7 +113,8 @@ namespace Avalonia.Native
.Bind<IMountedVolumeInfoProvider>().ToConstant(new MacOSMountedVolumeInfoProvider()) .Bind<IMountedVolumeInfoProvider>().ToConstant(new MacOSMountedVolumeInfoProvider())
.Bind<IPlatformDragSource>().ToConstant(new AvaloniaNativeDragSource(_factory)) .Bind<IPlatformDragSource>().ToConstant(new AvaloniaNativeDragSource(_factory))
.Bind<IPlatformLifetimeEventsImpl>().ToConstant(applicationPlatform) .Bind<IPlatformLifetimeEventsImpl>().ToConstant(applicationPlatform)
.Bind<INativeApplicationCommands>().ToConstant(new MacOSNativeMenuCommands(_factory.CreateApplicationCommands())); .Bind<INativeApplicationCommands>().ToConstant(new MacOSNativeMenuCommands(_factory.CreateApplicationCommands()))
.Bind<IActivatableLifetime>().ToSingleton<MacOSActivatableLifetime>();
var hotkeys = new PlatformHotkeyConfiguration(KeyModifiers.Meta, wholeWordTextActionModifiers: KeyModifiers.Alt); var hotkeys = new PlatformHotkeyConfiguration(KeyModifiers.Meta, wholeWordTextActionModifiers: KeyModifiers.Alt);
hotkeys.MoveCursorToTheStartOfLine.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers)); hotkeys.MoveCursorToTheStartOfLine.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers));

4
src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs

@ -23,9 +23,7 @@ namespace Avalonia
platform.SetupApplicationName(); platform.SetupApplicationName();
platform.SetupApplicationMenuExporter(); platform.SetupApplicationMenuExporter();
}); });
}) });
.UseLifetimeOverride(type => type == typeof(ClassicDesktopStyleApplicationLifetime)
? new MacOSClassicDesktopStyleApplicationLifetime() : null);
return builder; return builder;
} }

3
src/Avalonia.Native/MacOSClassicDesktopStyleApplicationLifetime.cs → src/Avalonia.Native/MacOSActivatableLifetime.cs

@ -6,8 +6,7 @@ namespace Avalonia.Native;
#nullable enable #nullable enable
internal class MacOSClassicDesktopStyleApplicationLifetime : ClassicDesktopStyleApplicationLifetime, internal class MacOSActivatableLifetime : IActivatableLifetime
IActivatableApplicationLifetime
{ {
/// <inheritdoc /> /// <inheritdoc />
public event EventHandler<ActivatedEventArgs>? Activated; public event EventHandler<ActivatedEventArgs>? Activated;

36
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<ActivatedEventArgs>? Activated;
public event EventHandler<ActivatedEventArgs>? Deactivated;
public bool TryLeaveBackground() => false;
public bool TryEnterBackground() => false;
}

20
src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs

@ -2,24 +2,12 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using System.Runtime.Versioning;
using Avalonia.Browser; using Avalonia.Browser;
using Avalonia.Browser.Interop;
using Avalonia.Threading;
namespace Avalonia; 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 AvaloniaView? View;
public Control? MainView 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."); 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() => false;
} }

4
src/Browser/Avalonia.Browser/WindowingPlatform.cs

@ -1,6 +1,7 @@
using System; using System;
using Avalonia.Browser.Interop; using Avalonia.Browser.Interop;
using Avalonia.Browser.Skia; using Avalonia.Browser.Skia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
using Avalonia.Platform; using Avalonia.Platform;
@ -43,7 +44,8 @@ internal class BrowserWindowingPlatform : IWindowingPlatform
.Bind<IWindowingPlatform>().ToConstant(instance) .Bind<IWindowingPlatform>().ToConstant(instance)
.Bind<IPlatformGraphics>().ToConstant(new BrowserSkiaGraphics()) .Bind<IPlatformGraphics>().ToConstant(new BrowserSkiaGraphics())
.Bind<IPlatformIconLoader>().ToSingleton<IconLoaderStub>() .Bind<IPlatformIconLoader>().ToSingleton<IconLoaderStub>()
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>(); .Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
.Bind<IActivatableLifetime>().ToSingleton<BrowserActivatableLifetime>();
if (AvaloniaLocator.Current.GetService<BrowserPlatformOptions>() is { } options if (AvaloniaLocator.Current.GetService<BrowserPlatformOptions>() is { } options
&& options.RegisterAvaloniaServiceWorker) && options.RegisterAvaloniaServiceWorker)

18
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<ActivatedEventArgs>? Activated;
public event EventHandler<ActivatedEventArgs>? Deactivated;
public bool TryLeaveBackground() => false;
public bool TryEnterBackground() => false;
}

4
src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs

@ -42,9 +42,9 @@ namespace Avalonia.iOS
[Export("application:didFinishLaunchingWithOptions:")] [Export("application:didFinishLaunchingWithOptions:")]
public bool FinishedLaunching(UIApplication application, NSDictionary launchOptions) public bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
{ {
var builder = AppBuilder.Configure<TApp>().UseiOS(); var builder = AppBuilder.Configure<TApp>().UseiOS(this);
var lifetime = new SingleViewLifetime(this); var lifetime = new SingleViewLifetime();
builder.AfterSetup(_ => builder.AfterSetup(_ =>
{ {

16
src/iOS/Avalonia.iOS/Platform.cs

@ -1,8 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
using Avalonia.iOS;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Rendering.Composition; using Avalonia.Rendering.Composition;
@ -39,13 +41,15 @@ namespace Avalonia
public static class IOSApplicationExtensions public static class IOSApplicationExtensions
{ {
public static AppBuilder UseiOS(this AppBuilder builder) public static AppBuilder UseiOS(this AppBuilder builder, IAvaloniaAppDelegate appDelegate)
{ {
return builder return builder
.UseStandardRuntimePlatformSubsystem() .UseStandardRuntimePlatformSubsystem()
.UseWindowingSubsystem(iOS.Platform.Register, "iOS") .UseWindowingSubsystem(() => iOS.Platform.Register(appDelegate), "iOS")
.UseSkia(); .UseSkia();
} }
public static AppBuilder UseiOS(this AppBuilder builder) => UseiOS(builder, null!);
} }
} }
@ -58,7 +62,7 @@ namespace Avalonia.iOS
public static DisplayLinkTimer? Timer; public static DisplayLinkTimer? Timer;
internal static Compositor? Compositor { get; private set; } internal static Compositor? Compositor { get; private set; }
public static void Register() public static void Register(IAvaloniaAppDelegate? appDelegate)
{ {
Options = AvaloniaLocator.Current.GetService<iOSPlatformOptions>() ?? new iOSPlatformOptions(); Options = AvaloniaLocator.Current.GetService<iOSPlatformOptions>() ?? new iOSPlatformOptions();
@ -77,6 +81,12 @@ namespace Avalonia.iOS
.Bind<IDispatcherImpl>().ToConstant(DispatcherImpl.Instance) .Bind<IDispatcherImpl>().ToConstant(DispatcherImpl.Instance)
.Bind<IKeyboardDevice>().ToConstant(keyboard); .Bind<IKeyboardDevice>().ToConstant(keyboard);
if (appDelegate is not null)
{
AvaloniaLocator.CurrentMutable
.Bind<IActivatableLifetime>().ToConstant(new ActivatableLifetime(appDelegate));
}
Compositor = new Compositor(AvaloniaLocator.Current.GetService<IPlatformGraphics>()); Compositor = new Compositor(AvaloniaLocator.Current.GetService<IPlatformGraphics>());
AvaloniaLocator.CurrentMutable.Bind<Compositor>().ToConstant(Compositor); AvaloniaLocator.CurrentMutable.Bind<Compositor>().ToConstant(Compositor);
} }

15
src/iOS/Avalonia.iOS/SingleViewLifetime.cs

@ -4,14 +4,8 @@ using Avalonia.Controls.ApplicationLifetimes;
namespace Avalonia.iOS; namespace Avalonia.iOS;
internal class SingleViewLifetime : ISingleViewApplicationLifetime, IActivatableApplicationLifetime internal class SingleViewLifetime : ISingleViewApplicationLifetime
{ {
public SingleViewLifetime(IAvaloniaAppDelegate avaloniaAppDelegate)
{
avaloniaAppDelegate.Activated += (_, args) => Activated?.Invoke(this, args);
avaloniaAppDelegate.Deactivated += (_, args) => Deactivated?.Invoke(this, args);
}
public AvaloniaView? View; public AvaloniaView? View;
public Control? MainView public Control? MainView
@ -19,9 +13,4 @@ internal class SingleViewLifetime : ISingleViewApplicationLifetime, IActivatable
get => View!.Content; get => View!.Content;
set => View!.Content = value; 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