diff --git a/doc/helpers/mediagallery.md b/doc/helpers/mediagallery.md new file mode 100644 index 000000000..394aff48e --- /dev/null +++ b/doc/helpers/mediagallery.md @@ -0,0 +1,149 @@ +--- +uid: Toolkit.Helpers.MediaGallery +--- +# MediaGallery + +## Summary + +`MediaGallery` is a static class that allows interaction with the device's media gallery, providing methods to check access permissions and save media files. + +## Remarks + +This class is designed to work on iOS, Mac Catalyst and Android platforms, utilizing platform-specific implementations for its methods. + +The API allows setting the `targetFileName`, which **should ideally be unique** - otherwise the OS will either overwrite an existing file with the same name (Android behavior), or generate a new name instead (iOS behavior). + +## Methods + +### CheckAccessAsync + +```csharp +public static async Task CheckAccessAsync() +``` + +#### Summary + +Checks the user's permission to access the device's gallery. Will trigger the permission request if not already granted. + +#### Returns + +A `Task` that completes with `true` if access is granted, and `false` otherwise. + +### SaveAsync (Stream) + +```csharp +public static async Task SaveAsync(MediaFileType type, Stream stream, string targetFileName) +``` + +#### Summary + +Saves a media file to the device's gallery using a stream. + +#### Parameters + +- `MediaFileType type`: The type of the media file (e.g., image, video). +- `Stream stream`: A stream representing the media file. +- `string targetFileName`: The desired file name for the saved media. + +#### Returns + +A `Task` that completes when the save operation is finished. + +### SaveAsync (byte array) + +```csharp +public static async Task SaveAsync(MediaFileType type, byte[] data, string targetFileName) +``` + +#### Summary + +Saves a media file to the device's gallery using a byte array. + +#### Parameters + +- `MediaFileType type`: The type of the media file (e.g., image, video). +- `byte[] data`: A byte array representing the media file. +- `string targetFileName`: The desired file name for the saved media. + +#### Returns + +A `Task` that completes when the save operation is finished. + +## Permissions + +### Android + +If your app supports only Android 10 and newer, no manifest changes are required. If you support earlier versions of Android, add the following into your manifest: + +```xml + +``` + +### iOS & Mac Catalyst + +If your app only supports iOS 14 and newer, update your `Info.plist` as follows: + +```xml +NSPhotoLibraryAddUsageDescription +This app needs access to the photo gallery for saving photos and videos +``` + +If you want to support earlier versions iOS, add the following as well: + +```xml +NSPhotoLibraryUsageDescription +This app needs access to the photo gallery for saving photos and videos +``` + +## Usage + +Make sure to wrap the usage of `MediaGallery` in a `#if __ANDROID__ || __IOS__` ... `#endif` block, as the class is only available on these targets. + +### Checking for gallery access + +```csharp +#if __ANDROID__ || __IOS__ +bool hasAccess = await MediaGallery.CheckAccessAsync(); +#endif +``` + +### Saving an image to the gallery using a byte array + +```csharp +#if __ANDROID__ || __IOS__ +byte[] imageData = ...; // Image data +await MediaGallery.SaveAsync(MediaFileType.Image, imageData, "MyImage.jpg"); +#endif +``` + +### Saving a video to the gallery using a stream + +```csharp +#if __ANDROID__ || __IOS__ +using Stream videoStream = ...; // Video stream +await MediaGallery.SaveAsync(MediaFileType.Video, videoStream, "MyVideo.mp4"); +#endif +``` + +### Copying an application package file to gallery + +```csharp +#if __ANDROID__ || __IOS__ +if (await MediaGallery.CheckAccessAsync()) +{ + var file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/UnoLogo.png", UriKind.Absolute)); + using var stream = await file.OpenStreamForReadAsync(); + await MediaGallery.SaveAsync(MediaFileType.Image, stream, "UnoLogo.png"); +} +else +{ + await new ContentDialog + { + Title = "Permission required", + Content = "The app requires access to the device's gallery to save the image.", + CloseButtonText = "OK", + XamlRoot = XamlRoot + }.ShowAsync(); +} +#endif +``` \ No newline at end of file diff --git a/doc/toc.yml b/doc/toc.yml index 89f79e1eb..ec0e40a6d 100644 --- a/doc/toc.yml +++ b/doc/toc.yml @@ -61,6 +61,8 @@ href: helpers/input-extensions.md - name: ItemsRepeater Extensions href: helpers/itemsrepeater-extensions.md + - name: MediaGallery + href: helpers/mediagallery.md - name: Progress Extensions href: helpers/progress-extensions.md - name: Responsive markup extension diff --git a/samples/Uno.Toolkit.Samples/Uno.Toolkit.Samples.Shared/Content/Helpers/MediaGalleryHelperSamplePage.xaml b/samples/Uno.Toolkit.Samples/Uno.Toolkit.Samples.Shared/Content/Helpers/MediaGalleryHelperSamplePage.xaml new file mode 100644 index 000000000..f2eafc5bb --- /dev/null +++ b/samples/Uno.Toolkit.Samples/Uno.Toolkit.Samples.Shared/Content/Helpers/MediaGalleryHelperSamplePage.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + diff --git a/samples/Uno.Toolkit.Samples/Uno.Toolkit.Samples.Shared/Content/Helpers/MediaGalleryHelperSamplePage.xaml.cs b/samples/Uno.Toolkit.Samples/Uno.Toolkit.Samples.Shared/Content/Helpers/MediaGalleryHelperSamplePage.xaml.cs new file mode 100644 index 000000000..25d7bed01 --- /dev/null +++ b/samples/Uno.Toolkit.Samples/Uno.Toolkit.Samples.Shared/Content/Helpers/MediaGalleryHelperSamplePage.xaml.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using System.Text; +using Uno.Toolkit.Samples.Entities; +using Uno.Toolkit.Samples.Helpers; +using Uno.Toolkit.Samples.ViewModels; +using Uno.Toolkit.UI; +using Windows.Foundation; +using Windows.Foundation.Collections; +using System.Windows.Input; +using System.Net.WebSockets; +using Windows.Storage; + +#if IS_WINUI +using Microsoft.UI; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Data; +using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Media; +using Microsoft.UI.Xaml.Navigation; +#else +using Windows.UI; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; +#endif + +namespace Uno.Toolkit.Samples.Content.Helpers; + +#if __ANDROID__ || __IOS__ +[SamplePage(SampleCategory.Helpers, "MediaGalleryHelper", SourceSdk.Uno, IconSymbol = Symbol.BrowsePhotos, DataType = typeof(MediaGalleryHelperSampleVM))] +#endif +public sealed partial class MediaGalleryHelperSamplePage : Page +{ + public MediaGalleryHelperSamplePage() + { + this.InitializeComponent(); + this.Loaded += (s, e) => + { + if ((DataContext as Sample)?.Data is MediaGalleryHelperSampleVM vm) + { + vm.XamlRoot = this.XamlRoot; + } + }; + } +} + +public class MediaGalleryHelperSampleVM : ViewModelBase +{ + public XamlRoot XamlRoot { get; set; } + +#if __ANDROID__ || __IOS__ + public ICommand CheckAccessCommand => new Command(async (_) => + { + var success = await MediaGallery.CheckAccessAsync(); + await new ContentDialog + { + Title = "Permission check", + Content = $"Access {(success ? "granted" : "denied")}.", + CloseButtonText = "OK", + XamlRoot = XamlRoot + }.ShowAsync(); + }); + + public ICommand SaveCommand => new Command(async (_) => + { + if (await MediaGallery.CheckAccessAsync()) + { + var file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/UnoLogo.png", UriKind.Absolute)); + using var stream = await file.OpenStreamForReadAsync(); + await MediaGallery.SaveAsync(MediaFileType.Image, stream, "UnoLogo.png"); + } + else + { + await new ContentDialog + { + Title = "Permission required", + Content = "The app requires access to the device's gallery to save the image.", + CloseButtonText = "OK", + XamlRoot = XamlRoot + }.ShowAsync(); + } + }); + + public ICommand SaveRandomNameCommand => new Command(async (_) => + { + if (await MediaGallery.CheckAccessAsync()) + { + var file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/UnoLogo.png", UriKind.Absolute)); + using var stream = await file.OpenStreamForReadAsync(); + + var fileName = Guid.NewGuid() + ".png"; + await MediaGallery.SaveAsync(MediaFileType.Image, stream, fileName); + } + else + { + await new ContentDialog + { + Title = "Permission required", + Content = "The app requires access to the device's gallery to save the image.", + CloseButtonText = "OK", + XamlRoot = XamlRoot + }.ShowAsync(); + } + }); +#endif +} diff --git a/samples/Uno.Toolkit.Samples/Uno.Toolkit.Samples.Shared/Uno.Toolkit.Samples.Shared.projitems b/samples/Uno.Toolkit.Samples/Uno.Toolkit.Samples.Shared/Uno.Toolkit.Samples.Shared.projitems index 7d86ac295..2a457f03b 100644 --- a/samples/Uno.Toolkit.Samples/Uno.Toolkit.Samples.Shared/Uno.Toolkit.Samples.Shared.projitems +++ b/samples/Uno.Toolkit.Samples/Uno.Toolkit.Samples.Shared/Uno.Toolkit.Samples.Shared.projitems @@ -77,6 +77,9 @@ BindingExtensionsSamplePage.xaml + + MediaGalleryHelperSamplePage.xaml + ResponsiveExtensionsSamplePage.xaml @@ -316,6 +319,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/samples/Uno.Toolkit.WinUI.Samples/Uno.Toolkit.WinUI.Samples.Mobile/MacCatalyst/Info.plist b/samples/Uno.Toolkit.WinUI.Samples/Uno.Toolkit.WinUI.Samples.Mobile/MacCatalyst/Info.plist index 5a4053db4..2a5f1622f 100644 --- a/samples/Uno.Toolkit.WinUI.Samples/Uno.Toolkit.WinUI.Samples.Mobile/MacCatalyst/Info.plist +++ b/samples/Uno.Toolkit.WinUI.Samples/Uno.Toolkit.WinUI.Samples.Mobile/MacCatalyst/Info.plist @@ -16,7 +16,8 @@ XSAppIconAssets Assets.xcassets/iconapp.appiconset - + NSPhotoLibraryAddUsageDescription + This app would like to save photos to your gallery