Browse Source

Fix android and add top level info early (#15158)

* Add and implement ISingleTopLevelApplicationLifetime (internal API)

* Fix Android initialization order, so we can have AfterSetup with usable TopLevel in the callback

* Fix android and futher simplify its initialization

* Return removed public API, make it all work together, introduce AvaloniaActivity

* Adjust some comments and minor bug fixes

* Add CreateAppBuilder to iOS and Tizen as well for consistency

* Add AfterApplicationSetup private api, so our backends have a safe place to setup avalonia views.

* Keep number of breaking changes minimal
pull/15074/head
Max Katz 2 years ago
committed by GitHub
parent
commit
cc52217c09
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 170
      src/Android/Avalonia.Android/AvaloniaActivity.cs
  2. 65
      src/Android/Avalonia.Android/AvaloniaMainActivity.App.cs
  3. 174
      src/Android/Avalonia.Android/AvaloniaMainActivity.cs
  4. 4
      src/Android/Avalonia.Android/AvaloniaView.cs
  5. 8
      src/Android/Avalonia.Android/IAvaloniaActivity.cs
  6. 49
      src/Android/Avalonia.Android/SingleViewLifetime.cs
  7. 17
      src/Avalonia.Controls/AppBuilder.cs
  8. 15
      src/Avalonia.Controls/ApplicationLifetimes/ISingleTopLevelApplicationLifetime.cs
  9. 20
      src/Browser/Avalonia.Browser/AvaloniaView.cs
  10. 2
      src/Browser/Avalonia.Browser/BrowserAppBuilder.cs
  11. 4
      src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs
  12. 2
      src/Headless/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs
  13. 55
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
  14. 2
      src/Tizen/Avalonia.Tizen/NuiAvaloniaView.cs
  15. 12
      src/Tizen/Avalonia.Tizen/NuiTizenApplication.cs
  16. 12
      src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs
  17. 1
      src/iOS/Avalonia.iOS/AvaloniaView.cs
  18. 39
      src/iOS/Avalonia.iOS/SingleViewLifetime.cs

170
src/Android/Avalonia.Android/AvaloniaActivity.cs

@ -0,0 +1,170 @@
#nullable enable
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.Versioning;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.OS;
using Android.Runtime;
using Android.Views;
using AndroidX.AppCompat.App;
using Avalonia.Controls.ApplicationLifetimes;
namespace Avalonia.Android;
/// <summary>
/// Common implementation of android activity that is integrated with Avalonia views.
/// If you need a base class for main activity of Avalonia app, see <see cref="AvaloniaMainActivity"/> or <see cref="AvaloniaMainActivity{TApp}"/>.
/// </summary>
public class AvaloniaActivity : AppCompatActivity, IAvaloniaActivity
{
private EventHandler<ActivatedEventArgs>? _onActivated, _onDeactivated;
private GlobalLayoutListener? _listener;
private object? _content;
internal AvaloniaView? _view;
public Action<int, Result, Intent?>? ActivityResult { get; set; }
public Action<int, string[], Permission[]>? RequestPermissionsResult { get; set; }
public event EventHandler<AndroidBackRequestedEventArgs>? BackRequested;
public object? Content
{
get => _content;
set
{
if (_content != value)
{
_content = value;
if (_view is not null)
{
_view.Content = _content;
}
}
}
}
event EventHandler<ActivatedEventArgs>? IAvaloniaActivity.Activated
{
add { _onActivated += value; }
remove { _onActivated -= value; }
}
event EventHandler<ActivatedEventArgs>? IAvaloniaActivity.Deactivated
{
add { _onDeactivated += value; }
remove { _onDeactivated -= value; }
}
[ObsoletedOSPlatform("android33.0")]
public override void OnBackPressed()
{
var eventArgs = new AndroidBackRequestedEventArgs();
BackRequested?.Invoke(this, eventArgs);
if (!eventArgs.Handled)
{
base.OnBackPressed();
}
}
protected override void OnCreate(Bundle? savedInstanceState)
{
InitializeAvaloniaView(_content);
base.OnCreate(savedInstanceState);
SetContentView(_view);
_listener = new GlobalLayoutListener(_view);
_view.ViewTreeObserver?.AddOnGlobalLayoutListener(_listener);
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 OnResume()
{
base.OnResume();
// Android only respects LayoutInDisplayCutoutMode value if it has been set once before window becomes visible.
if (OperatingSystem.IsAndroidVersionAtLeast(28) && Window is { Attributes: { } attributes })
{
attributes.LayoutInDisplayCutoutMode = LayoutInDisplayCutoutMode.ShortEdges;
}
}
protected override void OnDestroy()
{
if (_view is not null)
{
_view.Content = null;
_view.ViewTreeObserver?.RemoveOnGlobalLayoutListener(_listener);
_view.Dispose();
_view = null;
}
base.OnDestroy();
}
protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent? data)
{
base.OnActivityResult(requestCode, resultCode, data);
ActivityResult?.Invoke(requestCode, resultCode, data);
}
[SupportedOSPlatform("android23.0")]
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
{
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
RequestPermissionsResult?.Invoke(requestCode, permissions, grantResults);
}
[MemberNotNull(nameof(_view))]
private protected virtual void InitializeAvaloniaView(object? initialContent)
{
if (Avalonia.Application.Current is null)
{
throw new InvalidOperationException(
"Avalonia Application was not initialized. Make sure you have created AvaloniaMainActivity.");
}
_view = new AvaloniaView(this) { Content = initialContent };
}
private class GlobalLayoutListener : Java.Lang.Object, ViewTreeObserver.IOnGlobalLayoutListener
{
private readonly AvaloniaView _view;
public GlobalLayoutListener(AvaloniaView view)
{
_view = view;
}
public void OnGlobalLayout()
{
_view.TopLevelImpl?.Resize(_view.TopLevelImpl.ClientSize);
}
}
}

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

@ -1,66 +1,15 @@
#nullable enable
using Android.OS;
using Android.Views;
using Avalonia.Android.Platform;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Platform;
namespace Avalonia.Android
{
partial class AvaloniaMainActivity<TApp> where TApp : Application, new()
{
protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder.UseAndroid();
private static AppBuilder? s_appBuilder;
internal static object? ViewContent;
public object? Content
{
get
{
return ViewContent;
}
set
{
ViewContent = value;
if (View != null)
View.Content = value;
}
}
protected AppBuilder CreateAppBuilder()
{
var builder = AppBuilder.Configure<TApp>();
return CustomizeAppBuilder(builder);
}
private void InitializeApp()
{
if (s_appBuilder == null)
{
var builder = CreateAppBuilder();
namespace Avalonia.Android;
builder.SetupWithLifetime(new SingleViewLifetime());
s_appBuilder = builder;
}
if (Avalonia.Application.Current?.TryGetFeature<IActivatableLifetime>()
is AndroidActivatableLifetime activatableLifetime)
{
activatableLifetime.Activity = this;
}
View = new AvaloniaView(this);
if (ViewContent != null)
{
View.Content = ViewContent;
}
if (Avalonia.Application.Current?.ApplicationLifetime is SingleViewLifetime lifetime)
{
lifetime.View = View;
}
}
}
public class AvaloniaMainActivity<TApp> : AvaloniaMainActivity
where TApp : Application, new()
{
protected override AppBuilder CreateAppBuilder() => AppBuilder.Configure<TApp>();
}

174
src/Android/Avalonia.Android/AvaloniaMainActivity.cs

@ -1,141 +1,63 @@
#nullable enable
using System;
using System.Runtime.Versioning;
using Android.App;
using Android.Content;
using Android.Content.PM;
using System.Diagnostics.CodeAnalysis;
using Android.OS;
using Android.Runtime;
using Android.Views;
using AndroidX.AppCompat.App;
using Avalonia.Android.Platform;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Platform;
namespace Avalonia.Android
{
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; }
namespace Avalonia.Android;
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; }
}
[ObsoletedOSPlatform("android33.0")]
public override void OnBackPressed()
{
var eventArgs = new AndroidBackRequestedEventArgs();
BackRequested?.Invoke(this, eventArgs);
if (!eventArgs.Handled)
{
base.OnBackPressed();
}
}
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();
}
public class AvaloniaMainActivity : AvaloniaActivity
{
private protected static SingleViewLifetime? Lifetime;
protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data)
public override void OnCreate(Bundle? savedInstanceState, PersistableBundle? persistentState)
{
// Global IActivatableLifetime expects a main activity, so we need to replace it on each OnCreate.
if (Avalonia.Application.Current?.TryGetFeature<IActivatableLifetime>()
is AndroidActivatableLifetime activatableLifetime)
{
base.OnActivityResult(requestCode, resultCode, data);
ActivityResult?.Invoke(requestCode, resultCode, data);
activatableLifetime.Activity = this;
}
[SupportedOSPlatform("android23.0")]
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
{
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
RequestPermissionsResult?.Invoke(requestCode, permissions, grantResults);
}
base.OnCreate(savedInstanceState, persistentState);
}
public abstract partial class AvaloniaMainActivity<TApp> : AvaloniaMainActivity where TApp : Application, new()
private protected override void InitializeAvaloniaView(object? initialContent)
{
internal AvaloniaView View { get; set; }
private GlobalLayoutListener _listener;
protected override void OnCreate(Bundle savedInstanceState)
{
InitializeApp();
base.OnCreate(savedInstanceState);
SetContentView(View);
_listener = new GlobalLayoutListener(View);
View.ViewTreeObserver?.AddOnGlobalLayoutListener(_listener);
}
protected override void OnResume()
{
base.OnResume();
// Android only respects LayoutInDisplayCutoutMode value if it has been set once before window becomes visible.
if (OperatingSystem.IsAndroidVersionAtLeast(28) && Window is { Attributes: { } attributes })
{
attributes.LayoutInDisplayCutoutMode = LayoutInDisplayCutoutMode.ShortEdges;
}
}
protected override void OnDestroy()
{
View.Content = null;
View.ViewTreeObserver?.RemoveOnGlobalLayoutListener(_listener);
base.OnDestroy();
}
private class GlobalLayoutListener : Java.Lang.Object, ViewTreeObserver.IOnGlobalLayoutListener
{
private readonly AvaloniaView _view;
public GlobalLayoutListener(AvaloniaView view)
{
_view = view;
}
public void OnGlobalLayout()
{
_view.TopLevelImpl?.Resize(_view.TopLevelImpl.ClientSize);
}
// Android can run OnCreate + InitializeAvaloniaView multiple times per process lifetime.
// On each call we need to create new AvaloniaView, but we can't recreate Avalonia nor Avalonia controls.
// So, if lifetime was already created previously - recreate AvaloniaView.
// If not, initialize Avalonia, and create AvaloniaView inside of AfterSetup callback.
// We need this AfterSetup callback to match iOS/Browser behavior and ensure that view/toplevel is available in custom AfterSetup calls.
if (Lifetime is not null)
{
Lifetime.Activity = this;
_view = new AvaloniaView(this) { Content = initialContent };
}
else
{
var builder = CreateAppBuilder();
builder = CustomizeAppBuilder(builder);
Lifetime = new SingleViewLifetime();
Lifetime.Activity = this;
builder
.AfterApplicationSetup(_ =>
{
_view = new AvaloniaView(this) { Content = initialContent };
})
.SetupWithLifetime(Lifetime);
// AfterPlatformServicesSetup should always be called. If it wasn't, we have an unusual problem.
if (_view is null)
throw new InvalidOperationException("Unknown error: AvaloniaView initialization has failed.");
}
}
protected virtual AppBuilder CreateAppBuilder() => AppBuilder.Configure<Application>().UseAndroid();
protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder;
}

