Skip to content
This repository was archived by the owner on May 15, 2024. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
819c6f9
init SaveToGalery
dimonovdd Jan 9, 2021
a5d1a9f
fixed typos
dimonovdd Jan 10, 2021
6cc65af
Merge branch 'main' into featureSaveToGalery
mattleibow Jan 15, 2021
be537b6
Merge branch 'main' into featureSaveToGalery
dimonovdd Jan 16, 2021
e78d168
Merge branch 'main' into featureSaveToGalery
dimonovdd Jan 24, 2021
797bc90
added SaveToGaleryTestPhoto.jpg
dimonovdd Jan 24, 2021
ebde333
implementation for creating photos albums on ios
dimonovdd Jan 24, 2021
b376239
implementation for saving images and video files with metada on ios
dimonovdd Jan 25, 2021
eaa70f2
Merge branch 'main' into featureSaveToGalery
dimonovdd Jan 26, 2021
7e358a5
- implementation for saving images and video files from filePath on ios
dimonovdd Jan 26, 2021
9743df7
- implementation for saving images and video files from byte array, f…
dimonovdd Jan 30, 2021
ba09fb3
init SaveToGalery
dimonovdd Jan 9, 2021
3fa012b
fixed typos
dimonovdd Jan 10, 2021
37f8c02
added SaveToGaleryTestPhoto.jpg
dimonovdd Jan 24, 2021
c8a35d6
implementation for creating photos albums on ios
dimonovdd Jan 24, 2021
9c83cd9
implementation for saving images and video files with metada on ios
dimonovdd Jan 25, 2021
702dbbf
- implementation for saving images and video files from filePath on ios
dimonovdd Jan 26, 2021
7e3b885
- implementation for saving images and video files from byte array, f…
dimonovdd Jan 30, 2021
b647ab9
Merge branch 'featureSaveToGalery' of https://github.com/dimonovdd/Es…
dimonovdd Jan 30, 2021
42396aa
added macos implementation
dimonovdd Jan 30, 2021
e914f8e
Merge branch 'main' into featureSaveToGalery
dimonovdd Feb 13, 2021
c53a910
- impl UWP
dimonovdd Feb 13, 2021
665b88a
cleanup android implementation
dimonovdd Feb 13, 2021
fe8ba32
changes for PHAuthorizationStatus.Limited (iOS)
dimonovdd Feb 14, 2021
d8b4045
Removed DATE_TAKEN from SaveAsync method on droid
dimonovdd Feb 19, 2021
ee6335b
Merge branch 'main' into featureSaveToGalery
dimonovdd Feb 19, 2021
4d919db
reset DeviceTests.Android.csproj
dimonovdd Feb 20, 2021
3d79bfb
Renaming from SaveToGallery to MediaGallery
dimonovdd Feb 28, 2021
8cee060
updating sample and adding sample media files
dimonovdd Feb 28, 2021
8f14db4
added test for Save to gallery
dimonovdd Feb 28, 2021
5d95923
removed the functionality for creating albums
dimonovdd Apr 11, 2021
b71de73
fix typos in MediaGallery Tests
dimonovdd Apr 14, 2021
b5d5a6e
add docs and fix uwp
dimonovdd Apr 22, 2021
c722861
Merge branch 'main' into featureSaveToGalery
dimonovdd Jul 12, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added Assets/SaveToGaleryTestPhoto.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 1 addition & 3 deletions DeviceTests/DeviceTests.Android/DeviceTests.Android.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
<RootNamespace>DeviceTests.Droid</RootNamespace>
<AssemblyName>XamarinEssentialsDeviceTestsAndroid</AssemblyName>
<TargetFrameworkVersion>v10.0</TargetFrameworkVersion>
<AndroidUseLatestPlatformSdk>false</AndroidUseLatestPlatformSdk>
<AndroidApplication>True</AndroidApplication>
<AndroidUseIntermediateDesignerFile>true</AndroidUseIntermediateDesignerFile>
<AndroidResgenClass>Resource</AndroidResgenClass>
Expand All @@ -19,6 +18,7 @@
<AndroidEnableSGenConcurrent>true</AndroidEnableSGenConcurrent>
<AndroidUseAapt2>true</AndroidUseAapt2>
<AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType>
<AndroidSupportedAbis>armeabi-v7a;x86;x86_64;arm64-v8a</AndroidSupportedAbis>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
Expand All @@ -29,7 +29,6 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AndroidLinkMode>None</AndroidLinkMode>
<AndroidSupportedAbis>armeabi-v7a;x86;x86_64;arm64-v8a</AndroidSupportedAbis>
<JavaMaximumHeapSize>1G</JavaMaximumHeapSize>
<AotAssemblies>false</AotAssemblies>
<EnableLLVM>false</EnableLLVM>
Expand All @@ -47,7 +46,6 @@
<WarningLevel>4</WarningLevel>
<AndroidManagedSymbols>true</AndroidManagedSymbols>
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
<AndroidSupportedAbis>armeabi-v7a;x86;x86_64;arm64-v8a</AndroidSupportedAbis>
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
<JavaMaximumHeapSize>1G</JavaMaximumHeapSize>
</PropertyGroup>
Expand Down
2 changes: 2 additions & 0 deletions Samples/Samples.Mac/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,7 @@
</array>
<key>NSContactsUsageDescription</key>
<string>Contacts</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Photos</string>
</dict>
</plist>
2 changes: 2 additions & 0 deletions Samples/Samples.UWP/Package.appxmanifest
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
<Capabilities>
<Capability Name="internetClient" />
<uap:Capability Name="contacts"/>
<uap:Capability Name="picturesLibrary"/>
<uap:Capability Name="videosLibrary"/>
<DeviceCapability Name="location" />
<DeviceCapability Name="microphone"/>
<DeviceCapability Name="webcam"/>
Expand Down
4 changes: 2 additions & 2 deletions Samples/Samples/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ public partial class App : Application

