diff --git a/src/Android/Avalonia.Android/Avalonia.Android.csproj b/src/Android/Avalonia.Android/Avalonia.Android.csproj
index bd6eb4e903..b0b0712480 100644
--- a/src/Android/Avalonia.Android/Avalonia.Android.csproj
+++ b/src/Android/Avalonia.Android/Avalonia.Android.csproj
@@ -8,8 +8,6 @@
-
-
diff --git a/src/Android/Avalonia.Android/AvaloniaActivity.cs b/src/Android/Avalonia.Android/AvaloniaActivity.cs
index b5e4270028..d0538b6304 100644
--- a/src/Android/Avalonia.Android/AvaloniaActivity.cs
+++ b/src/Android/Avalonia.Android/AvaloniaActivity.cs
@@ -8,6 +8,7 @@ using Android.OS;
using Android.Runtime;
using Android.Views;
using AndroidX.AppCompat.App;
+using Avalonia.Android.Platform.Storage;
using Avalonia.Controls.ApplicationLifetimes;
namespace Avalonia.Android;
@@ -80,12 +81,23 @@ public class AvaloniaActivity : AppCompatActivity, IAvaloniaActivity
_listener = new GlobalLayoutListener(_view);
_view.ViewTreeObserver?.AddOnGlobalLayoutListener(_listener);
-
+
+ // TODO: we probably don't need to create AvaloniaView, if it's just a protocol activation, and main activity is already created.
if (Intent?.Data is {} androidUri
&& androidUri.IsAbsolute
- && Uri.TryCreate(androidUri.ToString(), UriKind.Absolute, out var protocolUri))
+ && Uri.TryCreate(androidUri.ToString(), UriKind.Absolute, out var uri))
{
- _onActivated?.Invoke(this, new ProtocolActivatedEventArgs(ActivationKind.OpenUri, protocolUri));
+ if (uri.Scheme == Uri.UriSchemeFile)
+ {
+ if (AndroidStorageItem.CreateItem(this, androidUri) is { } item)
+ {
+ _onActivated?.Invoke(this, new FileActivatedEventArgs(new [] { item }));
+ }
+ }
+ else
+ {
+ _onActivated?.Invoke(this, new ProtocolActivatedEventArgs(uri));
+ }
}
}
diff --git a/src/Android/Avalonia.Android/Platform/AndroidActivatableLifetime.cs b/src/Android/Avalonia.Android/Platform/AndroidActivatableLifetime.cs
index 62f347e9bf..a09c0ea88a 100644
--- a/src/Android/Avalonia.Android/Platform/AndroidActivatableLifetime.cs
+++ b/src/Android/Avalonia.Android/Platform/AndroidActivatableLifetime.cs
@@ -1,10 +1,9 @@
-using System;
using Android.App;
using Avalonia.Controls.ApplicationLifetimes;
namespace Avalonia.Android.Platform;
-internal class AndroidActivatableLifetime : IActivatableLifetime
+internal class AndroidActivatableLifetime : ActivatableLifetimeBase
{
private IAvaloniaActivity? _activity;
@@ -28,20 +27,10 @@ internal class AndroidActivatableLifetime : IActivatableLifetime
}
}
}
-
- public event EventHandler? Activated;
- public event EventHandler? Deactivated;
- public bool TryLeaveBackground() => false;
- public bool TryEnterBackground() => (_activity as Activity)?.MoveTaskToBack(true) == true;
+ public override bool TryEnterBackground() => (_activity as Activity)?.MoveTaskToBack(true) == true;
- private void ActivityOnDeactivated(object? sender, ActivatedEventArgs e)
- {
- Deactivated?.Invoke(this, e);
- }
+ private void ActivityOnDeactivated(object? sender, ActivatedEventArgs e) => OnDeactivated(e);
- private void ActivityOnActivated(object? sender, ActivatedEventArgs e)
- {
- Activated?.Invoke(this, e);
- }
+ private void ActivityOnActivated(object? sender, ActivatedEventArgs e) => OnActivated(e);
}
diff --git a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs
index 0c606ee07f..952717729f 100644
--- a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs
+++ b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs
@@ -8,7 +8,6 @@ using Android.App;
using Android.Content;
using Android.Provider;
using Android.Webkit;
-using AndroidX.DocumentFile.Provider;
using Avalonia.Logging;
using Avalonia.Platform.Storage;
using Java.Lang;
@@ -38,8 +37,8 @@ internal abstract class AndroidStorageItem : IStorageBookmarkItem
protected Activity Activity => _activity ?? throw new ObjectDisposedException(nameof(AndroidStorageItem));
- public virtual string Name => GetColumnValue(Activity, Uri, MediaStore.IMediaColumns.DisplayName)
- ?? Document?.Name
+ public virtual string Name => GetColumnValue(Activity, Uri, DocumentsContract.Document.ColumnDisplayName)
+ ?? GetColumnValue(Activity, Uri, MediaStore.IMediaColumns.DisplayName)
?? Uri.PathSegments?.LastOrDefault()?.Split("/", StringSplitOptions.RemoveEmptyEntries).LastOrDefault() ?? string.Empty;
public Uri Path => new(Uri.ToString()!);
@@ -69,7 +68,7 @@ internal abstract class AndroidStorageItem : IStorageBookmarkItem
public abstract Task GetBasicPropertiesAsync();
- protected string? GetColumnValue(Context context, AndroidUri contentUri, string column, string? selection = null, string[]? selectionArgs = null)
+ protected static string? GetColumnValue(Context context, AndroidUri contentUri, string column, string? selection = null, string[]? selectionArgs = null)
{
try
{
@@ -84,7 +83,7 @@ internal abstract class AndroidStorageItem : IStorageBookmarkItem
}
catch (Exception ex)
{
- Logger.TryGet(LogEventLevel.Verbose, LogArea.AndroidPlatform)?.Log(this, "File metadata reader failed: '{Exception}'", ex);
+ Logger.TryGet(LogEventLevel.Verbose, LogArea.AndroidPlatform)?.Log(null, "File metadata reader failed: '{Exception}'", ex);
}
return null;
@@ -102,18 +101,6 @@ internal abstract class AndroidStorageItem : IStorageBookmarkItem
return _parent;
}
- var document = Document;
-
- if (document == null)
- {
- return null;
- }
-
- if(document.ParentFile != null)
- {
- return new AndroidStorageFolder(Activity, document.ParentFile.Uri, true);
- }
-
using var javaFile = new JavaFile(Uri.Path!);
// Java file represents files AND directories. Don't be confused.
@@ -140,27 +127,25 @@ internal abstract class AndroidStorageItem : IStorageBookmarkItem
{
_activity = null;
}
-
- internal DocumentFile? Document
- {
- get
- {
- if (this is AndroidStorageFile)
- {
- return DocumentFile.FromSingleUri(Activity, Uri);
- }
- else
- {
- return DocumentFile.FromTreeUri(Activity, Uri);
- }
- }
- }
-
+
internal AndroidUri? PermissionRoot => _permissionRoot;
public abstract Task DeleteAsync();
public abstract Task MoveAsync(IStorageFolder destination);
+
+ public static IStorageItem CreateItem(Activity activity, AndroidUri uri)
+ {
+ var mimeType = GetColumnValue(activity, uri, DocumentsContract.Document.ColumnMimeType);
+ if (mimeType == DocumentsContract.Document.MimeTypeDir)
+ {
+ return new AndroidStorageFolder(activity, uri, false);
+ }
+ else
+ {
+ return new AndroidStorageFile(activity, uri);
+ }
+ }
}
internal class AndroidStorageFolder : AndroidStorageItem, IStorageBookmarkFolder
@@ -172,26 +157,24 @@ internal class AndroidStorageFolder : AndroidStorageItem, IStorageBookmarkFolder
public Task CreateFileAsync(string name)
{
var mimeType = MimeTypeMap.Singleton?.GetMimeTypeFromExtension(MimeTypeMap.GetFileExtensionFromUrl(name)) ?? "application/octet-stream";
- var newFile = Document?.CreateFile(mimeType, name);
-
+ var newFile = DocumentsContract.CreateDocument(Activity.ContentResolver!, Uri, mimeType, name);
if(newFile == null)
{
return Task.FromResult(null);
}
- return Task.FromResult(new AndroidStorageFile(Activity, newFile.Uri, this));
+ return Task.FromResult(new AndroidStorageFile(Activity, newFile, this));
}
public Task CreateFolderAsync(string name)
{
- var newFolder = Document?.CreateDirectory(name);
-
+ var newFolder = DocumentsContract.CreateDocument(Activity.ContentResolver!, Uri, DocumentsContract.Document.MimeTypeDir, name);
if (newFolder == null)
{
return Task.FromResult(null);
}
- return Task.FromResult(new AndroidStorageFolder(Activity, newFolder.Uri, false, this, PermissionRoot));
+ return Task.FromResult(new AndroidStorageFolder(Activity, newFolder, false, this, PermissionRoot));
}
public override async Task DeleteAsync()
@@ -220,7 +203,7 @@ internal class AndroidStorageFolder : AndroidStorageItem, IStorageBookmarkFolder
}
}
- DocumentFile.FromTreeUri(Activity, storageFolder.Uri)?.Delete();
+ DocumentsContract.DeleteDocument(Activity.ContentResolver!, storageFolder.Uri);
}
}
@@ -484,7 +467,7 @@ internal sealed class AndroidStorageFile : AndroidStorageItem, IStorageBookmarkF
try
{
if (Activity.ContentResolver is { } contentResolver &&
- storageFolder.Document?.Uri is { } targetParentUri &&
+ storageFolder.Uri is { } targetParentUri &&
await GetParentAsync() is AndroidStorageFolder parentFolder)
{
movedUri = DocumentsContract.MoveDocument(contentResolver, Uri, parentFolder.Uri, targetParentUri);
@@ -492,8 +475,7 @@ internal sealed class AndroidStorageFile : AndroidStorageItem, IStorageBookmarkF
}
catch (Exception)
{
- // There are many reason why DocumentContract will fail to move a file. We fallback to copying.
- return await MoveFileByCopy();
+ // There are many reason why DocumentContract will fail to move a file. We fallback to copying below.
}
}
diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ActivatableLifetimeBase.cs b/src/Avalonia.Controls/ApplicationLifetimes/ActivatableLifetimeBase.cs
new file mode 100644
index 0000000000..036e68e558
--- /dev/null
+++ b/src/Avalonia.Controls/ApplicationLifetimes/ActivatableLifetimeBase.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Controls.Platform;
+using Avalonia.Metadata;
+using Avalonia.Platform.Storage;
+using Avalonia.Threading;
+
+namespace Avalonia.Controls.ApplicationLifetimes;
+
+[PrivateApi]
+public abstract class ActivatableLifetimeBase : IActivatableLifetime
+{
+ public event EventHandler? Activated;
+ public event EventHandler? Deactivated;
+
+ public virtual bool TryLeaveBackground() => false;
+ public virtual bool TryEnterBackground() => false;
+
+ protected internal void OnActivated(ActivationKind kind) => OnActivated(new ActivatedEventArgs(kind));
+
+ protected internal void OnActivated(ActivatedEventArgs eventArgs) =>
+ Dispatcher.UIThread.Send(_ => Activated?.Invoke(this, eventArgs));
+
+ protected internal void OnDeactivated(ActivationKind kind) => OnDeactivated(new ActivatedEventArgs(kind));
+
+ protected internal void OnDeactivated(ActivatedEventArgs eventArgs) =>
+ Dispatcher.UIThread.Send(_ => Deactivated?.Invoke(this, eventArgs));
+}
diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ActivatedEventArgs.cs b/src/Avalonia.Controls/ApplicationLifetimes/ActivatedEventArgs.cs
index fb2eae1b52..79f3079e30 100644
--- a/src/Avalonia.Controls/ApplicationLifetimes/ActivatedEventArgs.cs
+++ b/src/Avalonia.Controls/ApplicationLifetimes/ActivatedEventArgs.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
namespace Avalonia.Controls.ApplicationLifetimes;
@@ -15,7 +16,7 @@ public class ActivatedEventArgs : EventArgs
{
Kind = kind;
}
-
+
///
/// The that this event represents.
///
diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ActivationKind.cs b/src/Avalonia.Controls/ApplicationLifetimes/ActivationKind.cs
index 6d72b56921..408387ffd3 100644
--- a/src/Avalonia.Controls/ApplicationLifetimes/ActivationKind.cs
+++ b/src/Avalonia.Controls/ApplicationLifetimes/ActivationKind.cs
@@ -3,10 +3,15 @@ namespace Avalonia.Controls.ApplicationLifetimes;
public enum ActivationKind
{
///
- /// When the application is passed a URI to open.
+ /// When the application is passed a file to open.
///
- OpenUri = 20,
-
+ File = 10,
+
+ ///
+ /// When the application is passed a URI to open, protocol activation.
+ ///
+ OpenUri = 20,
+
///
/// When the application is asked to reopen.
/// An example of this is on MacOS when all the windows are closed,
diff --git a/src/Avalonia.Controls/ApplicationLifetimes/FileActivatedEventArgs.cs b/src/Avalonia.Controls/ApplicationLifetimes/FileActivatedEventArgs.cs
new file mode 100644
index 0000000000..8d7b268b91
--- /dev/null
+++ b/src/Avalonia.Controls/ApplicationLifetimes/FileActivatedEventArgs.cs
@@ -0,0 +1,14 @@
+using System.Collections.Generic;
+using Avalonia.Platform.Storage;
+
+namespace Avalonia.Controls.ApplicationLifetimes;
+
+public sealed class FileActivatedEventArgs : ActivatedEventArgs
+{
+ public FileActivatedEventArgs(IReadOnlyList files) : base(ActivationKind.File)
+ {
+ Files = files;
+ }
+
+ public IReadOnlyList Files { get; }
+}
diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ProtocolActivatedEventArgs.cs b/src/Avalonia.Controls/ApplicationLifetimes/ProtocolActivatedEventArgs.cs
index e9706f1cf9..6456d76345 100644
--- a/src/Avalonia.Controls/ApplicationLifetimes/ProtocolActivatedEventArgs.cs
+++ b/src/Avalonia.Controls/ApplicationLifetimes/ProtocolActivatedEventArgs.cs
@@ -1,10 +1,12 @@
using System;
+using System.Linq;
+using Avalonia.Metadata;
namespace Avalonia.Controls.ApplicationLifetimes;
-public class ProtocolActivatedEventArgs : ActivatedEventArgs
+public sealed class ProtocolActivatedEventArgs : ActivatedEventArgs
{
- public ProtocolActivatedEventArgs(ActivationKind kind, Uri uri) : base(kind)
+ public ProtocolActivatedEventArgs(Uri uri) : base(ActivationKind.OpenUri)
{
Uri = uri;
}
diff --git a/src/Avalonia.Controls/Platform/IApplicationPlatformEvents.cs b/src/Avalonia.Controls/Platform/IApplicationPlatformEvents.cs
index 99bbb8b56d..dade84afc4 100644
--- a/src/Avalonia.Controls/Platform/IApplicationPlatformEvents.cs
+++ b/src/Avalonia.Controls/Platform/IApplicationPlatformEvents.cs
@@ -2,7 +2,7 @@ using Avalonia.Metadata;
namespace Avalonia.Platform
{
- [Unstable]
+ [Unstable("This interface will be removed in 12.0.")]
public interface IApplicationPlatformEvents
{
void RaiseUrlsOpened(string[] urls);
diff --git a/src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs b/src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs
index 298ef5619a..0a735f8f9e 100644
--- a/src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs
+++ b/src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs
@@ -1,8 +1,11 @@
using System;
+using System.Collections.Generic;
using System.ComponentModel;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Native.Interop;
using Avalonia.Platform;
+using Avalonia.Platform.Storage;
+using Avalonia.Platform.Storage.FileIO;
namespace Avalonia.Native
{
@@ -13,46 +16,85 @@ namespace Avalonia.Native
void IAvnApplicationEvents.FilesOpened(IAvnStringArray urls)
{
((IApplicationPlatformEvents)Application.Current)?.RaiseUrlsOpened(urls.ToStringArray());
+
+ if (AvaloniaLocator.Current.GetService() is ActivatableLifetimeBase lifetime)
+ {
+ var filePaths = urls.ToStringArray();
+ var files = new List(filePaths.Length);
+ foreach (var filePath in filePaths)
+ {
+ if (StorageProviderHelpers.TryCreateBclStorageItem(filePath) is { } file)
+ {
+ files.Add(file);
+ }
+ }
+
+ if (files.Count > 0)
+ {
+ lifetime.OnActivated(new FileActivatedEventArgs(files));
+ }
+ }
}
-
+
void IAvnApplicationEvents.UrlsOpened(IAvnStringArray urls)
{
// Raise the urls opened event to be compatible with legacy behavior.
((IApplicationPlatformEvents)Application.Current)?.RaiseUrlsOpened(urls.ToStringArray());
- if (AvaloniaLocator.Current.GetService() is MacOSActivatableLifetime lifetime)
+ if (AvaloniaLocator.Current.GetService() is ActivatableLifetimeBase lifetime)
{
+ var files = new List();
+ var uris = new List();
foreach (var url in urls.ToStringArray())
{
if (Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out var uri))
{
- lifetime.RaiseUrl(uri);
+ if (uri.Scheme == Uri.UriSchemeFile)
+ {
+ if (StorageProviderHelpers.TryCreateBclStorageItem(uri.LocalPath) is { } file)
+ {
+ files.Add(file);
+ }
+ }
+ else
+ {
+ uris.Add(uri);
+ }
}
}
+
+ foreach (var uri in uris)
+ {
+ lifetime.OnActivated(new ProtocolActivatedEventArgs(uri));
+ }
+ if (files.Count > 0)
+ {
+ lifetime.OnActivated(new FileActivatedEventArgs(files));
+ }
}
}
void IAvnApplicationEvents.OnReopen()
{
- if (AvaloniaLocator.Current.GetService() is MacOSActivatableLifetime lifetime)
+ if (AvaloniaLocator.Current.GetService() is ActivatableLifetimeBase lifetime)
{
- lifetime.RaiseActivated(ActivationKind.Reopen);
+ lifetime.OnActivated(ActivationKind.Reopen);
}
}
void IAvnApplicationEvents.OnHide()
{
- if (AvaloniaLocator.Current.GetService() is MacOSActivatableLifetime lifetime)
+ if (AvaloniaLocator.Current.GetService() is ActivatableLifetimeBase lifetime)
{
- lifetime.RaiseDeactivated(ActivationKind.Background);
+ lifetime.OnActivated(ActivationKind.Background);
}
}
void IAvnApplicationEvents.OnUnhide()
{
- if (AvaloniaLocator.Current.GetService() is MacOSActivatableLifetime lifetime)
+ if (AvaloniaLocator.Current.GetService() is ActivatableLifetimeBase lifetime)
{
- lifetime.RaiseActivated(ActivationKind.Background);
+ lifetime.OnActivated(ActivationKind.Background);
}
}
diff --git a/src/Avalonia.Native/MacOSActivatableLifetime.cs b/src/Avalonia.Native/MacOSActivatableLifetime.cs
index 54dfab6132..c96e43f724 100644
--- a/src/Avalonia.Native/MacOSActivatableLifetime.cs
+++ b/src/Avalonia.Native/MacOSActivatableLifetime.cs
@@ -6,16 +6,9 @@ namespace Avalonia.Native;
#nullable enable
-internal class MacOSActivatableLifetime : IActivatableLifetime
+internal class MacOSActivatableLifetime : ActivatableLifetimeBase
{
- ///
- public event EventHandler? Activated;
-
- ///
- public event EventHandler? Deactivated;
-
- ///
- public bool TryLeaveBackground()
+ public override bool TryLeaveBackground()
{
var nativeApplicationCommands = AvaloniaLocator.Current.GetService();
nativeApplicationCommands?.ShowApp();
@@ -23,27 +16,11 @@ internal class MacOSActivatableLifetime : IActivatableLifetime
return true;
}
- ///
- public bool TryEnterBackground()
+ public override bool TryEnterBackground()
{
var nativeApplicationCommands = AvaloniaLocator.Current.GetService();
nativeApplicationCommands?.HideApp();
return true;
}
-
- internal void RaiseUrl(Uri uri)
- {
- Activated?.Invoke(this, new ProtocolActivatedEventArgs(ActivationKind.OpenUri, uri));
- }
-
- internal void RaiseActivated(ActivationKind kind)
- {
- Activated?.Invoke(this, new ActivatedEventArgs(kind));
- }
-
- internal void RaiseDeactivated(ActivationKind kind)
- {
- Deactivated?.Invoke(this, new ActivatedEventArgs(kind));
- }
}
diff --git a/src/iOS/Avalonia.iOS/ActivatableLifetime.cs b/src/iOS/Avalonia.iOS/ActivatableLifetime.cs
index c3e2ddedde..58a8808002 100644
--- a/src/iOS/Avalonia.iOS/ActivatableLifetime.cs
+++ b/src/iOS/Avalonia.iOS/ActivatableLifetime.cs
@@ -3,16 +3,11 @@ using Avalonia.Controls.ApplicationLifetimes;
namespace Avalonia.iOS;
-internal class ActivatableLifetime : IActivatableLifetime
+internal class ActivatableLifetime : ActivatableLifetimeBase
{
public ActivatableLifetime(IAvaloniaAppDelegate avaloniaAppDelegate)
{
- avaloniaAppDelegate.Activated += (_, args) => Activated?.Invoke(this, args);
- avaloniaAppDelegate.Deactivated += (_, args) => Deactivated?.Invoke(this, args);
+ avaloniaAppDelegate.Activated += (_, args) => OnActivated(args);
+ avaloniaAppDelegate.Deactivated += (_, args) => OnDeactivated(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 6ea5d5f762..99e97a5631 100644
--- a/src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs
+++ b/src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs
@@ -1,7 +1,6 @@
using System;
using Foundation;
using Avalonia.Controls.ApplicationLifetimes;
-
using UIKit;
namespace Avalonia.iOS
@@ -74,7 +73,16 @@ namespace Avalonia.iOS
{
if (Uri.TryCreate(url.ToString(), UriKind.Absolute, out var uri))
{
- _onActivated?.Invoke(this, new ProtocolActivatedEventArgs(ActivationKind.OpenUri, uri));
+#if !TVOS
+ if (uri.Scheme == Uri.UriSchemeFile)
+ {
+ _onActivated?.Invoke(this, new FileActivatedEventArgs(new[] { Storage.IOSStorageItem.CreateItem(url) }));
+ }
+ else
+#endif
+ {
+ _onActivated?.Invoke(this, new ProtocolActivatedEventArgs(uri));
+ }
return true;
}
diff --git a/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs b/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs
index f9a7aaf445..cf124aa101 100644
--- a/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs
+++ b/src/iOS/Avalonia.iOS/Storage/IOSStorageItem.cs
@@ -29,6 +29,13 @@ internal abstract class IOSStorageItem : IStorageBookmarkItem
?? string.Empty;
}
}
+
+ public static IStorageItem CreateItem(NSUrl url, NSUrl? securityScopedAncestorUrl = null)
+ {
+ return url.HasDirectoryPath ?
+ new IOSStorageFolder(url, securityScopedAncestorUrl) :
+ new IOSStorageFile(url, securityScopedAncestorUrl);
+ }
internal NSUrl Url { get; }
// Calling StartAccessingSecurityScopedResource on items retrieved from, or created in a folder
@@ -161,7 +168,7 @@ internal sealed class IOSStorageFile : IOSStorageItem, IStorageBookmarkFile
public IOSStorageFile(NSUrl url, NSUrl? securityScopedAncestorUrl = null) : base(url, securityScopedAncestorUrl)
{
}
-
+
public Task OpenReadAsync()
{
return Task.FromResult(new IOSSecurityScopedStream(Url, SecurityScopedAncestorUrl, FileAccess.Read));
@@ -208,9 +215,7 @@ internal sealed class IOSStorageFolder : IOSStorageItem, IStorageBookmarkFolder
else
{
var items = content
- .Select(u => u.HasDirectoryPath ?
- (IStorageItem)new IOSStorageFolder(u, SecurityScopedAncestorUrl) :
- new IOSStorageFile(u, SecurityScopedAncestorUrl))
+ .Select(u => CreateItem(u, SecurityScopedAncestorUrl))
.ToArray();
tcs.TrySetResult(items);
}
diff --git a/src/iOS/Avalonia.iOS/Storage/IOSStorageProvider.cs b/src/iOS/Avalonia.iOS/Storage/IOSStorageProvider.cs
index 4e812b5d98..6d9d2b1e37 100644
--- a/src/iOS/Avalonia.iOS/Storage/IOSStorageProvider.cs
+++ b/src/iOS/Avalonia.iOS/Storage/IOSStorageProvider.cs
@@ -104,14 +104,31 @@ internal class IOSStorageProvider : IStorageProvider
public Task TryGetFileFromPathAsync(Uri filePath)
{
- // TODO: research if it's possible, maybe with additional permissions.
- return Task.FromResult(null);
+ var fileUrl = (NSUrl?)filePath;
+ var isDirectory = false;
+ var file = fileUrl is not null
+ && fileUrl.Path is { } path
+ && NSFileManager.DefaultManager.FileExists(path, ref isDirectory)
+ && !isDirectory
+ && NSFileManager.DefaultManager.IsReadableFile(path) ?
+ new IOSStorageFile(fileUrl) :
+ null;
+
+ return Task.FromResult(file);
}
public Task TryGetFolderFromPathAsync(Uri folderPath)
{
- // TODO: research if it's possible, maybe with additional permissions.
- return Task.FromResult(null);
+ var folderUrl = (NSUrl?)folderPath;
+ var isDirectory = false;
+ var folder = folderUrl is not null
+ && folderUrl.Path is { } path
+ && NSFileManager.DefaultManager.FileExists(path, ref isDirectory)
+ && isDirectory ?
+ new IOSStorageFolder(folderUrl) :
+ null;
+
+ return Task.FromResult(folder);
}
public Task TryGetWellKnownFolderAsync(WellKnownFolder wellKnownFolder)