diff --git a/Content/Media/baboon.png b/Content/Media/baboon.png
new file mode 100644
index 000000000..188cae51b
Binary files /dev/null and b/Content/Media/baboon.png differ
diff --git a/Content/Media/earth.mp4 b/Content/Media/earth.mp4
new file mode 100644
index 000000000..b11552f9c
Binary files /dev/null and b/Content/Media/earth.mp4 differ
diff --git a/Content/Media/lomonosov.jpg b/Content/Media/lomonosov.jpg
new file mode 100644
index 000000000..1a68fbffd
Binary files /dev/null and b/Content/Media/lomonosov.jpg differ
diff --git a/Content/Media/newtons_cradle.gif b/Content/Media/newtons_cradle.gif
new file mode 100644
index 000000000..7c05ee1e7
Binary files /dev/null and b/Content/Media/newtons_cradle.gif differ
diff --git a/DeviceTests/DeviceTests.Shared/DeviceTests.Shared.csproj b/DeviceTests/DeviceTests.Shared/DeviceTests.Shared.csproj
index 418312c80..d3638e7be 100644
--- a/DeviceTests/DeviceTests.Shared/DeviceTests.Shared.csproj
+++ b/DeviceTests/DeviceTests.Shared/DeviceTests.Shared.csproj
@@ -28,7 +28,8 @@
-
+
+
diff --git a/DeviceTests/DeviceTests.Shared/MediaGallery_Tests.cs b/DeviceTests/DeviceTests.Shared/MediaGallery_Tests.cs
new file mode 100644
index 000000000..df7382f57
--- /dev/null
+++ b/DeviceTests/DeviceTests.Shared/MediaGallery_Tests.cs
@@ -0,0 +1,36 @@
+using System.Linq;
+using System.Reflection;
+using System.Threading.Tasks;
+using Xamarin.Essentials;
+using Xunit;
+
+namespace DeviceTests.Shared
+{
+ public class MediaGallery_Tests
+ {
+ const string jpgName = "lomonosov.jpg";
+
+ [Fact]
+ [Trait(Traits.InteractionType, Traits.InteractionTypes.Human)]
+ public async Task SaveAsync()
+ {
+ var platform = DeviceInfo.Platform;
+ await MainThread.InvokeOnMainThreadAsync(async () =>
+ {
+ if (platform == DevicePlatform.iOS || platform == DevicePlatform.macOS)
+ await Permissions.RequestAsync();
+ else if (platform == DevicePlatform.Android)
+ await Permissions.RequestAsync();
+ });
+
+ var assembly = typeof(MediaGallery_Tests).GetTypeInfo().Assembly;
+ var resourceName = assembly
+ .GetManifestResourceNames()
+ .FirstOrDefault(n => n.EndsWith(jpgName));
+
+ using var fileStream = assembly.GetManifestResourceStream(resourceName);
+
+ await MediaGallery.SaveAsync(MediaFileType.Image, fileStream, jpgName);
+ }
+ }
+}
diff --git a/Samples/Samples.Mac/Info.plist b/Samples/Samples.Mac/Info.plist
index cd8219a3b..c81e407b8 100644
--- a/Samples/Samples.Mac/Info.plist
+++ b/Samples/Samples.Mac/Info.plist
@@ -45,5 +45,7 @@
NSContactsUsageDescription
Contacts
+ NSPhotoLibraryUsageDescription
+ Photos
diff --git a/Samples/Samples.UWP/Package.appxmanifest b/Samples/Samples.UWP/Package.appxmanifest
index 58a3ef232..6d1c06da2 100644
--- a/Samples/Samples.UWP/Package.appxmanifest
+++ b/Samples/Samples.UWP/Package.appxmanifest
@@ -32,6 +32,8 @@
+
+
diff --git a/Samples/Samples/App.xaml.cs b/Samples/Samples/App.xaml.cs
index ee6c39cef..f5032c1d3 100644
--- a/Samples/Samples/App.xaml.cs
+++ b/Samples/Samples/App.xaml.cs
@@ -24,10 +24,10 @@ public partial class App : Application
public App()
{
- InitializeComponent();
-
// Enable currently experimental features
- Device.SetFlags(new string[] { "MediaElement_Experimental" });
+ Device.SetFlags(new string[] { "MediaElement_Experimental", "RadioButton_Experimental" });
+
+ InitializeComponent();
VersionTracking.Track();
diff --git a/Samples/Samples/Helpers/EmbeddedResourceProvider.cs b/Samples/Samples/Helpers/EmbeddedResourceProvider.cs
new file mode 100644
index 000000000..b4ca6d6a6
--- /dev/null
+++ b/Samples/Samples/Helpers/EmbeddedResourceProvider.cs
@@ -0,0 +1,44 @@
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using Xamarin.Forms;
+
+namespace Samples.Helpers
+{
+ public static class EmbeddedMedia
+ {
+ public const string baboonPng = "baboon.png";
+ public const string earthMp4 = "earth.mp4";
+ public const string lomonosovJpg = "lomonosov.jpg";
+ public const string newtonsCradleGif = "newtons_cradle.gif";
+ }
+
+ public static class EmbeddedResourceProvider
+ {
+ static readonly Assembly assembly = typeof(EmbeddedResourceProvider).GetTypeInfo().Assembly;
+ static readonly string[] resources = assembly.GetManifestResourceNames();
+
+ public static Stream Load(string name)
+ {
+ name = GetFullName(name);
+
+ if (string.IsNullOrWhiteSpace(name))
+ return null;
+
+ return assembly.GetManifestResourceStream(name);
+ }
+
+ public static ImageSource GetImageSource(string name)
+ {
+ name = GetFullName(name);
+
+ if (string.IsNullOrWhiteSpace(name))
+ return null;
+
+ return ImageSource.FromResource(name, assembly);
+ }
+
+ static string GetFullName(string name)
+ => resources.FirstOrDefault(n => n.EndsWith($".Media.{name}"));
+ }
+}
diff --git a/Samples/Samples/Samples.csproj b/Samples/Samples/Samples.csproj
index dd9a13ad1..fce159436 100644
--- a/Samples/Samples/Samples.csproj
+++ b/Samples/Samples/Samples.csproj
@@ -22,6 +22,7 @@
+
diff --git a/Samples/Samples/View/MediaGalleryPage.xaml b/Samples/Samples/View/MediaGalleryPage.xaml
new file mode 100644
index 000000000..a0a329751
--- /dev/null
+++ b/Samples/Samples/View/MediaGalleryPage.xaml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Samples/Samples/View/MediaGalleryPage.xaml.cs b/Samples/Samples/View/MediaGalleryPage.xaml.cs
new file mode 100644
index 000000000..4e52bbcd6
--- /dev/null
+++ b/Samples/Samples/View/MediaGalleryPage.xaml.cs
@@ -0,0 +1,10 @@
+namespace Samples.View
+{
+ public partial class MediaGalleryPage : BasePage
+ {
+ public MediaGalleryPage()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/Samples/Samples/ViewModel/HomeViewModel.cs b/Samples/Samples/ViewModel/HomeViewModel.cs
index b2ca693f3..02dff9a94 100644
--- a/Samples/Samples/ViewModel/HomeViewModel.cs
+++ b/Samples/Samples/ViewModel/HomeViewModel.cs
@@ -240,6 +240,12 @@ public HomeViewModel()
typeof(WebAuthenticatorPage),
"Quickly and easily authenticate and wait for a callback.",
new[] { "auth", "authenticate", "authenticator", "web", "webauth" }),
+ new SampleItem(
+ "🖼",
+ "Media Gallery",
+ typeof(MediaGalleryPage),
+ "Quickly and easily manage media files",
+ new[] { "save", "media", "gallery", "image", "video", "jpg", "png" })
};
filteredItems = samples;
filterText = string.Empty;
diff --git a/Samples/Samples/ViewModel/MediaGalleryViewModel.cs b/Samples/Samples/ViewModel/MediaGalleryViewModel.cs
new file mode 100644
index 000000000..ff69f31bc
--- /dev/null
+++ b/Samples/Samples/ViewModel/MediaGalleryViewModel.cs
@@ -0,0 +1,92 @@
+using System;
+using System.IO;
+using System.Net;
+using System.Threading.Tasks;
+using System.Windows.Input;
+using Samples.Helpers;
+using Xamarin.Essentials;
+using Xamarin.Forms;
+
+namespace Samples.ViewModel
+{
+ public class MediaGalleryViewModel : BaseViewModel
+ {
+ public MediaGalleryViewModel()
+ {
+ SavevPngCommand = new Command(() => Save(MediaFileType.Image, EmbeddedMedia.baboonPng));
+ SaveJpgCommand = new Command(() => Save(MediaFileType.Image, EmbeddedMedia.lomonosovJpg));
+ SaveGifCommand = new Command(() => Save(MediaFileType.Image, EmbeddedMedia.newtonsCradleGif));
+ SaveVideoCommand = new Command(() => Save(MediaFileType.Video, EmbeddedMedia.earthMp4));
+
+ PngSource = EmbeddedResourceProvider.GetImageSource(EmbeddedMedia.baboonPng);
+ JpgSource = EmbeddedResourceProvider.GetImageSource(EmbeddedMedia.lomonosovJpg);
+ GifSource = EmbeddedResourceProvider.GetImageSource(EmbeddedMedia.newtonsCradleGif);
+ }
+
+ public bool FromStream { get; set; } = true;
+
+ public bool FromByteArray { get; set; }
+
+ public bool FromCacheDirectory { get; set; }
+
+ public ImageSource PngSource { get; }
+
+ public ImageSource JpgSource { get; }
+
+ public ImageSource GifSource { get; }
+
+ public ICommand SavevPngCommand { get; }
+
+ public ICommand SaveJpgCommand { get; }
+
+ public ICommand SaveGifCommand { get; }
+
+ public ICommand SaveVideoCommand { get; }
+
+ async void Save(MediaFileType type, string name)
+ {
+ try
+ {
+ using var stream = EmbeddedResourceProvider.Load(name);
+
+ if (FromStream)
+ {
+ await MediaGallery.SaveAsync(type, stream, name);
+ }
+ else if (FromByteArray)
+ {
+ using var memoryStream = new MemoryStream();
+ stream.CopyTo(memoryStream);
+
+ await MediaGallery.SaveAsync(type, memoryStream.ToArray(), name);
+ }
+ else if (FromCacheDirectory)
+ {
+ var filePath = SaveFileToCache(stream, name);
+
+ await MediaGallery.SaveAsync(type, filePath);
+ }
+
+ await DisplayAlertAsync("Save Completed Successfully");
+ }
+ catch (Exception ex)
+ {
+ await DisplayAlertAsync(ex.Message);
+ }
+ }
+
+ string SaveFileToCache(Stream data, string fileName)
+ {
+ var filePath = Path.Combine(FileSystem.CacheDirectory, fileName);
+
+ if (File.Exists(filePath))
+ File.Delete(filePath);
+
+ var stream = File.Create(filePath);
+ data.CopyTo(stream);
+ stream.Close();
+
+ return filePath;
+ }
+ }
+}
diff --git a/Xamarin.Essentials/MediaGallery/MediaGallery.android.cs b/Xamarin.Essentials/MediaGallery/MediaGallery.android.cs
new file mode 100644
index 000000000..6b5a06b3d
--- /dev/null
+++ b/Xamarin.Essentials/MediaGallery/MediaGallery.android.cs
@@ -0,0 +1,113 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Android.Content;
+using Android.Provider;
+using Android.Webkit;
+using Environment = Android.OS.Environment;
+using File = Java.IO.File;
+using MediaColumns = Android.Provider.MediaStore.MediaColumns;
+using Path = System.IO.Path;
+using Stream = System.IO.Stream;
+using Uri = Android.Net.Uri;
+
+namespace Xamarin.Essentials
+{
+ public static partial class MediaGallery
+ {
+ static async Task PlatformSaveAsync(MediaFileType type, byte[] data, string fileName)
+ {
+ using var ms = new MemoryStream(data);
+ await PlatformSaveAsync(type, ms, fileName);
+ }
+
+ static async Task PlatformSaveAsync(MediaFileType type, string filePath)
+ {
+ using var fileStream = System.IO.File.OpenRead(filePath);
+ await PlatformSaveAsync(type, fileStream, Path.GetFileName(filePath));
+ }
+
+ static async Task PlatformSaveAsync(MediaFileType type, Stream fileStream, string fileName)
+ {
+ var albumName = AppInfo.Name;
+
+ await Permissions.EnsureGrantedAsync();
+
+ var context = Platform.AppContext;
+ var dateTimeNow = DateTime.Now;
+
+ var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName);
+ var extension = Path.GetExtension(fileName).ToLower();
+ var newFileName = $"{fileNameWithoutExtension}_{dateTimeNow:yyyyMMdd_HHmmss}{extension}";
+
+ using var values = new ContentValues();
+
+ values.Put(MediaColumns.DateAdded, TimeSeconds(dateTimeNow));
+ values.Put(MediaColumns.Title, fileNameWithoutExtension);
+ values.Put(MediaColumns.DisplayName, newFileName);
+
+ var mimeType = MimeTypeMap.Singleton.GetMimeTypeFromExtension(extension.Replace(".", string.Empty));
+ if (!string.IsNullOrWhiteSpace(mimeType))
+ values.Put(MediaColumns.MimeType, mimeType);
+
+ using var externalContentUri = type == MediaFileType.Image
+ ? MediaStore.Images.Media.ExternalContentUri
+ : MediaStore.Video.Media.ExternalContentUri;
+
+ var relativePath = type == MediaFileType.Image
+ ? Environment.DirectoryPictures
+ : Environment.DirectoryMovies;
+
+ if (Platform.HasApiLevelQ)
+ {
+#if MONOANDROID10_0
+ values.Put(MediaColumns.RelativePath, Path.Combine(relativePath, albumName));
+ values.Put(MediaColumns.IsPending, true);
+
+ using var uri = context.ContentResolver.Insert(externalContentUri, values);
+ using var stream = context.ContentResolver.OpenOutputStream(uri);
+ fileStream.CopyTo(stream);
+ stream.Close();
+
+ values.Put(MediaColumns.IsPending, false);
+ context.ContentResolver.Update(uri, values, null, null);
+#else
+ throw new Exception("For using save to gallery on android 10 and newer, use the target TargetFrameworks MonoAndroid10.0 or MonoAndroid11.0");
+#endif
+ }
+ else
+ {
+#pragma warning disable CS0618 // Type or member is obsolete
+ using var directory = new File(Environment.GetExternalStoragePublicDirectory(relativePath), albumName);
+
+ directory.Mkdirs();
+ using var file = new File(directory, newFileName);
+
+ using var fileOutputStream = System.IO.File.Create(file.AbsolutePath);
+ fileStream.CopyTo(fileOutputStream);
+ fileOutputStream.Close();
+
+ values.Put(MediaColumns.Data, file.AbsolutePath);
+ context.ContentResolver.Insert(externalContentUri, values);
+
+ using var mediaScanIntent = new Intent(Intent.ActionMediaScannerScanFile);
+ mediaScanIntent.SetData(Uri.FromFile(file));
+ context.SendBroadcast(mediaScanIntent);
+#pragma warning restore CS0618 // Type or member is obsolete
+ }
+ }
+
+ static readonly DateTime jan1st1970 = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+
+ // JavaSystem.CurrentTimeMillis()
+
+ static long TimeMillis(DateTime current)
+ => (long)CalcTimeDifference(current).TotalMilliseconds;
+
+ static long TimeSeconds(DateTime current)
+ => (long)CalcTimeDifference(current).TotalSeconds;
+
+ static TimeSpan CalcTimeDifference(DateTime current)
+ => current.ToUniversalTime() - jan1st1970;
+ }
+}
diff --git a/Xamarin.Essentials/MediaGallery/MediaGallery.ios.macos.cs b/Xamarin.Essentials/MediaGallery/MediaGallery.ios.macos.cs
new file mode 100644
index 000000000..c4287e6db
--- /dev/null
+++ b/Xamarin.Essentials/MediaGallery/MediaGallery.ios.macos.cs
@@ -0,0 +1,102 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Foundation;
+using Photos;
+
+namespace Xamarin.Essentials
+{
+ public static partial class MediaGallery
+ {
+ static readonly string cacheDir = "EssentialsPhotosCacheDir";
+
+ static async Task PlatformSaveAsync(MediaFileType type, byte[] data, string fileName)
+ {
+ string filePath = null;
+
+ try
+ {
+ filePath = GetFilePath(fileName);
+ await File.WriteAllBytesAsync(filePath, data);
+
+ await PlatformSaveAsync(type, filePath);
+ }
+ finally
+ {
+ DeleteFile(filePath);
+ }
+ }
+
+ static async Task PlatformSaveAsync(MediaFileType type, Stream fileStream, string fileName)
+ {
+ string filePath = null;
+
+ try
+ {
+ filePath = GetFilePath(fileName);
+ using var stream = File.Create(filePath);
+ await fileStream.CopyToAsync(stream);
+ stream.Close();
+
+ await PlatformSaveAsync(type, filePath);
+ }
+ finally
+ {
+ DeleteFile(filePath);
+ }
+ }
+
+ static async Task PlatformSaveAsync(MediaFileType type, string filePath)
+ {
+ using var fileUri = new NSUrl(filePath);
+
+ await PhotoLibraryPerformChanges(() =>
+ {
+ using var request = type == MediaFileType.Video
+ ? PHAssetChangeRequest.FromVideo(fileUri)
+ : PHAssetChangeRequest.FromImage(fileUri);
+ });
+ }
+
+ static async Task PhotoLibraryPerformChanges(Action action)
+ {
+ var tcs = new TaskCompletionSource();
+
+ PHPhotoLibrary.SharedPhotoLibrary.PerformChanges(
+ () =>
+ {
+ try
+ {
+ action.Invoke();
+ }
+ catch (Exception ex)
+ {
+ tcs.TrySetResult(ex);
+ }
+ },
+ (success, error) =>
+ tcs.TrySetResult(error != null ? new NSErrorException(error) : null));
+
+ var exception = await tcs.Task;
+ if (exception != null)
+ throw exception;
+ }
+
+ static void DeleteFile(string filePath)
+ {
+ if (!string.IsNullOrWhiteSpace(filePath) && File.Exists(filePath))
+ File.Delete(filePath);
+ }
+
+ static string GetFilePath(string fileName)
+ {
+ fileName = fileName.Trim();
+ var dirPath = Path.Combine(FileSystem.CacheDirectory, cacheDir);
+ var filePath = Path.Combine(dirPath, fileName);
+
+ if (!Directory.Exists(dirPath))
+ Directory.CreateDirectory(dirPath);
+ return filePath;
+ }
+ }
+}
diff --git a/Xamarin.Essentials/MediaGallery/MediaGallery.netstandard.tvos.watchos.cs b/Xamarin.Essentials/MediaGallery/MediaGallery.netstandard.tvos.watchos.cs
new file mode 100644
index 000000000..9c9b2a1cd
--- /dev/null
+++ b/Xamarin.Essentials/MediaGallery/MediaGallery.netstandard.tvos.watchos.cs
@@ -0,0 +1,17 @@
+using System.IO;
+using System.Threading.Tasks;
+
+namespace Xamarin.Essentials
+{
+ public static partial class MediaGallery
+ {
+ static Task PlatformSaveAsync(MediaFileType type, byte[] data, string fileName)
+ => throw ExceptionUtils.NotSupportedOrImplementedException;
+
+ static Task PlatformSaveAsync(MediaFileType type, string filePath)
+ => throw ExceptionUtils.NotSupportedOrImplementedException;
+
+ static Task PlatformSaveAsync(MediaFileType type, Stream fileStream, string fileName)
+ => throw ExceptionUtils.NotSupportedOrImplementedException;
+ }
+}
diff --git a/Xamarin.Essentials/MediaGallery/MediaGallery.shared.cs b/Xamarin.Essentials/MediaGallery/MediaGallery.shared.cs
new file mode 100644
index 000000000..8aa7ba420
--- /dev/null
+++ b/Xamarin.Essentials/MediaGallery/MediaGallery.shared.cs
@@ -0,0 +1,49 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+
+namespace Xamarin.Essentials
+{
+ public enum MediaFileType
+ {
+ Image,
+ Video
+ }
+
+ public static partial class MediaGallery
+ {
+ public static Task SaveAsync(MediaFileType type, Stream fileStream, string fileName)
+ {
+ if (fileStream == null)
+ throw new ArgumentNullException(nameof(fileStream));
+ CheckParameters(fileName);
+
+ return PlatformSaveAsync(type, fileStream, fileName);
+ }
+
+ public static Task SaveAsync(MediaFileType type, byte[] data, string fileName)
+ {
+ if (data == null || !(data.Length > 0))
+ throw new ArgumentNullException(nameof(data));
+ CheckParameters(fileName);
+
+ return PlatformSaveAsync(type, data, fileName);
+ }
+
+ public static Task SaveAsync(MediaFileType type, string filePath)
+ {
+#if !NETSTANDARD1_0
+ if (string.IsNullOrWhiteSpace(filePath) || !File.Exists(filePath))
+ throw new ArgumentException(nameof(filePath));
+#endif
+
+ return PlatformSaveAsync(type, filePath);
+ }
+
+ static void CheckParameters(string fileName)
+ {
+ if (string.IsNullOrWhiteSpace(fileName))
+ throw new ArgumentException(nameof(fileName));
+ }
+ }
+}
diff --git a/Xamarin.Essentials/MediaGallery/MediaGallery.tizen.cs b/Xamarin.Essentials/MediaGallery/MediaGallery.tizen.cs
new file mode 100644
index 000000000..1ba90fe30
--- /dev/null
+++ b/Xamarin.Essentials/MediaGallery/MediaGallery.tizen.cs
@@ -0,0 +1,18 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+
+namespace Xamarin.Essentials
+{
+ public static partial class MediaGallery
+ {
+ static Task PlatformSaveAsync(MediaFileType type, byte[] data, string fileName)
+ => throw ExceptionUtils.NotSupportedOrImplementedException;
+
+ static Task PlatformSaveAsync(MediaFileType type, string filePath)
+ => throw ExceptionUtils.NotSupportedOrImplementedException;
+
+ static Task PlatformSaveAsync(MediaFileType type, Stream fileStream, string fileName)
+ => throw ExceptionUtils.NotSupportedOrImplementedException;
+ }
+}
diff --git a/Xamarin.Essentials/MediaGallery/MediaGallery.uwp.cs b/Xamarin.Essentials/MediaGallery/MediaGallery.uwp.cs
new file mode 100644
index 000000000..6097f6cbe
--- /dev/null
+++ b/Xamarin.Essentials/MediaGallery/MediaGallery.uwp.cs
@@ -0,0 +1,49 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices.WindowsRuntime;
+using System.Threading.Tasks;
+using Windows.Storage;
+
+namespace Xamarin.Essentials
+{
+ public static partial class MediaGallery
+ {
+ static async Task PlatformSaveAsync(MediaFileType type, byte[] data, string fileName)
+ {
+ var file = await GetStorageFile(type, fileName);
+ var buffer = WindowsRuntimeBuffer.Create(data, 0, data.Length, data.Length);
+ await FileIO.WriteBufferAsync(file, buffer);
+ }
+
+ static async Task PlatformSaveAsync(MediaFileType type, string filePath)
+ {
+ using var fileStream = File.OpenRead(filePath);
+ await PlatformSaveAsync(type, fileStream, Path.GetFileName(filePath));
+ }
+
+ static async Task PlatformSaveAsync(MediaFileType type, Stream fileStream, string fileName)
+ {
+ var file = await GetStorageFile(type, fileName);
+ using var stream = await file.OpenStreamForWriteAsync();
+ await fileStream.CopyToAsync(stream);
+ stream.Close();
+ }
+
+ static async Task GetStorageFile(MediaFileType type, string fileName)
+ {
+ var albumFolder = await GetAlbumFolder(type, AppInfo.Name);
+ return await albumFolder.CreateFileAsync(fileName, CreationCollisionOption.GenerateUniqueName);
+ }
+
+ static async Task GetAlbumFolder(MediaFileType type, string albumName)
+ {
+ var mediaFolder = type == MediaFileType.Image
+ ? KnownFolders.PicturesLibrary
+ : KnownFolders.VideosLibrary;
+
+ var folder = (await mediaFolder.GetFoldersAsync())?.FirstOrDefault(a => a.Name == albumName);
+ return folder ?? await mediaFolder.CreateFolderAsync(albumName);
+ }
+ }
+}
diff --git a/Xamarin.Essentials/Permissions/Permissions.ios.tvos.cs b/Xamarin.Essentials/Permissions/Permissions.ios.tvos.macos.cs
similarity index 100%
rename from Xamarin.Essentials/Permissions/Permissions.ios.tvos.cs
rename to Xamarin.Essentials/Permissions/Permissions.ios.tvos.macos.cs
diff --git a/docs/en/FrameworksIndex/xamarin-essentials-android.xml b/docs/en/FrameworksIndex/xamarin-essentials-android.xml
index c83f2f067..7d72b408f 100644
--- a/docs/en/FrameworksIndex/xamarin-essentials-android.xml
+++ b/docs/en/FrameworksIndex/xamarin-essentials-android.xml
@@ -585,6 +585,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/docs/en/FrameworksIndex/xamarin-essentials-ios.xml b/docs/en/FrameworksIndex/xamarin-essentials-ios.xml
index 021875739..c94248b50 100644
--- a/docs/en/FrameworksIndex/xamarin-essentials-ios.xml
+++ b/docs/en/FrameworksIndex/xamarin-essentials-ios.xml
@@ -564,6 +564,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/docs/en/FrameworksIndex/xamarin-essentials-macos.xml b/docs/en/FrameworksIndex/xamarin-essentials-macos.xml
index a0eb6f453..97abefa53 100644
--- a/docs/en/FrameworksIndex/xamarin-essentials-macos.xml
+++ b/docs/en/FrameworksIndex/xamarin-essentials-macos.xml
@@ -563,6 +563,15 @@
+
+
+
+
+
+
+
+
+
@@ -700,6 +709,9 @@
+
+
+
diff --git a/docs/en/FrameworksIndex/xamarin-essentials-tvos.xml b/docs/en/FrameworksIndex/xamarin-essentials-tvos.xml
index 2cb23a385..0e0d61d0f 100644
--- a/docs/en/FrameworksIndex/xamarin-essentials-tvos.xml
+++ b/docs/en/FrameworksIndex/xamarin-essentials-tvos.xml
@@ -563,6 +563,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/docs/en/FrameworksIndex/xamarin-essentials-uwp.xml b/docs/en/FrameworksIndex/xamarin-essentials-uwp.xml
index 84b1d398c..24ac9c485 100644
--- a/docs/en/FrameworksIndex/xamarin-essentials-uwp.xml
+++ b/docs/en/FrameworksIndex/xamarin-essentials-uwp.xml
@@ -565,6 +565,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/docs/en/FrameworksIndex/xamarin-essentials-watchos.xml b/docs/en/FrameworksIndex/xamarin-essentials-watchos.xml
index 6a74f770c..2abec29c1 100644
--- a/docs/en/FrameworksIndex/xamarin-essentials-watchos.xml
+++ b/docs/en/FrameworksIndex/xamarin-essentials-watchos.xml
@@ -563,6 +563,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/docs/en/FrameworksIndex/xamarin-essentials.xml b/docs/en/FrameworksIndex/xamarin-essentials.xml
index 0c91ab3ec..52f1e2bf3 100644
--- a/docs/en/FrameworksIndex/xamarin-essentials.xml
+++ b/docs/en/FrameworksIndex/xamarin-essentials.xml
@@ -562,6 +562,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/docs/en/Xamarin.Essentials/MediaFileType.xml b/docs/en/Xamarin.Essentials/MediaFileType.xml
new file mode 100644
index 000000000..8cdc5531b
--- /dev/null
+++ b/docs/en/Xamarin.Essentials/MediaFileType.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+ Xamarin.Essentials
+ 1.0.0.0
+
+
+ System.Enum
+
+
+ Enumerates the possible types of media files
+
+
+
+
+
+
+
+ Field
+
+ Xamarin.Essentials
+ 1.0.0.0
+
+
+ Xamarin.Essentials.MediaFileType
+
+ 0
+
+ Image
+
+
+
+
+
+
+ Field
+
+ Xamarin.Essentials
+ 1.0.0.0
+
+
+ Xamarin.Essentials.MediaFileType
+
+ 1
+
+ Video
+
+
+
+
diff --git a/docs/en/Xamarin.Essentials/MediaGallery.xml b/docs/en/Xamarin.Essentials/MediaGallery.xml
new file mode 100644
index 000000000..6b0480555
--- /dev/null
+++ b/docs/en/Xamarin.Essentials/MediaGallery.xml
@@ -0,0 +1,95 @@
+
+
+
+
+
+ Xamarin.Essentials
+ 1.0.0.0
+
+
+ System.Object
+
+
+
+ Class for accessing the device's native gallery.
+
+
+
+
+
+
+
+ Method
+
+ Xamarin.Essentials
+ 1.0.0.0
+
+
+ System.Threading.Tasks.Task
+
+
+
+
+
+
+ Type of media file to save.
+ Physical path to the file.
+ Saves media files to the device's gallery
+
+
+
+
+
+
+
+
+ Method
+
+ Xamarin.Essentials
+ 1.0.0.0
+
+
+ System.Threading.Tasks.Task
+
+
+
+
+
+
+
+ Type of media file to save.
+ Bytes to save to the file.
+ Name of the file with extension
+ Saves media files to the device's gallery
+
+
+
+
+
+
+
+
+ Method
+
+ Xamarin.Essentials
+ 1.0.0.0
+
+
+ System.Threading.Tasks.Task
+
+
+
+
+
+
+
+ Type of media file to save.
+ Stream to save to the file.
+ Name of the file with extension
+ Saves media files to the device's gallery
+
+
+
+
+
+
diff --git a/docs/en/index.xml b/docs/en/index.xml
index 5ffb9c6f3..4784a4728 100644
--- a/docs/en/index.xml
+++ b/docs/en/index.xml
@@ -195,6 +195,8 @@
+
+