Skip to content

Instantly share code, notes, and snippets.

@ericbrunner
Last active April 8, 2022 09:04
Show Gist options
  • Save ericbrunner/5a4c76cb5cbabc3a992c5a97a216c81c to your computer and use it in GitHub Desktop.
Save ericbrunner/5a4c76cb5cbabc3a992c5a97a216c81c to your computer and use it in GitHub Desktop.
Media Plugin Usage
using Acr.UserDialogs;
using Plugin.Connectivity;
using Plugin.Media;
using Plugin.Media.Abstractions;
using Plugin.Permissions;
using Plugin.Permissions.Abstractions;
using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using trucker_rolsped.AzureStorage;
using trucker_rolsped.Helper.HockeyApp;
using trucker_rolsped.Interfaces;
using trucker_rolsped.Interfaces.Utils;
using trucker_rolsped.Models;
using trucker_rolsped.StoreManager;
using trucker_rolsped.ViewModels.Media;
using Xamarin.Forms;
namespace trucker_rolsped.Pages.Media
{
[Flags]
public enum MediaType : byte
{
None = 0x00, // 0d, 0000 0000b
Photo = 0x01, // 2^0 = 1d, 0000 0001b
Video = 0x02, // 2^1 = 2d, 0000 0010b
TakenPhoto = 0x04, // 2^2 = 4d, 0000 0100b
PickedPhoto = 0x08 // 2^3 = 8d, 0000 1000b
}
public enum MediaState
{
Created = 0,
Uploaded = 1,
Queued = 2,
Error = 3,
NotFound = 4
}
public sealed class TruckerAppMediaFile : IDisposable
{
public MediaType Type { get; set; }
public MediaFile File { get; set; }
public string DokArt { get; set; }
public MediaState MediaState { get; set; }
public int TruckAppId { get; set; }
public string FilePath { get; set; }
public TruckerAppMediaFile()
{
MediaState = MediaState.Created;
}
public async void Dispose()
{
File?.Dispose();
}
}
public partial class Task70PhotoUploadPage : ContentPage, IRefreshPage
{
private readonly string _dokArt;
private readonly Task70PhotoUploadViewModel _task70PhotoUploadViewModel;
private readonly TaskCompletionSource<bool> _tcs;
private readonly int _truckAppId;
private readonly WorkflowItem _workflowItem;
public Task70PhotoUploadPage(TaskCompletionSource<bool> tcs, WorkflowItem workflowItem, string dokArt)
{
InitializeComponent();
_tcs = tcs;
_workflowItem = workflowItem;
_dokArt = dokArt;
_truckAppId = _workflowItem.TruckAppId;
_task70PhotoUploadViewModel = new Task70PhotoUploadViewModel(_workflowItem, dokArt);
BindingContext = _task70PhotoUploadViewModel;
NavigationPage.SetHasNavigationBar(this, false);
Device.OnPlatform(iOS: () => { Padding = new Thickness(0, 20, 0, 0); });
}
protected override bool OnBackButtonPressed()
{
return true;
}
private void ChangeMediaButtonEnableState(bool enable)
{
if (TakePhoto.IsVisible)
TakePhoto.IsEnabled = enable;
if (PickPhoto.IsVisible)
PickPhoto.IsEnabled = enable;
if (TakeNextPhoto.IsVisible)
TakeNextPhoto.IsEnabled = enable;
if (PickNextPhoto.IsVisible)
PickNextPhoto.IsEnabled = enable;
}
private async Task CheckToUploadPhoto()
{
try
{
if (SelectedMedia?.File != null)
{
bool isOk = await DisplayAlert("Foto geladen", "Wollen Sie das Foto zur Roland Spedition senden?", "Ja", "Nein");
if (isOk)
{
await UploadPhoto();
}
}
}
finally
{
SelectedMedia?.Dispose();
}
}
private async void CloseFotoDialogClicked(object sender, EventArgs e)
{
await CheckToUploadPhoto();
await Navigation.PopAsync(animated: false);
_tcs.SetResult(true);
}
// prevent that a user invoked Take and Pick Photo event at same time
private readonly SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1, 1);
private async void PickPhotoClicked(object sender, EventArgs e)
{
await _semaphoreSlim.WaitAsync();
try
{
ChangeMediaButtonEnableState(enable: false);
SelectedMedia?.Dispose();
SelectedMedia = null;
await CrossMedia.Current.Initialize();
var cameraStatus = await CrossPermissions.Current.CheckPermissionStatusAsync(Permission.Camera);
var storageStatus = await CrossPermissions.Current.CheckPermissionStatusAsync(Permission.Storage);
if (cameraStatus != PermissionStatus.Granted || storageStatus != PermissionStatus.Granted)
{
var results = await CrossPermissions.Current.RequestPermissionsAsync(new[] {Permission.Camera, Permission.Storage});
cameraStatus = results[Permission.Camera];
storageStatus = results[Permission.Storage];
}
if (cameraStatus == PermissionStatus.Granted && storageStatus == PermissionStatus.Granted)
{
if (!CrossMedia.Current.IsPickPhotoSupported)
{
await MetricsManagerHelper.Instance.SendGenericMessageToApplicationInsightsAsync($"{nameof(Task70PhotoUploadPage)}", "Not allowed to choose a photo.");
await DisplayAlert("Media Info", "Keine Berechtigung ein Foto auszuwählen.", "OK");
return;
}
SelectedMedia = new TruckerAppMediaFile
{
Type = MediaType.Photo | MediaType.PickedPhoto,
File = await CrossMedia.Current.PickPhotoAsync(new PickMediaOptions
{
CompressionQuality = 92,
PhotoSize = PhotoSize.Medium
})
};
if (SelectedMedia?.File == null)
{
SelectedMedia = null;
return;
}
var imageStream = await DependencyService.Get<Interfaces.Media.IMedia>().CopyMediaAsync(SelectedMedia.File.GetStream());
Image.Source = ImageSource.FromStream(() => imageStream);
SendMedia.IsEnabled = true;
}
else
{
await MetricsManagerHelper.Instance.SendGenericMessageToApplicationInsightsAsync($"{nameof(Task70PhotoUploadPage)}", "Not allowed to choose a photo.");
await DisplayAlert("Media Info", "Keine Berechtigung ein Foto auszuwählen.", "OK");
if (Device.OS == TargetPlatform.iOS)
//On iOS you may want to send your user to the settings screen.
CrossPermissions.Current.OpenAppSettings();
}
}
catch (Exception ex)
{
await MetricsManagerHelper.Instance.SendExceptionToApplicationInsightsAsync(ex).ConfigureAwait(true);
}
finally
{
ChangeMediaButtonEnableState(enable: true);
_semaphoreSlim.Release();
}
}
private async void TakePhotoClicked(object sender, EventArgs e)
{
await _semaphoreSlim.WaitAsync();
try
{
ChangeMediaButtonEnableState(enable: false);
SelectedMedia?.Dispose();
SelectedMedia = null;
await CrossMedia.Current.Initialize();
var cameraStatus = await CrossPermissions.Current.CheckPermissionStatusAsync(Permission.Camera);
var storageStatus = await CrossPermissions.Current.CheckPermissionStatusAsync(Permission.Storage);
if (cameraStatus != PermissionStatus.Granted || storageStatus != PermissionStatus.Granted)
{
var results = await CrossPermissions.Current.RequestPermissionsAsync(new[] {Permission.Camera, Permission.Storage});
cameraStatus = results[Permission.Camera];
storageStatus = results[Permission.Storage];
}
if (cameraStatus == PermissionStatus.Granted && storageStatus == PermissionStatus.Granted)
{
if (!CrossMedia.Current.IsCameraAvailable || !CrossMedia.Current.IsTakePhotoSupported)
{
await MetricsManagerHelper.Instance.SendGenericMessageToApplicationInsightsAsync($"{nameof(Task70PhotoUploadPage)}", "No camera available.");
await DisplayAlert("Media Info", "Keine Kamera verfügbar.", "OK");
return;
}
var localFileName = $"{DateTime.Now.ToString("yyyyMMddHHmmss", CultureInfo.InvariantCulture)}_{_truckAppId}_{_dokArt}.jpg";
SelectedMedia = new TruckerAppMediaFile
{
Type = MediaType.Photo | MediaType.TakenPhoto,
File = await CrossMedia.Current.TakePhotoAsync(new Plugin.Media.Abstractions.StoreCameraMediaOptions
{
PhotoSize = PhotoSize.Medium,
CompressionQuality = 92,
Name = localFileName,
Directory = "Truckerapp"
})
};
if (SelectedMedia?.File == null)
{
SelectedMedia = null;
return;
}
var imageStream = await DependencyService.Get<Interfaces.Media.IMedia>().CopyMediaAsync(SelectedMedia.File.GetStream());
Image.Source = ImageSource.FromStream(() => imageStream);
SendMedia.IsEnabled = true;
}
else
{
await MetricsManagerHelper.Instance.SendGenericMessageToApplicationInsightsAsync($"{nameof(Task70PhotoUploadPage)}", "Not allowed to use camera.");
await DisplayAlert("Media Info", "Keine Berechtigung die Kamera zu verwenden.", "OK");
if (Device.OS == TargetPlatform.iOS)
//On iOS you may want to send your user to the settings screen.
CrossPermissions.Current.OpenAppSettings();
}
}
catch (Exception ex)
{
await MetricsManagerHelper.Instance.SendExceptionToApplicationInsightsAsync(ex).ConfigureAwait(true);
}
finally
{
ChangeMediaButtonEnableState(enable: true);
_semaphoreSlim.Release();
}
}
private async void SendMediaOnClicked(object sender, EventArgs e)
{
SendMedia.IsEnabled = false;
try
{
await UploadPhoto();
}
catch (Exception exception)
{
await MetricsManagerHelper.Instance.SendExceptionToApplicationInsightsAsync(exception);
Image.Source = Device.OnPlatform(
iOS: ImageSource.FromFile("image_photo_plain.png"),
Android: ImageSource.FromFile("image_photo_plain.png"),
WinPhone: null
);
SelectedMedia?.Dispose();
SelectedMedia = null;
}
finally
{
EnabledNextButtons();
}
}
private async Task UploadPhoto()
{
try
{
SelectedMedia.DokArt = _dokArt;
SelectedMedia.TruckAppId = _truckAppId;
UserDialogs.Instance.ShowLoading("Foto wird hochgeladen...");
await AzureBlobStorageManager.Instance.UploadMediaAsync(SelectedMedia);
UserDialogs.Instance.HideLoading();
switch (SelectedMedia.MediaState)
{
case MediaState.Uploaded:
//var result = await DisplayAlert("Foto Upload", "Foto wurde erfolgreich hochgeladen. Möchten Sie das Foto jetzt vom Handy löschen?", "Ja", "Nein");
break;
case MediaState.Queued:
UserDialogs.Instance.ShowLoading("Upload erfolgt automatisch wenn Sie wieder mit Internet oder dem mobilem Netz verbunden sind!");
await Task.Delay(1500);
UserDialogs.Instance.HideLoading();
break;
case MediaState.Created:
await DisplayAlert("Foto Upload", "Sie müssen das Foto erneut auswählen bzw. machen, da es nicht gesendet wurde!", "Ok");
break;
case MediaState.Error:
await DisplayAlert("Foto Upload", "Sie müssen das Foto erneut auswählen bzw. machen, da es nicht gesendet wurde!", "Ok");
break;
default:
await DisplayAlert("Foto Upload", "Sie müssen das Foto erneut auswählen bzw. machen, da es nicht gesendet wurde!", "Ok");
break;
}
}
catch (Exception ex)
{
SelectedMedia.MediaState = MediaState.Error;
await DisplayAlert("Sende Media Fehler", ex.Message, "Ok");
}
finally
{
Image.Source = Device.OnPlatform(
iOS: ImageSource.FromFile("image_photo_plain.png"),
Android: ImageSource.FromFile("image_photo_plain.png"),
WinPhone: null
);
if (SelectedMedia.MediaState == MediaState.Uploaded || SelectedMedia.MediaState == MediaState.Queued)
{
_workflowItem.TruckAuftragWorkFlow.DokAnzahl++;
}
SelectedMedia?.Dispose();
if (SelectedMedia?.MediaState == MediaState.Uploaded)
{
await DeleteFileAfterUploadAsync();
}
SelectedMedia = null;
}
}
private async Task DeleteFileAfterUploadAsync()
{
//Platform specific file delete
var truckerappMedia = SelectedMedia.FilePath;
var platformFileHandler = DependencyService.Get<IFileHandling>();
if (platformFileHandler != null)
if (await platformFileHandler.FileExistsAsync(truckerappMedia))
{
var deleted = await platformFileHandler.DeleteFileAsync(truckerappMedia);
if (!deleted)
{
await MetricsManagerHelper.Instance.SendErrorToApplicationInsightsAsync($"Photo couldn't be deleted: {truckerappMedia}");
}
}
}
private void EnabledNextButtons()
{
TakePhoto.IsVisible = false;
PickPhoto.IsVisible = false;
TakeNextPhoto.IsVisible = true;
PickNextPhoto.IsVisible = true;
}
private bool IsTakenPhoto => (SelectedMedia.Type & MediaType.TakenPhoto) != 0x00;
private TruckerAppMediaFile SelectedMedia { get; set; }
protected override async void OnAppearing()
{
base.OnAppearing();
try
{
await RefreshItemsAsync(showActivityIndicator: false, syncItems: false);
}
catch (Exception e)
{
await MetricsManagerHelper.Instance.SendExceptionToApplicationInsightsAsync(e);
}
}
public async Task RefreshItemsAsync(bool showActivityIndicator, bool syncItems)
{
syncItems = syncItems && CrossConnectivity.Current.IsConnected;
//UserDialogs.Instance.ShowLoading("Daten werden geladen...", MaskType.Black);
_task70PhotoUploadViewModel.LanguageItems = await OfflineSyncStoreManager.Instance.TagSpracheStore.GetTagSpracheItemsAsync(syncItems);
//UserDialogs.Instance.HideLoading();
}
}
}
@Ines-Diaz
Copy link

