From 227f2c70543b570910f4f6fc284eaf15c59a767b Mon Sep 17 00:00:00 2001 From: Sergey Levshunov Date: Fri, 17 Oct 2025 11:32:02 +0200 Subject: [PATCH 1/2] update android imagepicker --- .../ImagePicker/DroidImagePickerService.cs | 57 +++++++++++++++---- .../IImagePickerActivityResultHandler.cs | 4 +- .../ImagePicker/ImagePickerActivity.cs | 52 ++++++++++++----- .../ImagePickerActivityResultHandler.cs | 10 ++-- ...Toolkit.WhiteLabel.Essentials.Droid.csproj | 1 + 5 files changed, 91 insertions(+), 33 deletions(-) diff --git a/Softeq.XToolkit.WhiteLabel.Essentials.Droid/ImagePicker/DroidImagePickerService.cs b/Softeq.XToolkit.WhiteLabel.Essentials.Droid/ImagePicker/DroidImagePickerService.cs index 6935a4386..4dbaf83bf 100644 --- a/Softeq.XToolkit.WhiteLabel.Essentials.Droid/ImagePicker/DroidImagePickerService.cs +++ b/Softeq.XToolkit.WhiteLabel.Essentials.Droid/ImagePicker/DroidImagePickerService.cs @@ -4,37 +4,61 @@ using System.Threading.Tasks; using Android.Content; using Android.Graphics; +using Android.Net; +using AndroidX.Activity.Result; +using AndroidX.Activity.Result.Contract; +using AndroidX.AppCompat.App; +using Java.Lang; using Softeq.XToolkit.Permissions; using Softeq.XToolkit.WhiteLabel.Droid.Providers; using Softeq.XToolkit.WhiteLabel.Essentials.ImagePicker; using CameraPermission = Microsoft.Maui.ApplicationModel.Permissions.Camera; using PermissionStatus = Softeq.XToolkit.Permissions.PermissionStatus; -using PhotosPermission = Microsoft.Maui.ApplicationModel.Permissions.Photos; namespace Softeq.XToolkit.WhiteLabel.Essentials.Droid.ImagePicker { - public class DroidImagePickerService : IImagePickerService + public class DroidImagePickerService : Java.Lang.Object, IImagePickerService, IActivityResultCallback { private readonly IPermissionsManager _permissionsManager; private readonly IContextProvider _contextProvider; + private readonly ActivityResultLauncher? _activityResultLauncher; + private readonly IImagePickerActivityResultHandler _handler; - private TaskCompletionSource? _taskCompletionSource; + private TaskCompletionSource? _pickPhotofileUriCompletionSource; + private TaskCompletionSource? _bitmapTaskCompletionSource; public DroidImagePickerService( + IImagePickerActivityResultHandler handler, IPermissionsManager permissionsManager, IContextProvider contextProvider) { + _handler = handler; _permissionsManager = permissionsManager; _contextProvider = contextProvider; + if (ActivityResultContracts.PickVisualMedia.InvokeIsPhotoPickerAvailable(_contextProvider.CurrentActivity) + && _contextProvider.CurrentActivity is AppCompatActivity appCompatActivity) + { + _activityResultLauncher = appCompatActivity.RegisterForActivityResult( + new ActivityResultContracts.PickVisualMedia(), this); + } } public async Task PickPhotoAsync(float quality) { - var permissionStatus = await _permissionsManager.CheckWithRequestAsync().ConfigureAwait(false); - - if (permissionStatus != PermissionStatus.Granted) + if (_activityResultLauncher != null) { - return null; + _pickPhotofileUriCompletionSource = new TaskCompletionSource(); + + var request = new PickVisualMediaRequest.Builder() + .SetMediaType(ActivityResultContracts.PickVisualMedia.ImageOnly.Instance) + .Build(); + _activityResultLauncher.Launch(request); + + var fileUri = await _pickPhotofileUriCompletionSource.Task + .ConfigureAwait(false); + + return await GetImageAsync(ImagePickerMode.ImageCropOnly, quality, fileUri) + .ConfigureAwait(false); } return await GetImageAsync(ImagePickerMode.Gallery, quality).ConfigureAwait(false); @@ -52,20 +76,25 @@ public DroidImagePickerService( return await GetImageAsync(ImagePickerMode.Camera, quality).ConfigureAwait(false); } - private async Task GetImageAsync(int mode, float quality) + private async Task GetImageAsync(int mode, float quality, Uri? fileUri = null) { var activity = _contextProvider.CurrentActivity; var intent = new Intent(activity, typeof(ImagePickerActivity)); intent.PutExtra(ImagePickerActivity.ModeKey, mode); - _taskCompletionSource = new TaskCompletionSource(); + if (mode == ImagePickerMode.ImageCropOnly) + { + intent.SetData(fileUri); + } + + _bitmapTaskCompletionSource = new TaskCompletionSource(); ImagePickerActivity.ImagePicked += OnImagePicked; activity.StartActivity(intent); - var bitmap = await _taskCompletionSource.Task.ConfigureAwait(false); + var bitmap = await _bitmapTaskCompletionSource.Task.ConfigureAwait(false); return new DroidImagePickerResult { @@ -78,7 +107,13 @@ private async Task GetImageAsync(int mode, float quality) private void OnImagePicked(object? sender, Bitmap? e) { ImagePickerActivity.ImagePicked -= OnImagePicked; - _taskCompletionSource!.SetResult(e); + _bitmapTaskCompletionSource!.SetResult(e); + } + + void IActivityResultCallback.OnActivityResult(Object? result) + { + var uri = result as Uri; + _pickPhotofileUriCompletionSource!.TrySetResult(uri); } } } diff --git a/Softeq.XToolkit.WhiteLabel.Essentials.Droid/ImagePicker/IImagePickerActivityResultHandler.cs b/Softeq.XToolkit.WhiteLabel.Essentials.Droid/ImagePicker/IImagePickerActivityResultHandler.cs index 743f8a66b..64027e88f 100644 --- a/Softeq.XToolkit.WhiteLabel.Essentials.Droid/ImagePicker/IImagePickerActivityResultHandler.cs +++ b/Softeq.XToolkit.WhiteLabel.Essentials.Droid/ImagePicker/IImagePickerActivityResultHandler.cs @@ -11,9 +11,9 @@ namespace Softeq.XToolkit.WhiteLabel.Essentials.Droid.ImagePicker { public interface IImagePickerActivityResultHandler { - Task HandleImagePickerCameraResultAsync(Activity activity, Result resultCode, Uri? fileUri); + Task HandleImagePickerCameraResultAsync(Activity activity, Uri? fileUri); - Task HandleImagePickerGalleryResultAsync(Activity activity, Result resultCode, Intent? data); + Task HandleImagePickerGalleryResultAsync(Activity activity, Uri? fileUri); Task HandleCustomResultAsync(int requestCode, Result resultCode, Intent? data); } diff --git a/Softeq.XToolkit.WhiteLabel.Essentials.Droid/ImagePicker/ImagePickerActivity.cs b/Softeq.XToolkit.WhiteLabel.Essentials.Droid/ImagePicker/ImagePickerActivity.cs index d5f68dfde..2c6288c3d 100644 --- a/Softeq.XToolkit.WhiteLabel.Essentials.Droid/ImagePicker/ImagePickerActivity.cs +++ b/Softeq.XToolkit.WhiteLabel.Essentials.Droid/ImagePicker/ImagePickerActivity.cs @@ -15,7 +15,6 @@ using Softeq.XToolkit.Common.Extensions; using Softeq.XToolkit.WhiteLabel.Droid.Providers; using AUri = Android.Net.Uri; -using ImageOrientation = Android.Media.Orientation; using IOException = System.IO.IOException; namespace Softeq.XToolkit.WhiteLabel.Essentials.Droid.ImagePicker @@ -26,6 +25,7 @@ public static class ImagePickerMode { public const int Camera = 1; public const int Gallery = 2; + public const int ImageCropOnly = 3; } [Activity] @@ -36,6 +36,8 @@ internal class ImagePickerActivity : Activity private const string ImagesFolder = "Pictures"; private const string CameraFileUriKey = "CameraFileUri"; + private readonly IImagePickerActivityResultHandler _handler = Dependencies.Container.Resolve(); + private AUri? _fileUri; private Intent _pickIntent = default!; @@ -75,6 +77,9 @@ protected override void OnCreate(Bundle? savedInstanceState) case ImagePickerMode.Gallery: PickImage(); break; + case ImagePickerMode.ImageCropOnly: + HandleImagePickerGalleryResultAsync(Intent.Data).FireAndForget(); + break; } } catch @@ -83,10 +88,18 @@ protected override void OnCreate(Bundle? savedInstanceState) } finally { - _pickIntent.Dispose(); + _pickIntent?.Dispose(); } } + private async Task HandleImagePickerGalleryResultAsync(AUri? fileUri) + { + var bitmap = await _handler + .HandleImagePickerGalleryResultAsync(this, fileUri) + .ConfigureAwait(false); + OnImagePicked(bitmap); + } + protected override void OnSaveInstanceState(Bundle outState) { if (_fileUri != null) @@ -104,25 +117,34 @@ protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result private async Task HandleOnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent? data) { - var handler = Dependencies.Container.Resolve(); - if (requestCode == ImagePickerMode.Camera) { - var bitmap = await handler - .HandleImagePickerCameraResultAsync(this, resultCode, _fileUri) - .ConfigureAwait(false); - OnImagePicked(bitmap); + if (resultCode == Result.Ok) + { + var bitmap = await _handler + .HandleImagePickerCameraResultAsync(this, _fileUri) + .ConfigureAwait(false); + OnImagePicked(bitmap); + } + else + { + OnImagePicked(null); + } } else if (requestCode == ImagePickerMode.Gallery) { - var bitmap = await handler - .HandleImagePickerGalleryResultAsync(this, resultCode, data) - .ConfigureAwait(false); - OnImagePicked(bitmap); + if (resultCode == Result.Ok) + { + HandleImagePickerGalleryResultAsync(data?.Data).FireAndForget(); + } + else + { + OnImagePicked(null); + } } else { - handler.HandleCustomResultAsync(requestCode, resultCode, data).FireAndForget(); + _handler.HandleCustomResultAsync(requestCode, resultCode, data).FireAndForget(); } } @@ -143,7 +165,9 @@ private void CaptureCamera() private void PickImage() { - _pickIntent = new Intent(Intent.ActionPick); + _pickIntent = new Intent(Intent.ActionOpenDocument); + _pickIntent.AddCategory(Intent.CategoryOpenable); + _pickIntent.AddFlags(ActivityFlags.GrantReadUriPermission); _pickIntent.SetType("image/*"); StartActivityForResult(_pickIntent, ImagePickerMode.Gallery); } diff --git a/Softeq.XToolkit.WhiteLabel.Essentials.Droid/ImagePicker/ImagePickerActivityResultHandler.cs b/Softeq.XToolkit.WhiteLabel.Essentials.Droid/ImagePicker/ImagePickerActivityResultHandler.cs index 8cee6ceee..364e2103b 100644 --- a/Softeq.XToolkit.WhiteLabel.Essentials.Droid/ImagePicker/ImagePickerActivityResultHandler.cs +++ b/Softeq.XToolkit.WhiteLabel.Essentials.Droid/ImagePicker/ImagePickerActivityResultHandler.cs @@ -13,9 +13,9 @@ namespace Softeq.XToolkit.WhiteLabel.Essentials.Droid.ImagePicker { public sealed class ImagePickerActivityResultHandler : IImagePickerActivityResultHandler { - public Task HandleImagePickerCameraResultAsync(Activity activity, Result resultCode, Uri? fileUri) + public Task HandleImagePickerCameraResultAsync(Activity activity, Uri? fileUri) { - if (resultCode == Result.Ok && fileUri != null) + if (fileUri != null) { return GetBitmapFromUriAsync(activity, fileUri); } @@ -23,11 +23,9 @@ public sealed class ImagePickerActivityResultHandler : IImagePickerActivityResul return Task.FromResult(default(Bitmap?)); } - public Task HandleImagePickerGalleryResultAsync(Activity activity, Result resultCode, Intent? data) + public Task HandleImagePickerGalleryResultAsync(Activity activity, Uri? fileUri) { - var fileUri = data?.Data; - - if (resultCode == Result.Ok && fileUri != null) + if (fileUri != null) { return GetBitmapFromUriAsync(activity, fileUri); } diff --git a/Softeq.XToolkit.WhiteLabel.Essentials.Droid/Softeq.XToolkit.WhiteLabel.Essentials.Droid.csproj b/Softeq.XToolkit.WhiteLabel.Essentials.Droid/Softeq.XToolkit.WhiteLabel.Essentials.Droid.csproj index 67a72c90d..20543c905 100644 --- a/Softeq.XToolkit.WhiteLabel.Essentials.Droid/Softeq.XToolkit.WhiteLabel.Essentials.Droid.csproj +++ b/Softeq.XToolkit.WhiteLabel.Essentials.Droid/Softeq.XToolkit.WhiteLabel.Essentials.Droid.csproj @@ -24,6 +24,7 @@ + From dc76964b61cdbfe0ba72a951bb2b8a0d90153851 Mon Sep 17 00:00:00 2001 From: Pavel Leonenko Date: Wed, 22 Oct 2025 08:25:34 +0200 Subject: [PATCH 2/2] Clean up --- .../ImagePicker/DroidImagePickerService.cs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/Softeq.XToolkit.WhiteLabel.Essentials.Droid/ImagePicker/DroidImagePickerService.cs b/Softeq.XToolkit.WhiteLabel.Essentials.Droid/ImagePicker/DroidImagePickerService.cs index 4dbaf83bf..4e4aa2571 100644 --- a/Softeq.XToolkit.WhiteLabel.Essentials.Droid/ImagePicker/DroidImagePickerService.cs +++ b/Softeq.XToolkit.WhiteLabel.Essentials.Droid/ImagePicker/DroidImagePickerService.cs @@ -8,7 +8,6 @@ using AndroidX.Activity.Result; using AndroidX.Activity.Result.Contract; using AndroidX.AppCompat.App; -using Java.Lang; using Softeq.XToolkit.Permissions; using Softeq.XToolkit.WhiteLabel.Droid.Providers; using Softeq.XToolkit.WhiteLabel.Essentials.ImagePicker; @@ -22,17 +21,14 @@ public class DroidImagePickerService : Java.Lang.Object, IImagePickerService, IA private readonly IPermissionsManager _permissionsManager; private readonly IContextProvider _contextProvider; private readonly ActivityResultLauncher? _activityResultLauncher; - private readonly IImagePickerActivityResultHandler _handler; - private TaskCompletionSource? _pickPhotofileUriCompletionSource; + private TaskCompletionSource? _pickPhotoFileUriCompletionSource; private TaskCompletionSource? _bitmapTaskCompletionSource; public DroidImagePickerService( - IImagePickerActivityResultHandler handler, IPermissionsManager permissionsManager, IContextProvider contextProvider) { - _handler = handler; _permissionsManager = permissionsManager; _contextProvider = contextProvider; if (ActivityResultContracts.PickVisualMedia.InvokeIsPhotoPickerAvailable(_contextProvider.CurrentActivity) @@ -47,14 +43,14 @@ public DroidImagePickerService( { if (_activityResultLauncher != null) { - _pickPhotofileUriCompletionSource = new TaskCompletionSource(); + _pickPhotoFileUriCompletionSource = new TaskCompletionSource(); var request = new PickVisualMediaRequest.Builder() .SetMediaType(ActivityResultContracts.PickVisualMedia.ImageOnly.Instance) .Build(); _activityResultLauncher.Launch(request); - var fileUri = await _pickPhotofileUriCompletionSource.Task + var fileUri = await _pickPhotoFileUriCompletionSource.Task .ConfigureAwait(false); return await GetImageAsync(ImagePickerMode.ImageCropOnly, quality, fileUri) @@ -110,10 +106,10 @@ private void OnImagePicked(object? sender, Bitmap? e) _bitmapTaskCompletionSource!.SetResult(e); } - void IActivityResultCallback.OnActivityResult(Object? result) + void IActivityResultCallback.OnActivityResult(Java.Lang.Object? result) { var uri = result as Uri; - _pickPhotofileUriCompletionSource!.TrySetResult(uri); + _pickPhotoFileUriCompletionSource!.TrySetResult(uri); } } }