public App()
{
InitializeComponent();

// Enable currently experimental features
Device.SetFlags(new string[] { "MediaElement_Experimental" });

InitializeComponent();

VersionTracking.Track();

MainPage = new NavigationPage(new HomePage());
Expand Down
50 changes: 50 additions & 0 deletions Samples/Samples/View/SaveToGalleryPage.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<views:BasePage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:Samples.View"
xmlns:viewmodels="clr-namespace:Samples.ViewModel"
x:Class="Samples.View.SaveToGalleryPage"
Title="Save To Galery">

<views:BasePage.BindingContext>
<viewmodels:SaveToGalleryViewModel />
</views:BasePage.BindingContext>

<ScrollView>
<Grid RowSpacing="20" Padding="16">
<Grid.RowDefinitions>
<RowDefinition Height="400"/>
<RowDefinition Height="40"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>

<Grid ColumnSpacing="8" RowSpacing="8">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="40"/>
<RowDefinition Height="*"/>
<RowDefinition Height="40"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>

<Image Grid.Row="0" Grid.Column="0" Source="{Binding PngUrl}"/>
<Button Grid.Row="1" Grid.Column="0" Text="Png" Command="{Binding SavevPngCommand}"/>

<Image Grid.Row="0" Grid.Column="1" Source="{Binding JpgUrl}"/>
<Button Grid.Row="1" Grid.Column="1" Text="Jpg" Command="{Binding SaveJpgCommand}"/>


<Image Grid.Row="2" Grid.Column="0" Source="{Binding GifUrl}" IsAnimationPlaying="True"/>
<Button Grid.Row="3" Grid.Column="0" Text="Gif" Command="{Binding SaveGifCommand}"/>

<MediaElement Grid.Row="2" Grid.Column="1" Source="{Binding VideoUrl}"/>
<Button Grid.Row="3" Grid.Column="1" Text="Video" Command="{Binding SaveVideoCommand}"/>
</Grid>

<Button Grid.Row="1" Text="From Cache Directory" Command="{Binding SaveFromCacheCommand}"/>

</Grid>
</ScrollView>
</views:BasePage>
10 changes: 10 additions & 0 deletions Samples/Samples/View/SaveToGalleryPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Samples.View
{
public partial class SaveToGalleryPage : BasePage
{
public SaveToGalleryPage()
{
InitializeComponent();
}
}
}
6 changes: 6 additions & 0 deletions Samples/Samples/ViewModel/HomeViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
"🖼",
"Save To Gallery",
typeof(SaveToGalleryPage),
"Save To Gallery",
new[] { "save", "gallery", "image", "jpg", "png" })
};
filteredItems = samples;
filterText = string.Empty;
Expand Down
123 changes: 123 additions & 0 deletions Samples/Samples/ViewModel/SaveToGalleryViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
using System;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.Essentials;
using Xamarin.Forms;

