-
Notifications
You must be signed in to change notification settings - Fork 29
feat: MediaGallery
#1146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: MediaGallery
#1146
Changes from all commits
42242b1
889b460
23d966f
648e32d
3c36c6f
d2824e0
a1f2d07
1f0282a
cb63db8
a66c75d
444cda4
8db1f4c
926abd4
606ffd3
0a3a0e4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -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). | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
## Methods | ||||||
|
||||||
### CheckAccessAsync | ||||||
|
||||||
```csharp | ||||||
public static async Task<bool> CheckAccessAsync() | ||||||
``` | ||||||
|
||||||
#### Summary | ||||||
|
||||||
Checks the user's permission to access the device's gallery. Will trigger the permission request if not already granted. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
#### Returns | ||||||
|
||||||
A `Task<bool>` 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 | ||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> | ||||||
``` | ||||||
|
||||||
### iOS & Mac Catalyst | ||||||
|
||||||
If your app only supports iOS 14 and newer, update your `Info.plist` as follows: | ||||||
|
||||||
```xml | ||||||
<key>NSPhotoLibraryAddUsageDescription</key> | ||||||
<string>This app needs access to the photo gallery for saving photos and videos</string> | ||||||
``` | ||||||
|
||||||
If you want to support earlier versions iOS, add the following as well: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
```xml | ||||||
<key>NSPhotoLibraryUsageDescription</key> | ||||||
<string>This app needs access to the photo gallery for saving photos and videos</string> | ||||||
``` | ||||||
|
||||||
## 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 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
```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 | ||||||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
<Page | ||
x:Class="Uno.Toolkit.Samples.Content.Helpers.MediaGalleryHelperSamplePage" | ||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" | ||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | ||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" | ||
xmlns:helpers="using:Uno.Toolkit.Samples.Content.Helpers" | ||
xmlns:local="using:Uno.Toolkit.Samples.Content.Helpers" | ||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" | ||
xmlns:sample="using:Uno.Toolkit.Samples" | ||
xmlns:utu="using:Uno.Toolkit.UI" | ||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" | ||
mc:Ignorable="d"> | ||
|
||
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> | ||
<sample:SamplePageLayout IsDesignAgnostic="True"> | ||
<sample:SamplePageLayout.DesignAgnosticTemplate> | ||
<DataTemplate> | ||
<StackPanel> | ||
<Button Command="{Binding Data.CheckAccessCommand}">Check access</Button> | ||
<Button Command="{Binding Data.SaveCommand}">Save UnoLogo.png to gallery</Button> | ||
<Button Command="{Binding Data.SaveRandomNameCommand}">Save with random name to gallery</Button> | ||
</StackPanel> | ||
</DataTemplate> | ||
</sample:SamplePageLayout.DesignAgnosticTemplate> | ||
</sample:SamplePageLayout> | ||
</Grid> | ||
</Page> |
MartinZikmund marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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))] | ||
MartinZikmund marked this conversation as resolved.
Show resolved
Hide resolved
|
||
#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 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
#if __IOS__ || __ANDROID__ | ||
namespace Uno.Toolkit.UI; | ||
|
||
/// <summary> | ||
/// Represents a media file type. | ||
/// </summary> | ||
public enum MediaFileType | ||
{ | ||
/// <summary> | ||
/// Image media file type. | ||
/// </summary> | ||
Image, | ||
|
||
/// <summary> | ||
/// Video media file type. | ||
/// </summary> | ||
Video, | ||
} | ||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.