4
src/Android/Avalonia.Android/AvaloniaView.cs

@ -35,6 +35,7 @@ namespace Avalonia.Android
}
internal TopLevelImpl TopLevelImpl => _view;
internal TopLevel TopLevel => _root;
public object Content
{
@ -121,9 +122,6 @@ namespace Avalonia.Android
MaxClientSize = size;
base.OnResized(size);
}
public WindowState WindowState { get; set; }
public IDisposable ShowDialog() => null;
}
}
}

8
src/Android/Avalonia.Android/IAvaloniaActivity.cs

@ -1,10 +1,12 @@
using System;
#nullable enable
using System;
using Avalonia.Controls.ApplicationLifetimes;
namespace Avalonia.Android;
public interface IAvaloniaActivity : IActivityResultHandler, IActivityNavigationService
{
event EventHandler<ActivatedEventArgs> Activated;
event EventHandler<ActivatedEventArgs> Deactivated;
object? Content { get; set; }
event EventHandler<ActivatedEventArgs>? Activated;
event EventHandler<ActivatedEventArgs>? Deactivated;
}

49
src/Android/Avalonia.Android/SingleViewLifetime.cs

@ -1,26 +1,47 @@
using Avalonia.Controls;
#nullable enable
using System.Diagnostics.CodeAnalysis;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
namespace Avalonia.Android
namespace Avalonia.Android;
internal class SingleViewLifetime : ISingleViewApplicationLifetime, ISingleTopLevelApplicationLifetime
{
internal class SingleViewLifetime : ISingleViewApplicationLifetime
{
private AvaloniaView _view;
private Control? _mainView;
private AvaloniaMainActivity? _activity;
public AvaloniaView View
/// <summary>
/// Since Main Activity can be swapped, we should adjust litetime as well.
/// </summary>
public AvaloniaMainActivity Activity
{
[return: MaybeNull] get => _activity!;
internal set
{
if (_activity != null)
{
_activity.Content = null;
}
_activity = value;
_activity.Content = _mainView;
}
}
public Control? MainView
{
get => _mainView;
set
{
get => _view; internal set
if (_mainView != value)
{
if (_view != null)
_mainView = value;
if (_activity != null)
{
_view.Content = null;
_view.Dispose();
_activity.Content = _mainView;
}
_view = value;
_view.Content = MainView;
}
}
public Control MainView { get; set; }
}
public TopLevel? TopLevel => _activity?._view?.TopLevel;
}

17
src/Avalonia.Controls/AppBuilder.cs

@ -69,10 +69,14 @@ namespace Avalonia
public string? RenderingSubsystemName { get; private set; }
/// <summary>
/// Gets or sets a method to call after the <see cref="Application"/> is setup.
/// Gets a method to call after the <see cref="Application"/> is setup.
/// </summary>
public Action<AppBuilder> AfterSetupCallback { get; private set; } = builder => { };
/// <summary>
/// Callbacks that are commonly used by backends to initialize avalonia views.
/// </summary>
private Action<AppBuilder> AfterApplicationSetupCallback { get; set; } = builder => { };
public Action<AppBuilder> AfterPlatformServicesSetupCallback { get; private set; } = builder => { };
@ -159,8 +163,14 @@ namespace Avalonia
AfterSetupCallback = (Action<AppBuilder>)Delegate.Combine(AfterSetupCallback, callback);
return Self;
}
[PrivateApi]
public AppBuilder AfterApplicationSetup(Action<AppBuilder> callback)
{
AfterApplicationSetupCallback = (Action<AppBuilder>)Delegate.Combine(AfterPlatformServicesSetupCallback, callback);
return Self;
}
public AppBuilder AfterPlatformServicesSetup(Action<AppBuilder> callback)
{
AfterPlatformServicesSetupCallback = (Action<AppBuilder>)Delegate.Combine(AfterPlatformServicesSetupCallback, callback);
@ -328,6 +338,7 @@ namespace Avalonia
AvaloniaLocator.CurrentMutable.BindToSelf(Instance);
Instance.RegisterServices();
Instance.Initialize();
AfterApplicationSetupCallback?.Invoke(Self);
AfterSetupCallback?.Invoke(Self);
Instance.OnFrameworkInitializationCompleted();
}