namespace Samples.ViewModel
{
public class SaveToGalleryViewModel : BaseViewModel
{
const string jpgFileName = "Lomonosov.jpg";
const string successMsg = "Save Completed Successfully";
readonly string albumName;

public SaveToGalleryViewModel()
{
albumName = AppInfo.Name;

SavevPngCommand = new Command(() => SaveAsStream(PngUrl, "essential.png"));
SaveJpgCommand = new Command(() => Save(JpgUrl, jpgFileName));
SaveGifCommand = new Command(() => Save(GifUrl, "test.gif"));
SaveVideoCommand = new Command(() => Save(VideoUrl, "essential.mp4"));

SaveFromCacheCommand = new Command(SaveFromCacheDirectory);
}

public ICommand SavevPngCommand { get; }

public ICommand SaveJpgCommand { get; }

public ICommand SaveGifCommand { get; }

public ICommand SaveVideoCommand { get; }

public ICommand SaveFromCacheCommand { get; }

public string PngUrl
=> "https://raw.githubusercontent.com/xamarin/Essentials/main/Assets/xamarin.essentials_128x128.png";

public string JpgUrl
=> "https://raw.githubusercontent.com/dimonovdd/Essentials/featureSaveToGalery/Assets/SaveToGaleryTestPhoto.jpg";

public string GifUrl
=> "https://i.gifer.com/769R.gif";

public string VideoUrl
=> "https://file-examples-com.github.io/uploads/2017/04/file_example_MP4_480_1_5MG.mp4";

async void SaveAsStream(string url, string name)
{
try
{
using var client = new WebClient();
var stream = client.OpenRead(url);

var data = await DownloadFile(url);
await SaveToGallery.SaveAsync(
url == VideoUrl ? MediaFileType.Video : MediaFileType.Image,
stream,
name,
albumName);

await DisplayAlertAsync(successMsg);
}
catch (Exception ex)
{
await DisplayAlertAsync(ex.Message);
}
}

async void Save(string url, string name)
{
try
{
var data = await DownloadFile(url);
await SaveToGallery.SaveAsync(
url == VideoUrl ? MediaFileType.Video : MediaFileType.Image,
data,
name,
albumName);

await DisplayAlertAsync(successMsg);
}
catch (Exception ex)
{
await DisplayAlertAsync(ex.Message);
}
}

async void SaveFromCacheDirectory()
{
try
{
var filePath = SaveFileToCache(await DownloadFile(JpgUrl), jpgFileName);
await SaveToGallery.SaveAsync(MediaFileType.Image, filePath, albumName);

await DisplayAlertAsync(successMsg);
}
catch (Exception ex)
{
await DisplayAlertAsync(ex.Message);
}
}

string SaveFileToCache(byte[] data, string fileName)
{
var filePath = Path.Combine(FileSystem.CacheDirectory, fileName);

if (!File.Exists(filePath))
File.WriteAllBytes(filePath, data);

return filePath;
}

async Task<byte[]> DownloadFile(string url)
{
using var client = new WebClient();
return await client.DownloadDataTaskAsync(url);
}
}
}
112 changes: 112 additions & 0 deletions Xamarin.Essentials/SaveToGallery/SaveToGallery.android.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
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 SaveToGallery
{
static async Task PlatformSaveAsync(MediaFileType type, byte[] data, string fileName, string albumName)
{
using var ms = new MemoryStream(data);
await PlatformSaveAsync(type, ms, fileName, albumName);
}

static async Task PlatformSaveAsync(MediaFileType type, string filePath, string albumName)
{
using var fileStream = System.IO.File.OpenRead(filePath);
await PlatformSaveAsync(type, fileStream, Path.GetFileName(filePath), albumName);
}

static async Task PlatformSaveAsync(MediaFileType type, Stream fileStream, string fileName, string albumName)
{
await Permissions.EnsureGrantedAsync<Permissions.StorageWrite>();

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.DateTaken, TimeMillis(dateTimeNow));
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;
}
}
Loading