-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement TopLevel.Launcher (#14320)
* Implement ILauncher * Update dialogs page to include Launcher buttons * Fix control catalog * Add return comments
- Loading branch information
Showing
18 changed files
with
445 additions
and
49 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.