15
src/Avalonia.Controls/ApplicationLifetimes/ISingleTopLevelApplicationLifetime.cs

@ -0,0 +1,15 @@
using System.ComponentModel;
using Avalonia.Metadata;
namespace Avalonia.Controls.ApplicationLifetimes;
/// <summary>
/// Used in our internal projects. Until we figure out way to add this information to the public API.
/// </summary>
[NotClientImplementable]
[PrivateApi]
[EditorBrowsable(EditorBrowsableState.Never)]
public interface ISingleTopLevelApplicationLifetime : IApplicationLifetime
{
TopLevel? TopLevel { get; }
}

20
src/Browser/Avalonia.Browser/AvaloniaView.cs

@ -140,6 +140,16 @@ namespace Avalonia.Browser
InputHelper.FocusElement(_containerElement);
}
public Control? Content
{
get => (Control)_topLevel.Content!;
set => _topLevel.Content = value;
}
public bool IsComposing { get; private set; }
internal TopLevel TopLevel => _topLevel;
private static RawPointerPoint ExtractRawPointerFromJSArgs(JSObject args)
{
var point = new RawPointerPoint
@ -429,15 +439,7 @@ namespace Avalonia.Browser
Dispatcher.UIThread.RunJobs(DispatcherPriority.UiThreadRender);
ManualTriggerRenderTimer.Instance.RaiseTick();
}
public Control? Content
{
get => (Control)_topLevel.Content!;
set => _topLevel.Content = value;
}
public bool IsComposing { get; private set; }
internal INativeControlHostImpl GetNativeControlHostImpl()
{
return new BrowserNativeControlHost(_nativeControlsContainer);

2
src/Browser/Avalonia.Browser/BrowserAppBuilder.cs

@ -55,7 +55,7 @@ public static class BrowserAppBuilder
var lifetime = new BrowserSingleViewLifetime();
builder
.AfterSetup(_ =>
.AfterApplicationSetup(_ =>
{
lifetime.View = new AvaloniaView(mainDivId);
})

4
src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs

@ -6,7 +6,7 @@ using Avalonia.Browser;
namespace Avalonia;
internal class BrowserSingleViewLifetime : ISingleViewApplicationLifetime
internal class BrowserSingleViewLifetime : ISingleViewApplicationLifetime, ISingleTopLevelApplicationLifetime
{
public AvaloniaView? View;
@ -32,4 +32,6 @@ internal class BrowserSingleViewLifetime : ISingleViewApplicationLifetime
throw new InvalidOperationException("Browser lifetime was not initialized. Make sure AppBuilder.StartBrowserApp was called.");
}
}
public TopLevel? TopLevel => View?.TopLevel;
}

