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