Browse Source
* Implement ILauncher * Update dialogs page to include Launcher buttons * Fix control catalog * Add return commentspull/14530/head
committed by
GitHub
18 changed files with 445 additions and 49 deletions
@ -0,0 +1,56 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Android.Content; |
|||
using Avalonia.Android.Platform.Storage; |
|||
using Avalonia.Platform.Storage; |
|||
using AndroidUri = Android.Net.Uri; |
|||
|
|||
namespace Avalonia.Android.Platform; |
|||
|
|||
internal class AndroidLauncher : ILauncher |
|||
{ |
|||
private readonly Context _context; |
|||
|
|||
public AndroidLauncher(Context context) |
|||
{ |
|||
_context = context; |
|||
} |
|||
|
|||
public Task<bool> LaunchUriAsync(Uri uri) |
|||
{ |
|||
_ = uri ?? throw new ArgumentNullException(nameof(uri)); |
|||
if (uri.IsAbsoluteUri && _context.PackageManager is { } packageManager) |
|||
{ |
|||
var intent = new Intent(Intent.ActionView, AndroidUri.Parse(uri.OriginalString)); |
|||
if (intent.ResolveActivity(packageManager) is not null) |
|||
{ |
|||
var flags = ActivityFlags.ClearTop | ActivityFlags.NewTask; |
|||
intent.SetFlags(flags); |
|||
_context.StartActivity(intent); |
|||
} |
|||
} |
|||
return Task.FromResult(false); |
|||
} |
|||
|
|||
public Task<bool> LaunchFileAsync(IStorageItem storageItem) |
|||
{ |
|||
_ = storageItem ?? throw new ArgumentNullException(nameof(storageItem)); |
|||
var androidUri = (storageItem as AndroidStorageItem)?.Uri |
|||
?? (storageItem.TryGetLocalPath() is { } localPath ? AndroidUri.Parse(localPath) : null); |
|||
|
|||
if (androidUri is not null && _context.PackageManager is { } packageManager) |
|||
{ |
|||
var intent = new Intent(Intent.ActionView, androidUri); |
|||
// intent.SetDataAndType(contentUri, request.File.ContentType);
|
|||
intent.SetFlags(ActivityFlags.GrantReadUriPermission); |
|||
if (intent.ResolveActivity(packageManager) is not null |
|||
&& Intent.CreateChooser(intent, string.Empty) is { } chooserIntent) |
|||
{ |
|||
var flags = ActivityFlags.ClearTop | ActivityFlags.NewTask; |
|||
chooserIntent.SetFlags(flags); |
|||
_context.StartActivity(chooserIntent); |
|||
} |
|||
} |
|||
return Task.FromResult(false); |
|||
} |
|||
} |
|||
@ -0,0 +1,90 @@ |
|||
using System; |
|||
using System.Diagnostics; |
|||
using System.Text.RegularExpressions; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Compatibility; |
|||
using Avalonia.Metadata; |
|||
|
|||
namespace Avalonia.Platform.Storage.FileIO; |
|||
|
|||
internal class BclLauncher : ILauncher |
|||
{ |
|||
public virtual Task<bool> LaunchUriAsync(Uri uri) |
|||
{ |
|||
_ = uri ?? throw new ArgumentNullException(nameof(uri)); |
|||
if (uri.IsAbsoluteUri) |
|||
{ |
|||
return Task.FromResult(Exec(uri.AbsoluteUri)); |
|||
} |
|||
|
|||
return Task.FromResult(false); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// This Process based implementation doesn't handle the case, when there is no app to handle link.
|
|||
/// It will still return true in this case.
|
|||
/// </summary>
|
|||
public virtual Task<bool> LaunchFileAsync(IStorageItem storageItem) |
|||
{ |
|||
_ = storageItem ?? throw new ArgumentNullException(nameof(storageItem)); |
|||
if (storageItem.TryGetLocalPath() is { } localPath |
|||
&& CanOpenFileOrDirectory(localPath)) |
|||
{ |
|||
return Task.FromResult(Exec(localPath)); |
|||
} |
|||
|
|||
return Task.FromResult(false); |
|||
} |
|||
|
|||
protected virtual bool CanOpenFileOrDirectory(string localPath) => true; |
|||
|
|||
private static bool Exec(string urlOrFile) |
|||
{ |
|||
if (OperatingSystemEx.IsLinux()) |
|||
{ |
|||
// If no associated application/json MimeType is found xdg-open opens return error
|
|||
// but it tries to open it anyway using the console editor (nano, vim, other..)
|
|||
ShellExec($"xdg-open {urlOrFile}", waitForExit: false); |
|||
return true; |
|||
} |
|||
else if (OperatingSystemEx.IsWindows() || OperatingSystemEx.IsMacOS()) |
|||
{ |
|||
using var process = Process.Start(new ProcessStartInfo |
|||
{ |
|||
FileName = OperatingSystemEx.IsWindows() ? urlOrFile : "open", |
|||
Arguments = OperatingSystemEx.IsMacOS() ? $"{urlOrFile}" : "", |
|||
CreateNoWindow = true, |
|||
UseShellExecute = OperatingSystemEx.IsWindows() |
|||
}); |
|||
return true; |
|||
} |
|||
else |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
private static void ShellExec(string cmd, bool waitForExit = true) |
|||
{ |
|||
var escapedArgs = Regex.Replace(cmd, "(?=[`~!#&*()|;'<>])", "\\") |
|||
.Replace("\"", "\\\\\\\""); |
|||
|
|||
using (var process = Process.Start( |
|||
new ProcessStartInfo |
|||
{ |
|||
FileName = "/bin/sh", |
|||
Arguments = $"-c \"{escapedArgs}\"", |
|||
RedirectStandardOutput = true, |
|||
UseShellExecute = false, |
|||
CreateNoWindow = true, |
|||
WindowStyle = ProcessWindowStyle.Hidden |
|||
} |
|||
)) |
|||
{ |
|||
if (waitForExit) |
|||
{ |
|||
process?.WaitForExit(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,69 @@ |
|||
using System; |
|||
using System.IO; |
|||
using System.Runtime.Versioning; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Platform.Storage; |
|||
using Avalonia.Platform.Storage.FileIO; |
|||
|
|||
namespace Avalonia.Platform.Storage; |
|||
|
|||
/// <summary>
|
|||
/// Starts the default app associated with the specified file or URI.
|
|||
/// </summary>
|
|||
public interface ILauncher |
|||
{ |
|||
/// <summary>
|
|||
/// Starts the default app associated with the URI scheme name for the specified URI.
|
|||
/// </summary>
|
|||
/// <param name="uri">The URI.</param>
|
|||
/// <returns>True, if launch operation was successful. False, if unsupported or failed.</returns>
|
|||
Task<bool> LaunchUriAsync(Uri uri); |
|||
|
|||
/// <summary>
|
|||
/// Starts the default app associated with the specified storage file or folder.
|
|||
/// </summary>
|
|||
/// <param name="storageItem">The file or folder.</param>
|
|||
/// <returns>True, if launch operation was successful. False, if unsupported or failed.</returns>
|
|||
Task<bool> LaunchFileAsync(IStorageItem storageItem); |
|||
} |
|||
|
|||
internal class NoopLauncher : ILauncher |
|||
{ |
|||
public Task<bool> LaunchUriAsync(Uri uri) => Task.FromResult(false); |
|||
public Task<bool> LaunchFileAsync(IStorageItem storageItem) => Task.FromResult(false); |
|||
} |
|||
|
|||
public static class LauncherExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Starts the default app associated with the specified storage file.
|
|||
/// </summary>
|
|||
/// <param name="launcher">ILauncher instance.</param>
|
|||
/// <param name="fileInfo">The file.</param>
|
|||
public static Task<bool> LaunchFileInfoAsync(this ILauncher launcher, FileInfo fileInfo) |
|||
{ |
|||
_ = fileInfo ?? throw new ArgumentNullException(nameof(fileInfo)); |
|||
if (!fileInfo.Exists) |
|||
{ |
|||
return Task.FromResult(false); |
|||
} |
|||
|
|||
return launcher.LaunchFileAsync(new BclStorageFile(fileInfo)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Starts the default app associated with the specified storage directory (folder).
|
|||
/// </summary>
|
|||
/// <param name="launcher">ILauncher instance.</param>
|
|||
/// <param name="directoryInfo">The directory.</param>
|
|||
public static Task<bool> LaunchDirectoryInfoAsync(this ILauncher launcher, DirectoryInfo directoryInfo) |
|||
{ |
|||
_ = directoryInfo ?? throw new ArgumentNullException(nameof(directoryInfo)); |
|||
if (!directoryInfo.Exists) |
|||
{ |
|||
return Task.FromResult(false); |
|||
} |
|||
|
|||
return launcher.LaunchFileAsync(new BclStorageFolder(directoryInfo)); |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Browser.Interop; |
|||
using Avalonia.Platform.Storage; |
|||
|
|||
namespace Avalonia.Browser.Storage; |
|||
|
|||
internal class BrowserLauncher : ILauncher |
|||
{ |
|||
public Task<bool> LaunchUriAsync(Uri uri) |
|||
{ |
|||
_ = uri ?? throw new ArgumentNullException(nameof(uri)); |
|||
|
|||
if (uri.IsAbsoluteUri) |
|||
{ |
|||
var window = NavigationHelper.WindowOpen(uri.AbsoluteUri, "_blank"); |
|||
return Task.FromResult(window is not null); |
|||
} |
|||
return Task.FromResult(false); |
|||
} |
|||
|
|||
public Task<bool> LaunchFileAsync(IStorageItem storageItem) |
|||
{ |
|||
_ = storageItem ?? throw new ArgumentNullException(nameof(storageItem)); |
|||
|
|||
return Task.FromResult(false); |
|||
} |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
using Avalonia.Platform.Storage; |
|||
using Avalonia.Tizen.Platform; |
|||
using Tizen.Applications; |
|||
|
|||
namespace Avalonia.Tizen; |
|||
|
|||
internal class TizenLauncher : ILauncher |
|||
{ |
|||
public async Task<bool> LaunchUriAsync(Uri uri) |
|||
{ |
|||
if (uri is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(uri)); |
|||
} |
|||
|
|||
if (!uri.IsAbsoluteUri) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (!await Permissions.RequestPrivilegeAsync(Permissions.LaunchPrivilege)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
var appControl = new AppControl |
|||
{ |
|||
Operation = AppControlOperations.ShareText, |
|||
Uri = uri.AbsoluteUri |
|||
}; |
|||
|
|||
if (uri.AbsoluteUri.StartsWith("geo:")) |
|||
appControl.Operation = AppControlOperations.Pick; |
|||
else if (uri.AbsoluteUri.StartsWith("http")) |
|||
appControl.Operation = AppControlOperations.View; |
|||
else if (uri.AbsoluteUri.StartsWith("mailto:")) |
|||
appControl.Operation = AppControlOperations.Compose; |
|||
else if (uri.AbsoluteUri.StartsWith("sms:")) |
|||
appControl.Operation = AppControlOperations.Compose; |
|||
else if (uri.AbsoluteUri.StartsWith("tel:")) |
|||
appControl.Operation = AppControlOperations.Dial; |
|||
|
|||
AppControl.SendLaunchRequest(appControl); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
public async Task<bool> LaunchFileAsync(IStorageItem storageItem) |
|||
{ |
|||
if (storageItem is null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(storageItem)); |
|||
} |
|||
|
|||
if (!await Permissions.RequestPrivilegeAsync(Permissions.LaunchPrivilege)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
var appControl = new AppControl |
|||
{ |
|||
Operation = AppControlOperations.View, |
|||
Mime = "*/*", |
|||
Uri = "file://" + storageItem.Path, |
|||
}; |
|||
|
|||
AppControl.SendLaunchRequest(appControl); |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Platform.Storage; |
|||
using Foundation; |
|||
using UIKit; |
|||
|
|||
namespace Avalonia.iOS; |
|||
|
|||
internal class IOSLauncher : ILauncher |
|||
{ |
|||
public Task<bool> LaunchUriAsync(Uri uri) |
|||
{ |
|||
_ = uri ?? throw new ArgumentNullException(nameof(uri)); |
|||
|
|||
if (uri.IsAbsoluteUri && UIApplication.SharedApplication.CanOpenUrl(uri)) |
|||
{ |
|||
return UIApplication.SharedApplication.OpenUrlAsync(uri!, new UIApplicationOpenUrlOptions()); |
|||
} |
|||
|
|||
return Task.FromResult(false); |
|||
} |
|||
|
|||
public Task<bool> LaunchFileAsync(IStorageItem storageItem) |
|||
{ |
|||
_ = storageItem ?? throw new ArgumentNullException(nameof(storageItem)); |
|||
|
|||
#if !TVOS
|
|||
var uri = (storageItem as Storage.IOSStorageItem)?.Url |
|||
?? (storageItem.TryGetLocalPath() is { } localPath ? NSUrl.FromFilename(localPath) : null); |
|||
if (uri is not null) |
|||
{ |
|||
var documentController = new UIDocumentInteractionController() |
|||
{ |
|||
Name = storageItem.Name, |
|||
Url = uri |
|||
}; |
|||
|
|||
var result = documentController.PresentPreview(true); |
|||
return Task.FromResult(result); |
|||
} |
|||
#endif
|
|||
|
|||
return Task.FromResult(false); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue