csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
256 lines
7.5 KiB
256 lines
7.5 KiB
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 Android.Window;
|
|
using AndroidX.AppCompat.App;
|
|
using Avalonia.Android.Platform;
|
|
using Avalonia.Android.Platform.Storage;
|
|
using Avalonia.Controls.ApplicationLifetimes;
|
|
using Avalonia.Platform;
|
|
|
|
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"/>.
|
|
/// </summary>
|
|
public class AvaloniaActivity : AppCompatActivity, IAvaloniaActivity
|
|
{
|
|
private EventHandler<ActivatedEventArgs>? _onActivated, _onDeactivated;
|
|
private GlobalLayoutListener? _listener;
|
|
private object? _content;
|
|
private bool _contentViewSet;
|
|
internal AvaloniaView? _view;
|
|
private BackPressedCallback? _currentBackPressedCallback;
|
|
private bool _shouldNavigateBack;
|
|
|
|
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)
|
|
{
|
|
if (!_contentViewSet)
|
|
{
|
|
_contentViewSet = true;
|
|
|
|
SetContentView(_view);
|
|
|
|
// By default, the view isn't focused if the activity is created anew, so we force focus.
|
|
_view.RequestFocus();
|
|
|
|
_listener = new GlobalLayoutListener(_view);
|
|
|
|
_view.ViewTreeObserver?.AddOnGlobalLayoutListener(_listener);
|
|
}
|
|
|
|
_view.Content = _content;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets whether to call the default back handler after our back handler is called.
|
|
/// </summary>
|
|
internal bool ShouldNavigateBack
|
|
{
|
|
get
|
|
{
|
|
var goBack = _shouldNavigateBack;
|
|
_shouldNavigateBack = false;
|
|
|
|
return goBack;
|
|
}
|
|
}
|
|
|
|
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()
|
|
{
|
|
if (OperatingSystem.IsAndroidVersionAtLeast(33))
|
|
return;
|
|
|
|
var eventArgs = new AndroidBackRequestedEventArgs();
|
|
|
|
BackRequested?.Invoke(this, eventArgs);
|
|
|
|
if (!eventArgs.Handled)
|
|
{
|
|
base.OnBackPressed();
|
|
}
|
|
}
|
|
|
|
protected override void OnCreate(Bundle? savedInstanceState)
|
|
{
|
|
InitializeAvaloniaView(_content);
|
|
|
|
base.OnCreate(savedInstanceState);
|
|
|
|
if (Avalonia.Application.Current?.TryGetFeature<IActivatableLifetime>()
|
|
is AndroidActivatableLifetime activatableLifetime)
|
|
{
|
|
activatableLifetime.CurrentIntendActivity = this;
|
|
}
|
|
|
|
HandleIntent(Intent);
|
|
}
|
|
|
|
protected override void OnNewIntent(Intent? intent)
|
|
{
|
|
base.OnNewIntent(intent);
|
|
|
|
HandleIntent(intent);
|
|
}
|
|
|
|
protected override void OnStop()
|
|
{
|
|
_onDeactivated?.Invoke(this, new ActivatedEventArgs(ActivationKind.Background));
|
|
|
|
if (OperatingSystem.IsAndroidVersionAtLeast(33))
|
|
{
|
|
_currentBackPressedCallback?.Remove();
|
|
_currentBackPressedCallback = null;
|
|
}
|
|
base.OnStop();
|
|
}
|
|
|
|
protected override void OnStart()
|
|
{
|
|
_onActivated?.Invoke(this, new ActivatedEventArgs(ActivationKind.Background));
|
|
|
|
if (OperatingSystem.IsAndroidVersionAtLeast(33))
|
|
{
|
|
_currentBackPressedCallback = new BackPressedCallback(this);
|
|
OnBackPressedDispatcher.AddCallback(this, _currentBackPressedCallback);
|
|
}
|
|
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;
|
|
}
|
|
|
|
// We inform the ContentView that it has become visible. OnVisibleChanged() sometimes doesn't get called. Issue #15807.
|
|
_view?.OnVisibilityChanged(true);
|
|
}
|
|
|
|
protected override void OnDestroy()
|
|
{
|
|
if (_view is not null)
|
|
{
|
|
if (_listener is not null)
|
|
{
|
|
_view.ViewTreeObserver?.RemoveOnGlobalLayoutListener(_listener);
|
|
}
|
|
_view.Content = null;
|
|
_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 void HandleIntent(Intent? intent)
|
|
{
|
|
if (intent?.Data is { } androidUri
|
|
&& androidUri.IsAbsolute
|
|
&& Uri.TryCreate(androidUri.ToString(), UriKind.Absolute, out var uri))
|
|
{
|
|
if (uri.Scheme == Uri.UriSchemeFile || uri.Scheme == "content")
|
|
{
|
|
if (AndroidStorageItem.CreateItem(this, androidUri) is { } item)
|
|
{
|
|
_onActivated?.Invoke(this, new FileActivatedEventArgs(new[] { item }));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_onActivated?.Invoke(this, new ProtocolActivatedEventArgs(uri));
|
|
}
|
|
}
|
|
}
|
|
|
|
public void OnBackInvoked()
|
|
{
|
|
var eventArgs = new AndroidBackRequestedEventArgs();
|
|
|
|
BackRequested?.Invoke(this, eventArgs);
|
|
|
|
_shouldNavigateBack = !eventArgs.Handled;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|