2
src/Headless/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs

@ -26,7 +26,7 @@ namespace Avalonia
UseHeadlessDrawing = false,
FrameBufferFormat = PixelFormat.Bgra8888
})
.AfterSetup(_ =>
.AfterApplicationSetup(_ =>
{
var lt = ((IClassicDesktopStyleApplicationLifetime)builder.Instance!.ApplicationLifetime!);
lt.Startup += async delegate

55
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs

@ -1,5 +1,6 @@
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading;
using Avalonia;
@ -88,7 +89,7 @@ namespace Avalonia.LinuxFramebuffer
}
}
class LinuxFramebufferLifetime : IControlledApplicationLifetime, ISingleViewApplicationLifetime
class LinuxFramebufferLifetime : IControlledApplicationLifetime, ISingleViewApplicationLifetime, ISingleTopLevelApplicationLifetime
{
private readonly IOutputBackend _fb;
private readonly IInputBackend? _inputBackend;
@ -114,25 +115,7 @@ namespace Avalonia.LinuxFramebuffer
{
if (_topLevel == null)
{
var inputBackend = _inputBackend;
if (inputBackend == null)
{
if (Environment.GetEnvironmentVariable("AVALONIA_USE_EVDEV") == "1")
inputBackend = EvDevBackend.CreateFromEnvironment();
else
inputBackend = new LibInputBackend();
}
var tl = new EmbeddableControlRoot(new FramebufferToplevelImpl(_fb, inputBackend));
tl.Prepare();
tl.StartRendering();
_topLevel = tl;
if (_topLevel is IFocusScope scope && _topLevel.FocusManager is FocusManager focusManager)
{
focusManager.SetFocusScope(scope);
}
EnsureTopLevel();
}
_topLevel.Content = value;
@ -156,6 +139,38 @@ namespace Avalonia.LinuxFramebuffer
ExitCode = e.ApplicationExitCode;
_cts.Cancel();
}
public TopLevel? TopLevel
{
get
{
EnsureTopLevel();
return _topLevel;
}
}
[MemberNotNull(nameof(_topLevel))]
private void EnsureTopLevel()
{
var inputBackend = _inputBackend;
if (inputBackend == null)
{
if (Environment.GetEnvironmentVariable("AVALONIA_USE_EVDEV") == "1")
inputBackend = EvDevBackend.CreateFromEnvironment();
else
inputBackend = new LibInputBackend();
}
var tl = new EmbeddableControlRoot(new FramebufferToplevelImpl(_fb, inputBackend));
tl.Prepare();
tl.StartRendering();
_topLevel = tl;
if (_topLevel is IFocusScope scope && _topLevel.FocusManager is FocusManager focusManager)
{
focusManager.SetFocusScope(scope);
}
}
}
}

2
src/Tizen/Avalonia.Tizen/NuiAvaloniaView.cs

@ -36,7 +36,7 @@ public class NuiAvaloniaView : GLView, ITizenView, ITextInputMethodImpl
set => _inputRoot = value;
}
private TopLevel TopLevel
internal TopLevel TopLevel
=> _topLevel ?? throw new InvalidOperationException($"{nameof(NuiAvaloniaView)} hasn't been initialized");
internal TopLevelImpl TopLevelImpl

12
src/Tizen/Avalonia.Tizen/NuiTizenApplication.cs

@ -13,7 +13,7 @@ public class NuiTizenApplication<TApp> : NUIApplication
private SingleViewLifetime? _lifetime;
private class SingleViewLifetime : ISingleViewApplicationLifetime
private class SingleViewLifetime : ISingleViewApplicationLifetime, ISingleTopLevelApplicationLifetime
{
public NuiAvaloniaView View { get; }
@ -27,8 +27,11 @@ public class NuiTizenApplication<TApp> : NUIApplication
get => View.Content;
set => View.Content = value;
}
public TopLevel? TopLevel => View.TopLevel;
}
protected virtual AppBuilder CreateAppBuilder() => AppBuilder.Configure<Application>().UseTizen();
protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder;
protected override void OnCreate()
@ -55,13 +58,12 @@ public class NuiTizenApplication<TApp> : NUIApplication
SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
Logger.TryGet(LogEventLevel.Debug, LogKey)?.Log(null, "App builder");
var builder = AppBuilder.Configure<TApp>().UseTizen();
var builder = CreateAppBuilder();
TizenThreadingInterface.MainloopContext.Post(_ =>
{
CustomizeAppBuilder(builder);
builder.AfterSetup(_ => _lifetime!.View.Initialise());
builder = CustomizeAppBuilder(builder);
builder.AfterApplicationSetup(_ => _lifetime!.View.Initialise());
Logger.TryGet(LogEventLevel.Debug, LogKey)?.Log(null, "Setup lifetime");
builder.SetupWithLifetime(_lifetime!);

12
src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs

@ -33,20 +33,22 @@ namespace Avalonia.iOS
add { _onDeactivated += value; }
remove { _onDeactivated -= value; }
}
protected virtual AppBuilder CreateAppBuilder() => AppBuilder.Configure<Application>().UseiOS();
protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder;
[Export("window")]
public UIWindow? Window { get; set; }
[Export("application:didFinishLaunchingWithOptions:")]
public bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
{
var builder = AppBuilder.Configure<TApp>().UseiOS(this);
var builder = CreateAppBuilder();
builder = CustomizeAppBuilder(builder);
var lifetime = new SingleViewLifetime();
builder.AfterSetup(_ =>
builder.AfterApplicationSetup(_ =>
{
Window = new UIWindow();
@ -60,8 +62,6 @@ namespace Avalonia.iOS
view.InitWithController(controller);
});
CustomizeAppBuilder(builder);
builder.SetupWithLifetime(lifetime);
Window!.MakeKeyAndVisible();

1
src/iOS/Avalonia.iOS/AvaloniaView.cs

@ -29,6 +29,7 @@ namespace Avalonia.iOS
{
internal IInputRoot InputRoot
=> _inputRoot ?? throw new InvalidOperationException($"{nameof(IWindowImpl.SetInputRoot)} must have been called");
internal TopLevel TopLevel => _topLevel;
private readonly TopLevelImpl _topLevelImpl;
private readonly EmbeddableControlRoot _topLevel;

39
src/iOS/Avalonia.iOS/SingleViewLifetime.cs

@ -1,16 +1,45 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
namespace Avalonia.iOS;
internal class SingleViewLifetime : ISingleViewApplicationLifetime
{
public AvaloniaView? View;
internal class SingleViewLifetime : ISingleViewApplicationLifetime, ISingleTopLevelApplicationLifetime
{
private Control? _mainView;
private AvaloniaView? _view;
public AvaloniaView View
{
[return: MaybeNull] get => _view!;
internal set
{
if (_view != null)
{
_view.Content = null;
_view.Dispose();
}
_view = value;
_view.Content = _mainView;
}
}
public Control? MainView
{
get => View!.Content;
set => View!.Content = value;
get => _mainView;
set
{
if (_mainView != value)
{
_mainView = value;
if (_view != null)
{
_view.Content = _mainView;
}
}
}
}
public TopLevel? TopLevel => View?.TopLevel;
}

Loading…
Cancel
Save