Hi, I know it's been too long since this post and I guess I won't get any response. But I would like to know if there would be any way to fix the following problem. When using SemaphoreSlim the error that only one operation can be activated at the moment disappears, but it remains for one/two seconds on the button page to which I added the function of taking a photo and my goal is that after taking the photo directly display the page I navigate to after. How could I avoid that delay of one/two seconds that occurs to me?

@Ines-Diaz
Copy link

Also, I would like to tell you about another problem. After taking the photo and navigating to the next page, the camera automatically reopens and I can't figure out the error. Thank you very much, best regards.

@ericbrunner
Copy link
Author

@Ines-Diaz Well to be honest, that is very long ago when I posted that gist ;-) I can only suggest that you use the official Xamarin.Essentials MediaPicker and if you have any questions post it in the GitHub Repo:

Documentation: https://docs.microsoft.com/en-us/xamarin/essentials/media-picker?context=xamarin%2Fandroid&tabs=android
GitHub Repo: https://github.com/xamarin/Essentials
MediaPicker Source: https://github.com/xamarin/Essentials/tree/main/Xamarin.Essentials/MediaPicker

I added that to suppress that concurrency error as stated // prevent that a user invoked Take and Pick Photo event at same time

You currently have a delay of a few seconds as far as I understand between taking the image on the button page and displaying the image on another page.

Do you use that UploadPhoto method?: There is a delay: https://gist.github.com/ericbrunner/5a4c76cb5cbabc3a992c5a97a216c81c#file-task70photouploadpage-xaml-cs-L333

Well that is my custom code so feel free to grap and modify it your needs, but I could only give you the advice to go with the official Xamarin.Essentials , clone the git repo and start the samples and take a look in the usage of the MediaPicker here: https://github.com/xamarin/Essentials/blob/main/Samples/Samples/ViewModel/MediaPickerViewModel.cs

@Ines-Diaz
Copy link

@ericbrunner Thank you very much for your response. I'll put into practice. All the best :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment