From 236ff77b08048762d47577a4e8707bddfea487ed Mon Sep 17 00:00:00 2001 From: Hashir Rajah Date: Mon, 24 Nov 2025 18:15:00 +0400 Subject: [PATCH 01/16] V1 --- src/android/ImagePicker.java | 109 ++++++++++++++++++++++++++++++++--- 1 file changed, 102 insertions(+), 7 deletions(-) diff --git a/src/android/ImagePicker.java b/src/android/ImagePicker.java index cf993e0d..2db56a67 100644 --- a/src/android/ImagePicker.java +++ b/src/android/ImagePicker.java @@ -8,7 +8,9 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.database.Cursor; +import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.Canvas; import android.graphics.Color; import android.net.Uri; import android.os.Build; @@ -17,6 +19,7 @@ import android.provider.MediaStore; import android.provider.OpenableColumns; import android.util.Log; +import android.util.Size; import android.view.Gravity; import android.view.ViewGroup; import android.view.WindowManager; @@ -26,6 +29,7 @@ import androidx.activity.result.PickVisualMediaRequest; import androidx.activity.result.contract.ActivityResultContracts; +import androidx.camera.core.ResolutionInfo; import androidx.core.content.ContextCompat; import org.apache.cordova.CallbackContext; @@ -39,6 +43,8 @@ import java.io.FileOutputStream; import java.io.InputStream; import java.util.ArrayList; +import java.util.List; +import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -54,6 +60,7 @@ public class ImagePicker extends CordovaPlugin { private CallbackContext callbackContext; private int maxImageCount; + private double maxFileSize; private LinearLayout layout = null; public boolean execute(String action, final JSONArray args, final CallbackContext callbackContext) throws JSONException { @@ -67,6 +74,7 @@ public boolean execute(String action, final JSONArray args, final CallbackContex } else if (ACTION_GET_PICTURES.equals(action)) { final JSONObject params = args.getJSONObject(0); this.maxImageCount = params.has("maximumImagesCount") ? params.getInt("maximumImagesCount") : 20; + this.maxFileSize = params.has("maxFileSize") ? params.getDouble("maxFileSize") : 50; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && SdkExtensions.getExtensionVersion(Build.VERSION_CODES.R) >= 2) { int deviceMaxLimit = MediaStore.getPickImagesMaxLimit(); if (this.maxImageCount > deviceMaxLimit) { @@ -76,14 +84,21 @@ public boolean execute(String action, final JSONArray args, final CallbackContex } boolean useFilePicker = params.has("useFilePicker") && params.getBoolean("useFilePicker"); + boolean allowVideo = params.has("allow_video") && params.getBoolean("allow_video"); Intent imagePickerIntent = null; if (useFilePicker) { + List mimeTypes = new ArrayList<>(); + mimeTypes.add("image/*"); + if (allowVideo) { + mimeTypes.add("video/*"); + } imagePickerIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT); - imagePickerIntent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*"); + imagePickerIntent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "*/*"); + imagePickerIntent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes.toArray(new String[0])); imagePickerIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); } else { - PickVisualMediaRequest pickVisualMediaRequest = new PickVisualMediaRequest.Builder().setMediaType(ActivityResultContracts.PickVisualMedia.ImageOnly.INSTANCE).build(); + PickVisualMediaRequest pickVisualMediaRequest = new PickVisualMediaRequest.Builder().setMediaType(allowVideo ? ActivityResultContracts.PickVisualMedia.ImageAndVideo.INSTANCE : ActivityResultContracts.PickVisualMedia.ImageOnly.INSTANCE).build(); imagePickerIntent = new ActivityResultContracts.PickMultipleVisualMedia(maxImageCount).createIntent(cordova.getContext(), pickVisualMediaRequest); } @@ -98,6 +113,7 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { ExecutorService executor = Executors.newSingleThreadExecutor(); ArrayList imageInfos = new ArrayList<>(); executor.execute(() -> { + boolean sizeLimitExceeded = false; try { cordova.getActivity().runOnUiThread(() -> { showLoader(); @@ -110,16 +126,30 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == SELECT_PICTURE) { if (data.getData() != null) { uri = data.getData(); - path = ImagePicker.this.copyFileToInternalStorage(uri, ""); - if (path.equals("-1")) { - callbackContext.error(FILE_ACCESS_ERROR); - return; + double size = this.getFileSizeFromUri(uri); + if (size > this.maxFileSize) { + sizeLimitExceeded = true; + } else { + path = ImagePicker.this.copyFileToInternalStorage(uri, ""); + if (path.equals("-1")) { + callbackContext.error(FILE_ACCESS_ERROR); + return; + } + fileURIs.add(path); } - fileURIs.add(path); } else { ClipData clip = data.getClipData(); for (int i = 0; i < clip.getItemCount(); i++) { uri = clip.getItemAt(i).getUri(); + double size = this.getFileSizeFromUri(uri); + if (size > this.maxFileSize) { + sizeLimitExceeded = true; + if (i + 1 >= ImagePicker.this.maxImageCount) { + break; + } + continue; + } + path = ImagePicker.this.copyFileToInternalStorage(uri, ""); if (path.equals("-1")) { callbackContext.error(FILE_ACCESS_ERROR); @@ -136,6 +166,11 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; Uri ImageUri = Uri.parse(fileURIs.get(i)); + Log.d("hashir", "uri: " + fileURIs.get(i)); + File videoFile = new File( + cordova.getContext().getFilesDir(), + fileURIs.get(i) + ); BitmapFactory.decodeFile(new File(ImageUri.getPath()).getAbsolutePath(), options); JSONObject json = new JSONObject(); json.put("path", fileURIs.get(i)); @@ -144,6 +179,9 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { imageInfos.add(json); } JSONArray res = new JSONArray(imageInfos); + if (sizeLimitExceeded) { + this.showMaxFileSizeExceededWarning(); + } callbackContext.success(res); } else if (resultCode == Activity.RESULT_CANCELED && data != null) { String error = data.getStringExtra("ERRORMESSAGE"); @@ -204,6 +242,11 @@ public void onRestoreStateForActivityResult(Bundle state, CallbackContext callba this.callbackContext = callbackContext; } + public boolean isVideo(Uri uri) { + String mime = cordova.getContext().getContentResolver().getType(uri); + return mime != null && mime.startsWith("video/"); + } + @Override public void onRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults) throws JSONException { @@ -285,6 +328,54 @@ private String copyFileToInternalStorage(Uri uri, String newDirName) { return output.getPath(); } + private double getFileSizeFromUri(Uri uri) { + double sizeBytes = 0; + Cursor cursor = cordova.getActivity().getContentResolver().query( + uri, + null, + null, + null, + null + ); + if (cursor != null) { + int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE); + cursor.moveToFirst(); + sizeBytes = cursor.getLong(sizeIndex); + cursor.close(); + } + + // Convert bytes → MB + return sizeBytes / (1024.0 * 1024.0); + } + + private String generateVideoThumbnail(File videoFile) { + String thumbnailUri = ""; + if (cordova.getContext() == null) { return thumbnailUri; } + + String filename = "video_thumb_" + UUID.randomUUID().toString() + ".jpg"; + File thumbnail = new File(cordova.getContext().getFilesDir(), filename); + Bitmap bitmap = null; + + bitmap = generateColoredBitmap(new Size(500, 500), Color.DKGRAY); + try (FileOutputStream out = new FileOutputStream(thumbnail)) { + bitmap.compress(Bitmap.CompressFormat.JPEG, 80, out); + out.flush(); + } catch (Exception e) { + return thumbnailUri; + } + + thumbnailUri = Uri.fromFile(thumbnail).toString(); + return thumbnailUri; + } + + public Bitmap generateColoredBitmap(Size size, int color) { + Bitmap bitmap = Bitmap.createBitmap(size.getWidth(), size.getHeight(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + canvas.drawColor(color); + + return bitmap; + } + private void showMaxLimitWarning(boolean useFilePicker) { String toastMsg = "You can only select up to " + this.maxImageCount + " image(s)"; if (useFilePicker) { @@ -299,4 +390,8 @@ private void showMaxLimitWarning(int deviceMaxLimit) { (Toast.makeText(cordova.getContext(), toastMsg, Toast.LENGTH_LONG)).show(); } + private void showMaxFileSizeExceededWarning() { + String toastMsg = "Media(s) above max limit " + this.maxFileSize + "MB not selected"; + cordova.getActivity().runOnUiThread(() -> (Toast.makeText(cordova.getContext(), toastMsg, Toast.LENGTH_LONG)).show()); + } } From 1ca7b1f8295a7789346100aa6591f92ca462559f Mon Sep 17 00:00:00 2001 From: Hashir Rajah Date: Tue, 25 Nov 2025 11:03:19 +0400 Subject: [PATCH 02/16] v2 --- src/android/ImagePicker.java | 29 ++++++++++++++++++----------- www/imagepicker.js | 1 + 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/android/ImagePicker.java b/src/android/ImagePicker.java index 2db56a67..fa8aa7a9 100644 --- a/src/android/ImagePicker.java +++ b/src/android/ImagePicker.java @@ -23,6 +23,7 @@ import android.view.Gravity; import android.view.ViewGroup; import android.view.WindowManager; +import android.webkit.MimeTypeMap; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.Toast; @@ -60,7 +61,7 @@ public class ImagePicker extends CordovaPlugin { private CallbackContext callbackContext; private int maxImageCount; - private double maxFileSize; + private int maxFileSize; private LinearLayout layout = null; public boolean execute(String action, final JSONArray args, final CallbackContext callbackContext) throws JSONException { @@ -74,7 +75,7 @@ public boolean execute(String action, final JSONArray args, final CallbackContex } else if (ACTION_GET_PICTURES.equals(action)) { final JSONObject params = args.getJSONObject(0); this.maxImageCount = params.has("maximumImagesCount") ? params.getInt("maximumImagesCount") : 20; - this.maxFileSize = params.has("maxFileSize") ? params.getDouble("maxFileSize") : 50; + this.maxFileSize = params.has("maxFileSize") ? params.getInt("maxFileSize") : 50; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && SdkExtensions.getExtensionVersion(Build.VERSION_CODES.R) >= 2) { int deviceMaxLimit = MediaStore.getPickImagesMaxLimit(); if (this.maxImageCount > deviceMaxLimit) { @@ -163,19 +164,22 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { } } for (int i=0; i < fileURIs.size(); i++) { + String fileUri = fileURIs.get(i); + boolean isVideo = this.isVideo(fileUri); BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; - Uri ImageUri = Uri.parse(fileURIs.get(i)); - Log.d("hashir", "uri: " + fileURIs.get(i)); - File videoFile = new File( - cordova.getContext().getFilesDir(), - fileURIs.get(i) - ); + Uri ImageUri = Uri.parse(fileUri); BitmapFactory.decodeFile(new File(ImageUri.getPath()).getAbsolutePath(), options); JSONObject json = new JSONObject(); - json.put("path", fileURIs.get(i)); + json.put("path", fileUri); + json.put("isVideo", isVideo); json.put("width", options.outWidth); json.put("height", options.outHeight); + if (isVideo) { + File videoFile = new File(fileUri); + String videoThumbnail = this.generateVideoThumbnail(videoFile); + json.put("thumbnail", videoThumbnail); + } imageInfos.add(json); } JSONArray res = new JSONArray(imageInfos); @@ -242,8 +246,11 @@ public void onRestoreStateForActivityResult(Bundle state, CallbackContext callba this.callbackContext = callbackContext; } - public boolean isVideo(Uri uri) { - String mime = cordova.getContext().getContentResolver().getType(uri); + public boolean isVideo(String filePath) { + String extension = MimeTypeMap.getFileExtensionFromUrl(filePath); + String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension( + extension != null ? extension.toLowerCase() : null + ); return mime != null && mime.startsWith("video/"); } diff --git a/www/imagepicker.js b/www/imagepicker.js index 800cb4ab..a99f2807 100644 --- a/www/imagepicker.js +++ b/www/imagepicker.js @@ -59,6 +59,7 @@ ImagePicker.prototype.getPictures = function(success, fail, options) { var params = { maximumImagesCount: options.maximumImagesCount ? options.maximumImagesCount : 15, + maxFileSize: options.maxFileSize ? options.maxFileSize : 50, useFilePicker: options.useFilePicker ? options.useFilePicker : false, width: options.width ? options.width : 0, height: options.height ? options.height : 0, From 24c12f26ed4032774317de157f1d4c4867475c57 Mon Sep 17 00:00:00 2001 From: Hashir Rajah Date: Tue, 25 Nov 2025 11:54:15 +0400 Subject: [PATCH 03/16] Android version --- src/android/ImagePicker.java | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/android/ImagePicker.java b/src/android/ImagePicker.java index fa8aa7a9..a1e48d97 100644 --- a/src/android/ImagePicker.java +++ b/src/android/ImagePicker.java @@ -12,6 +12,8 @@ import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; +import android.media.MediaMetadataRetriever; +import android.media.ThumbnailUtils; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -42,6 +44,7 @@ import java.io.File; import java.io.FileOutputStream; +import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; @@ -362,8 +365,37 @@ private String generateVideoThumbnail(File videoFile) { String filename = "video_thumb_" + UUID.randomUUID().toString() + ".jpg"; File thumbnail = new File(cordova.getContext().getFilesDir(), filename); Bitmap bitmap = null; + Size size = null; - bitmap = generateColoredBitmap(new Size(500, 500), Color.DKGRAY); + try (MediaMetadataRetriever retriever = new MediaMetadataRetriever()) { + retriever.setDataSource(String.valueOf(videoFile)); + + String widthStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH); + String heightStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT); + + if (widthStr != null && heightStr != null) { + int videoWidth = Integer.parseInt(widthStr); + int videoHeight = Integer.parseInt(heightStr); + size = new Size(videoWidth, videoHeight); + } + try { + retriever.release(); + } catch (IOException e) { + Log.e("ImagePicker", "generateVideoThumbnail: " + e.getMessage()); + } + } catch (Exception e) { + Log.e("ImagePicker", "generateVideoThumbnail: " + e.getMessage()); + } + + if (size == null) { + size = new Size(500, 500); + } + + try { + bitmap = ThumbnailUtils.createVideoThumbnail(videoFile, size, null); + } catch (IOException e) { + bitmap = generateColoredBitmap(size, Color.DKGRAY); + } try (FileOutputStream out = new FileOutputStream(thumbnail)) { bitmap.compress(Bitmap.CompressFormat.JPEG, 80, out); out.flush(); From 59ad151b4d41f19f96e577fa95ae1bf8359e242f Mon Sep 17 00:00:00 2001 From: Hashir Rajah Date: Tue, 25 Nov 2025 12:25:15 +0400 Subject: [PATCH 04/16] Remove unused import --- src/android/ImagePicker.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/android/ImagePicker.java b/src/android/ImagePicker.java index a1e48d97..99c91a19 100644 --- a/src/android/ImagePicker.java +++ b/src/android/ImagePicker.java @@ -32,7 +32,6 @@ import androidx.activity.result.PickVisualMediaRequest; import androidx.activity.result.contract.ActivityResultContracts; -import androidx.camera.core.ResolutionInfo; import androidx.core.content.ContextCompat; import org.apache.cordova.CallbackContext; From 684b9b109d8785b633068eff7d07d18ad34761e4 Mon Sep 17 00:00:00 2001 From: parveshneedhoo Date: Wed, 26 Nov 2025 12:33:24 +0400 Subject: [PATCH 05/16] support picking video from picker --- src/ios/SOSPicker.m | 107 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 104 insertions(+), 3 deletions(-) diff --git a/src/ios/SOSPicker.m b/src/ios/SOSPicker.m index 83dc60b7..b8c368ef 100644 --- a/src/ios/SOSPicker.m +++ b/src/ios/SOSPicker.m @@ -192,16 +192,58 @@ - (void)assetsPickerController:(GMImagePickerController *)picker didFinishPickin NSString* filePath; CDVPluginResult* result = nil; - for (GMFetchItem *item in fetchArray) { + NSArray *phAssets = picker.selectedAssets; + + for (NSInteger i = 0; i < fetchArray.count; i++) { + GMFetchItem *item = fetchArray[i]; + PHAsset *phAsset = (i < phAssets.count) ? phAssets[i] : nil; if ( !item.image_fullsize ) { continue; } - + + BOOL isVideo = NO; + if (phAsset && phAsset.mediaType == PHAssetMediaTypeVideo) { + isVideo = YES; + } + + NSString *fileExtension = isVideo ? @"mp4" : @"jpg"; do { - filePath = [NSString stringWithFormat:@"%@/%@.%@", libPath, [[NSUUID UUID] UUIDString], @"jpg"]; + filePath = [NSString stringWithFormat:@"%@/%@.%@", libPath, [[NSUUID UUID] UUIDString], fileExtension]; } while ([fileMgr fileExistsAtPath:filePath]); + if (isVideo && phAsset) { + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + __block BOOL exportSuccess = NO; + __block NSError *exportError = nil; + + [self exportVideoFromPHAsset:phAsset toPath:filePath completion:^(BOOL success, NSError *error) { + exportSuccess = success; + exportError = error; + dispatch_semaphore_signal(semaphore); + }]; + + dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 5 * 60 * NSEC_PER_SEC); + if (dispatch_semaphore_wait(semaphore, timeout) != 0) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:@"Video export timeout"]; + break; + } + + if (exportSuccess) { + CGSize videoSize = [self getVideoDimensionsAtPath:filePath]; + NSDictionary *videoInfo = @{@"path":[[NSURL fileURLWithPath:filePath] absoluteString], + @"isVideo": @(YES), + @"width": [NSNumber numberWithFloat:videoSize.width], + @"height": [NSNumber numberWithFloat:videoSize.height]}; + [resultList addObject: videoInfo]; + } else { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:exportError ? exportError.localizedDescription : @"Video export failed"]; + break; + } + continue; + } + + // Handle images NSData* data = nil; if (self.width == 0 && self.height == 0) { // no scaling required @@ -269,6 +311,65 @@ - (void)assetsPickerController:(GMImagePickerController *)picker didFinishPickin } +- (CGSize)getVideoDimensionsAtPath:(NSString *)path { + AVAsset *asset = [AVAsset assetWithURL:[NSURL fileURLWithPath:path]]; + NSArray *tracks = [asset tracksWithMediaType:AVMediaTypeVideo]; + if ([tracks count] > 0) { + AVAssetTrack *videoTrack = [tracks objectAtIndex:0]; + CGSize size = videoTrack.naturalSize; + return size; + } + return CGSizeMake(0, 0); +} + +- (void)exportVideoFromPHAsset:(PHAsset *)asset toPath:(NSString *)outputPath completion:(void (^)(BOOL success, NSError *error))completion { + PHVideoRequestOptions *options = [[PHVideoRequestOptions alloc] init]; + options.version = PHVideoRequestOptionsVersionCurrent; + options.deliveryMode = PHVideoRequestOptionsDeliveryModeFastFormat; + options.networkAccessAllowed = YES; + + [[PHImageManager defaultManager] requestAVAssetForVideo:asset options:options resultHandler:^(AVAsset *avAsset, AVAudioMix *audioMix, NSDictionary *info) { + if (avAsset == nil) { + if (completion) { + completion(NO, [NSError errorWithDomain:@"VideoExport" code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed to load video asset"}]); + } + return; + } + + NSError *error; + + if ([avAsset isKindOfClass:[AVURLAsset class]]) { + AVURLAsset *urlAsset = (AVURLAsset *)avAsset; + NSURL *sourceURL = urlAsset.URL; + + if (sourceURL && [[NSFileManager defaultManager] isReadableFileAtPath:sourceURL.path]) { + NSError *copyError = nil; + if ([[NSFileManager defaultManager] copyItemAtPath:sourceURL.path toPath:outputPath error:©Error]) { + if (completion) { + completion(YES, nil); + } + return; + } else { + error = [NSError errorWithDomain:@"VideoExport" code:-1 userInfo:@{NSLocalizedDescriptionKey: copyError.localizedDescription}]; + if (completion) { + completion(NO, error); + } + } + } else { + error = [NSError errorWithDomain:@"VideoExport" code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Source file not readable or doesn't exist"}]; + if (completion) { + completion(NO, error); + } + } + } else { + error = [NSError errorWithDomain:@"VideoExport" code:-1 userInfo:@{NSLocalizedDescriptionKey: @"AVAsset is not AVURLAsset"}]; + if (completion) { + completion(NO, error); + } + } + }]; +} + //Optional implementation: -(void)assetsPickerControllerDidCancel:(GMImagePickerController *)picker { From 7e81273bcd3ba5f55b16cd95d64c378f3ef29a99 Mon Sep 17 00:00:00 2001 From: parveshneedhoo Date: Wed, 26 Nov 2025 18:55:38 +0400 Subject: [PATCH 06/16] thumbnail for videos --- src/ios/SOSPicker.m | 52 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/src/ios/SOSPicker.m b/src/ios/SOSPicker.m index b8c368ef..40bf3275 100644 --- a/src/ios/SOSPicker.m +++ b/src/ios/SOSPicker.m @@ -7,8 +7,6 @@ // #import "SOSPicker.h" - - #import "GMImagePickerController.h" #import "GMFetchItem.h" @@ -231,10 +229,14 @@ - (void)assetsPickerController:(GMImagePickerController *)picker didFinishPickin if (exportSuccess) { CGSize videoSize = [self getVideoDimensionsAtPath:filePath]; - NSDictionary *videoInfo = @{@"path":[[NSURL fileURLWithPath:filePath] absoluteString], + NSString *thumbnailPath = [self generateThumbnailForVideoAtURL:[NSURL fileURLWithPath:filePath]]; + NSMutableDictionary *videoInfo = [NSMutableDictionary dictionaryWithDictionary:@{@"path":[[NSURL fileURLWithPath:filePath] absoluteString], @"isVideo": @(YES), @"width": [NSNumber numberWithFloat:videoSize.width], - @"height": [NSNumber numberWithFloat:videoSize.height]}; + @"height": [NSNumber numberWithFloat:videoSize.height]}]; + if (thumbnailPath) { + [videoInfo setObject:[[NSURL fileURLWithPath:thumbnailPath] absoluteString] forKey:@"thumbnail"]; + } [resultList addObject: videoInfo]; } else { result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:exportError ? exportError.localizedDescription : @"Video export failed"]; @@ -369,6 +371,48 @@ - (void)exportVideoFromPHAsset:(PHAsset *)asset toPath:(NSString *)outputPath co } }]; } + +- (NSString*)generateThumbnailForVideoAtURL:(NSURL *)videoURL { + AVAsset *asset = [AVAsset assetWithURL:videoURL]; + AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:asset]; + imageGenerator.appliesPreferredTrackTransform = YES; + CMTime time = CMTimeMakeWithSeconds(1.0, 600); + NSError *error = nil; + CMTime actualTime; + CGImageRef imageRef = [imageGenerator copyCGImageAtTime:time actualTime:&actualTime error:&error]; + + if (error) { + NSLog(@"Error generating thumbnail: %@", error.localizedDescription); + return nil; + } + + UIImage *thumbnail = [[UIImage alloc] initWithCGImage:imageRef]; + CGImageRelease(imageRef); + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES); + NSString *libraryDirectory = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"NoCloud"]; + NSError *directoryError = nil; + + if (![[NSFileManager defaultManager] fileExistsAtPath:libraryDirectory]) { + [[NSFileManager defaultManager] createDirectoryAtPath:libraryDirectory withIntermediateDirectories:YES attributes:nil error:&directoryError]; + if (directoryError) { + NSLog(@"Error creating NoCloud directory: %@", directoryError.localizedDescription); + return nil; + } + } + + NSString *uniqueFileName = [NSString stringWithFormat:@"video_thumb_%@.jpg", [[NSUUID UUID] UUIDString]]; + NSString *filePath = [libraryDirectory stringByAppendingPathComponent:uniqueFileName]; + NSData *jpegData = UIImageJPEGRepresentation(thumbnail, 1.0); + + if ([jpegData writeToFile:filePath atomically:YES]) { + NSLog(@"Thumbnail saved successfully at path: %@", filePath); + } else { + NSLog(@"Failed to save thumbnail."); + return nil; + } + + return filePath; +} //Optional implementation: -(void)assetsPickerControllerDidCancel:(GMImagePickerController *)picker From 4c76828980b6a254aafd3586d3ed55f44df7cf81 Mon Sep 17 00:00:00 2001 From: parveshneedhoo Date: Wed, 26 Nov 2025 22:16:01 +0400 Subject: [PATCH 07/16] limitation for video size --- src/ios/GMImagePicker/GMGridViewController.h | 1 + src/ios/GMImagePicker/GMGridViewController.m | 56 ++++++++++++++++---- src/ios/SOSPicker.h | 1 + src/ios/SOSPicker.m | 41 ++++++++++++++ 4 files changed, 89 insertions(+), 10 deletions(-) diff --git a/src/ios/GMImagePicker/GMGridViewController.h b/src/ios/GMImagePicker/GMGridViewController.h index 3265f1dc..ba2d473c 100755 --- a/src/ios/GMImagePicker/GMGridViewController.h +++ b/src/ios/GMImagePicker/GMGridViewController.h @@ -17,6 +17,7 @@ @interface GMGridViewController : UICollectionViewController @property (strong) PHFetchResult *assetsFetchResults; +@property (nonatomic, strong) NSArray *filteredAssets; @property (nonatomic, weak) NSMutableDictionary * dic_asset_fetches; -(id)initWithPicker:(GMImagePickerController *)picker; diff --git a/src/ios/GMImagePicker/GMGridViewController.m b/src/ios/GMImagePicker/GMGridViewController.m index 92a13ba9..21b4bfec 100755 --- a/src/ios/GMImagePicker/GMGridViewController.m +++ b/src/ios/GMImagePicker/GMGridViewController.m @@ -82,6 +82,12 @@ @implementation GMGridViewController @synthesize dic_asset_fetches; +- (void)setAssetsFetchResults:(PHFetchResult *)assetsFetchResults +{ + _assetsFetchResults = assetsFetchResults; + [self filterAssets]; +} + -(id)initWithPicker:(GMImagePickerController *)picker { //Custom init. The picker contains custom information to create the FlowLayout @@ -147,6 +153,7 @@ - (void)viewDidLoad } self.imageManager = [[PHCachingImageManager alloc] init]; + [self filterAssets]; [self resetCachedAssets]; [[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self]; @@ -222,6 +229,34 @@ - (void)setupViews self.collectionView.backgroundColor = [UIColor whiteColor]; } +- (void)filterAssets +{ + if (!self.assetsFetchResults) { + self.filteredAssets = @[]; + return; + } + + NSMutableArray *filtered = [NSMutableArray array]; + + // Check if delegate implements shouldShowAsset method + BOOL implementsFilter = [self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldShowAsset:)]; + + for (NSInteger i = 0; i < self.assetsFetchResults.count; i++) { + PHAsset *asset = self.assetsFetchResults[i]; + + if (implementsFilter) { + if ([self.picker.delegate assetsPickerController:self.picker shouldShowAsset:asset]) { + [filtered addObject:asset]; + } + } else { + // If delegate doesn't implement the method, show all assets + [filtered addObject:asset]; + } + } + + self.filteredAssets = [filtered copy]; +} + - (void)setupButtons { self.navigationItem.rightBarButtonItem = @@ -310,7 +345,7 @@ - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cell NSInteger currentTag = cell.tag + 1; cell.tag = currentTag; - PHAsset *asset = self.assetsFetchResults[indexPath.item]; + PHAsset *asset = self.filteredAssets[indexPath.item]; [cell bind:asset]; //GMFetchItem * fetch_item = [dic_asset_fetches objectForKey:[NSNumber numberWithLong:indexPath.item] ]; @@ -438,7 +473,7 @@ - (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtInde return NO; } - PHAsset *asset = self.assetsFetchResults[indexPath.item]; + PHAsset *asset = self.filteredAssets[indexPath.item]; //GMFetchItem * fetch_item = [dic_asset_fetches objectForKey:[ NSNumber numberWithLong:indexPath.item ]]; GMFetchItem * fetch_item = [dic_asset_fetches objectForKey:asset]; @@ -580,7 +615,7 @@ - (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtInde - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { - PHAsset *asset = self.assetsFetchResults[indexPath.item]; + PHAsset *asset = self.filteredAssets[indexPath.item]; //GMFetchItem * fetch_item = [dic_asset_fetches objectForKey:[ NSNumber numberWithLong:indexPath.item ]]; GMFetchItem * fetch_item = [dic_asset_fetches objectForKey:asset]; @@ -593,7 +628,7 @@ - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPa - (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath { - PHAsset *asset = self.assetsFetchResults[indexPath.item]; + PHAsset *asset = self.filteredAssets[indexPath.item]; if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldDeselectAsset:)]) return [self.picker.delegate assetsPickerController:self.picker shouldDeselectAsset:asset]; @@ -603,7 +638,7 @@ - (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIn - (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath { - PHAsset *asset = self.assetsFetchResults[indexPath.item]; + PHAsset *asset = self.filteredAssets[indexPath.item]; //GMFetchItem * fetch_item = [dic_asset_fetches objectForKey:[ NSNumber numberWithLong:indexPath.item ]]; GMFetchItem * fetch_item = [dic_asset_fetches objectForKey:asset]; @@ -616,7 +651,7 @@ - (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndex - (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath { - PHAsset *asset = self.assetsFetchResults[indexPath.item]; + PHAsset *asset = self.filteredAssets[indexPath.item]; if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldHighlightAsset:)]) return [self.picker.delegate assetsPickerController:self.picker shouldHighlightAsset:asset]; @@ -626,7 +661,7 @@ - (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtI - (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath { - PHAsset *asset = self.assetsFetchResults[indexPath.item]; + PHAsset *asset = self.filteredAssets[indexPath.item]; if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didHighlightAsset:)]) [self.picker.delegate assetsPickerController:self.picker didHighlightAsset:asset]; @@ -634,7 +669,7 @@ - (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtInde - (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath { - PHAsset *asset = self.assetsFetchResults[indexPath.item]; + PHAsset *asset = self.filteredAssets[indexPath.item]; if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didUnhighlightAsset:)]) [self.picker.delegate assetsPickerController:self.picker didUnhighlightAsset:asset]; @@ -646,7 +681,7 @@ - (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIn - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { - NSInteger count = self.assetsFetchResults.count; + NSInteger count = self.filteredAssets.count; return count; } @@ -664,6 +699,7 @@ - (void)photoLibraryDidChange:(PHChange *)changeInstance // get the new fetch result self.assetsFetchResults = [collectionChanges fetchResultAfterChanges]; + [self filterAssets]; //NSLog( @"reset all" ); @@ -788,7 +824,7 @@ - (NSArray *)assetsAtIndexPaths:(NSArray *)indexPaths NSMutableArray *assets = [NSMutableArray arrayWithCapacity:indexPaths.count]; for (NSIndexPath *indexPath in indexPaths) { - PHAsset *asset = self.assetsFetchResults[indexPath.item]; + PHAsset *asset = self.filteredAssets[indexPath.item]; [assets addObject:asset]; } return assets; diff --git a/src/ios/SOSPicker.h b/src/ios/SOSPicker.h index 12bdfeeb..2670a0cf 100644 --- a/src/ios/SOSPicker.h +++ b/src/ios/SOSPicker.h @@ -25,5 +25,6 @@ @property (nonatomic, assign) NSInteger height; @property (nonatomic, assign) NSInteger quality; @property (nonatomic, assign) NSInteger outputType; +@property (nonatomic, assign) NSInteger maxMB; @end diff --git a/src/ios/SOSPicker.m b/src/ios/SOSPicker.m index 40bf3275..47a6dec4 100644 --- a/src/ios/SOSPicker.m +++ b/src/ios/SOSPicker.m @@ -7,6 +7,8 @@ // #import "SOSPicker.h" +#import +#import #import "GMImagePickerController.h" #import "GMFetchItem.h" @@ -65,6 +67,7 @@ - (void) getPictures:(CDVInvokedUrlCommand *)command { self.width = [[options objectForKey:@"width"] integerValue]; self.height = [[options objectForKey:@"height"] integerValue]; self.quality = [[options objectForKey:@"quality"] integerValue]; + self.maxMB = [[options objectForKey:@"maxFileSize"] integerValue]; self.callbackId = command.callbackId; [self launchGMImagePicker:allow_video title:title message:message disable_popover:disable_popover maximumImagesCount:maximumImagesCount]; @@ -435,4 +438,42 @@ - (void) closeImagePicker:(CDVInvokedUrlCommand *)command { [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } +#pragma mark - GMImagePickerControllerDelegate - Asset Filtering + +- (BOOL)assetsPickerController:(GMImagePickerController *)picker shouldShowAsset:(PHAsset *)asset { + if (asset.mediaType != PHAssetMediaTypeVideo) { + return YES; + } + + NSArray *resources = [PHAssetResource assetResourcesForAsset:asset]; + long long fileSize = 0; + + for (PHAssetResource *resource in resources) { + if (resource.type == PHAssetResourceTypeVideo || + resource.type == PHAssetResourceTypeFullSizeVideo || + resource.type == PHAssetResourceTypePairedVideo) { + + @try { + id fileSizeValue = [resource valueForKey:@"fileSize"]; + if ([fileSizeValue isKindOfClass:[NSNumber class]]) { + fileSize = [fileSizeValue longLongValue]; + break; + } + } @catch (NSException *exception) { + // Continue to next resource if fileSize is not available + } + } + } + + if (fileSize > 0) { + CGFloat mb = fileSize / (1024.0 * 1024.0); + + if (mb > self.maxMB) { + return NO; + } + return YES; + } + return YES; +} + @end From 3c2e11b7413492d65f962470d60e9bfbeda319c3 Mon Sep 17 00:00:00 2001 From: parveshneedhoo Date: Thu, 27 Nov 2025 11:04:06 +0400 Subject: [PATCH 08/16] refactor-allow selecting video more than limit,show toast afterward --- src/ios/GMImagePicker/GMGridViewController.h | 1 - src/ios/GMImagePicker/GMGridViewController.m | 56 ++----- src/ios/SOSPicker.h | 2 + src/ios/SOSPicker.m | 167 ++++++++++++++----- 4 files changed, 134 insertions(+), 92 deletions(-) diff --git a/src/ios/GMImagePicker/GMGridViewController.h b/src/ios/GMImagePicker/GMGridViewController.h index ba2d473c..3265f1dc 100755 --- a/src/ios/GMImagePicker/GMGridViewController.h +++ b/src/ios/GMImagePicker/GMGridViewController.h @@ -17,7 +17,6 @@ @interface GMGridViewController : UICollectionViewController @property (strong) PHFetchResult *assetsFetchResults; -@property (nonatomic, strong) NSArray *filteredAssets; @property (nonatomic, weak) NSMutableDictionary * dic_asset_fetches; -(id)initWithPicker:(GMImagePickerController *)picker; diff --git a/src/ios/GMImagePicker/GMGridViewController.m b/src/ios/GMImagePicker/GMGridViewController.m index 21b4bfec..92a13ba9 100755 --- a/src/ios/GMImagePicker/GMGridViewController.m +++ b/src/ios/GMImagePicker/GMGridViewController.m @@ -82,12 +82,6 @@ @implementation GMGridViewController @synthesize dic_asset_fetches; -- (void)setAssetsFetchResults:(PHFetchResult *)assetsFetchResults -{ - _assetsFetchResults = assetsFetchResults; - [self filterAssets]; -} - -(id)initWithPicker:(GMImagePickerController *)picker { //Custom init. The picker contains custom information to create the FlowLayout @@ -153,7 +147,6 @@ - (void)viewDidLoad } self.imageManager = [[PHCachingImageManager alloc] init]; - [self filterAssets]; [self resetCachedAssets]; [[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self]; @@ -229,34 +222,6 @@ - (void)setupViews self.collectionView.backgroundColor = [UIColor whiteColor]; } -- (void)filterAssets -{ - if (!self.assetsFetchResults) { - self.filteredAssets = @[]; - return; - } - - NSMutableArray *filtered = [NSMutableArray array]; - - // Check if delegate implements shouldShowAsset method - BOOL implementsFilter = [self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldShowAsset:)]; - - for (NSInteger i = 0; i < self.assetsFetchResults.count; i++) { - PHAsset *asset = self.assetsFetchResults[i]; - - if (implementsFilter) { - if ([self.picker.delegate assetsPickerController:self.picker shouldShowAsset:asset]) { - [filtered addObject:asset]; - } - } else { - // If delegate doesn't implement the method, show all assets - [filtered addObject:asset]; - } - } - - self.filteredAssets = [filtered copy]; -} - - (void)setupButtons { self.navigationItem.rightBarButtonItem = @@ -345,7 +310,7 @@ - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cell NSInteger currentTag = cell.tag + 1; cell.tag = currentTag; - PHAsset *asset = self.filteredAssets[indexPath.item]; + PHAsset *asset = self.assetsFetchResults[indexPath.item]; [cell bind:asset]; //GMFetchItem * fetch_item = [dic_asset_fetches objectForKey:[NSNumber numberWithLong:indexPath.item] ]; @@ -473,7 +438,7 @@ - (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtInde return NO; } - PHAsset *asset = self.filteredAssets[indexPath.item]; + PHAsset *asset = self.assetsFetchResults[indexPath.item]; //GMFetchItem * fetch_item = [dic_asset_fetches objectForKey:[ NSNumber numberWithLong:indexPath.item ]]; GMFetchItem * fetch_item = [dic_asset_fetches objectForKey:asset]; @@ -615,7 +580,7 @@ - (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtInde - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { - PHAsset *asset = self.filteredAssets[indexPath.item]; + PHAsset *asset = self.assetsFetchResults[indexPath.item]; //GMFetchItem * fetch_item = [dic_asset_fetches objectForKey:[ NSNumber numberWithLong:indexPath.item ]]; GMFetchItem * fetch_item = [dic_asset_fetches objectForKey:asset]; @@ -628,7 +593,7 @@ - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPa - (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath { - PHAsset *asset = self.filteredAssets[indexPath.item]; + PHAsset *asset = self.assetsFetchResults[indexPath.item]; if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldDeselectAsset:)]) return [self.picker.delegate assetsPickerController:self.picker shouldDeselectAsset:asset]; @@ -638,7 +603,7 @@ - (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIn - (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath { - PHAsset *asset = self.filteredAssets[indexPath.item]; + PHAsset *asset = self.assetsFetchResults[indexPath.item]; //GMFetchItem * fetch_item = [dic_asset_fetches objectForKey:[ NSNumber numberWithLong:indexPath.item ]]; GMFetchItem * fetch_item = [dic_asset_fetches objectForKey:asset]; @@ -651,7 +616,7 @@ - (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndex - (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath { - PHAsset *asset = self.filteredAssets[indexPath.item]; + PHAsset *asset = self.assetsFetchResults[indexPath.item]; if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldHighlightAsset:)]) return [self.picker.delegate assetsPickerController:self.picker shouldHighlightAsset:asset]; @@ -661,7 +626,7 @@ - (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtI - (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath { - PHAsset *asset = self.filteredAssets[indexPath.item]; + PHAsset *asset = self.assetsFetchResults[indexPath.item]; if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didHighlightAsset:)]) [self.picker.delegate assetsPickerController:self.picker didHighlightAsset:asset]; @@ -669,7 +634,7 @@ - (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtInde - (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath { - PHAsset *asset = self.filteredAssets[indexPath.item]; + PHAsset *asset = self.assetsFetchResults[indexPath.item]; if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didUnhighlightAsset:)]) [self.picker.delegate assetsPickerController:self.picker didUnhighlightAsset:asset]; @@ -681,7 +646,7 @@ - (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIn - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { - NSInteger count = self.filteredAssets.count; + NSInteger count = self.assetsFetchResults.count; return count; } @@ -699,7 +664,6 @@ - (void)photoLibraryDidChange:(PHChange *)changeInstance // get the new fetch result self.assetsFetchResults = [collectionChanges fetchResultAfterChanges]; - [self filterAssets]; //NSLog( @"reset all" ); @@ -824,7 +788,7 @@ - (NSArray *)assetsAtIndexPaths:(NSArray *)indexPaths NSMutableArray *assets = [NSMutableArray arrayWithCapacity:indexPaths.count]; for (NSIndexPath *indexPath in indexPaths) { - PHAsset *asset = self.filteredAssets[indexPath.item]; + PHAsset *asset = self.assetsFetchResults[indexPath.item]; [assets addObject:asset]; } return assets; diff --git a/src/ios/SOSPicker.h b/src/ios/SOSPicker.h index 2670a0cf..e4fe306d 100644 --- a/src/ios/SOSPicker.h +++ b/src/ios/SOSPicker.h @@ -26,5 +26,7 @@ @property (nonatomic, assign) NSInteger quality; @property (nonatomic, assign) NSInteger outputType; @property (nonatomic, assign) NSInteger maxMB; +@property (nonatomic, assign) BOOL videoSizeLimitExceeded; +@property (nonatomic, assign) BOOL videoExportFailed; @end diff --git a/src/ios/SOSPicker.m b/src/ios/SOSPicker.m index 47a6dec4..99ea0ba6 100644 --- a/src/ios/SOSPicker.m +++ b/src/ios/SOSPicker.m @@ -68,6 +68,8 @@ - (void) getPictures:(CDVInvokedUrlCommand *)command { self.height = [[options objectForKey:@"height"] integerValue]; self.quality = [[options objectForKey:@"quality"] integerValue]; self.maxMB = [[options objectForKey:@"maxFileSize"] integerValue]; + self.videoSizeLimitExceeded = NO; + self.videoExportFailed = NO; self.callbackId = command.callbackId; [self launchGMImagePicker:allow_video title:title message:message disable_popover:disable_popover maximumImagesCount:maximumImagesCount]; @@ -214,6 +216,10 @@ - (void)assetsPickerController:(GMImagePickerController *)picker didFinishPickin } while ([fileMgr fileExistsAtPath:filePath]); if (isVideo && phAsset) { + NSInteger exceeded = [self checkVideoSize:phAsset]; + + if (exceeded) continue; + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); __block BOOL exportSuccess = NO; __block NSError *exportError = nil; @@ -224,10 +230,10 @@ - (void)assetsPickerController:(GMImagePickerController *)picker didFinishPickin dispatch_semaphore_signal(semaphore); }]; - dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 5 * 60 * NSEC_PER_SEC); + dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC); if (dispatch_semaphore_wait(semaphore, timeout) != 0) { - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:@"Video export timeout"]; - break; + self.videoExportFailed = YES; + continue; } if (exportSuccess) { @@ -242,8 +248,8 @@ - (void)assetsPickerController:(GMImagePickerController *)picker didFinishPickin } [resultList addObject: videoInfo]; } else { - result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:exportError ? exportError.localizedDescription : @"Video export failed"]; - break; + self.videoExportFailed = YES; + continue; } continue; } @@ -254,7 +260,7 @@ - (void)assetsPickerController:(GMImagePickerController *)picker didFinishPickin // no scaling required if (self.outputType == BASE64_STRING){ UIImage* image = [UIImage imageNamed:item.image_fullsize]; - NSDictionary *imageInfo = @{@"path":[UIImageJPEGRepresentation(image, self.quality/100.0f) base64EncodedStringWithOptions:0], + NSDictionary *imageInfo = @{@"path":[UIImageJPEGRepresentation(image, self.quality/100.0f) base64EncodedStringWithOptions:0], @"width": [NSNumber numberWithFloat:image.size.width], @"height": [NSNumber numberWithFloat:image.size.height]}; [resultList addObject: imageInfo]; @@ -262,8 +268,8 @@ - (void)assetsPickerController:(GMImagePickerController *)picker didFinishPickin if (self.quality == 100) { // no scaling, no downsampling, this is the fastest option UIImage* image = [UIImage imageNamed:item.image_fullsize]; - NSDictionary *imageInfo = @{@"path":item.image_fullsize, - @"width": [NSNumber numberWithFloat:image.size.width], + NSDictionary *imageInfo = @{@"path":item.image_fullsize, + @"width": [NSNumber numberWithFloat:image.size.width], @"height": [NSNumber numberWithFloat:image.size.height]}; [resultList addObject: imageInfo]; @@ -275,8 +281,8 @@ - (void)assetsPickerController:(GMImagePickerController *)picker didFinishPickin result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[err localizedDescription]]; break; } else { - NSDictionary *imageInfo = @{@"path":[[NSURL fileURLWithPath:filePath] absoluteString], - @"width": [NSNumber numberWithFloat:image.size.width], + NSDictionary *imageInfo = @{@"path":[[NSURL fileURLWithPath:filePath] absoluteString], + @"width": [NSNumber numberWithFloat:image.size.width], @"height": [NSNumber numberWithFloat:image.size.height]}; [resultList addObject: imageInfo]; } @@ -293,13 +299,13 @@ - (void)assetsPickerController:(GMImagePickerController *)picker didFinishPickin break; } else { if(self.outputType == BASE64_STRING){ - NSDictionary *imageInfo = @{@"path":[data base64EncodedStringWithOptions:0], - @"width": [NSNumber numberWithFloat:scaledImage.size.width], + NSDictionary *imageInfo = @{@"path":[data base64EncodedStringWithOptions:0], + @"width": [NSNumber numberWithFloat:scaledImage.size.width], @"height": [NSNumber numberWithFloat:scaledImage.size.height]}; [resultList addObject: imageInfo]; } else { - NSDictionary *imageInfo = @{@"path":[[NSURL fileURLWithPath:filePath] absoluteString], - @"width": [NSNumber numberWithFloat:scaledImage.size.width], + NSDictionary *imageInfo = @{@"path":[[NSURL fileURLWithPath:filePath] absoluteString], + @"width": [NSNumber numberWithFloat:scaledImage.size.width], @"height": [NSNumber numberWithFloat:scaledImage.size.height]}; [resultList addObject: imageInfo]; } @@ -311,11 +317,51 @@ - (void)assetsPickerController:(GMImagePickerController *)picker didFinishPickin result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:resultList]; } + // if videoSizeLimitExceeded is YES, display toast message + if (self.videoSizeLimitExceeded == YES && self.videoExportFailed == YES) { + NSString *toastMsg = [NSString stringWithFormat:@"Video(s) above max limit %ldMB not selected and some videos failed to be exported", (long)self.maxMB]; + [self showToastMessage:toastMsg]; + } else if (self.videoSizeLimitExceeded == YES) { + NSString *toastMsg = [NSString stringWithFormat:@"Video(s) above max limit %ldMB not selected", (long)self.maxMB]; + [self showToastMessage:toastMsg]; + } else if (self.videoExportFailed == YES) { + NSString *toastMsg = [NSString stringWithFormat:@"Some videos failed to be exported", (long)self.maxMB]; + [self showToastMessage:toastMsg]; + } + [self.viewController dismissViewControllerAnimated:YES completion:nil]; [self.commandDelegate sendPluginResult:result callbackId:self.callbackId]; } +- (BOOL)checkVideoSize:(PHAsset *) asset { + NSArray *resources = [PHAssetResource assetResourcesForAsset:asset]; + long long fileSize = 0; + + for (PHAssetResource *resource in resources) { + if (resource.type == PHAssetResourceTypeVideo || + resource.type == PHAssetResourceTypeFullSizeVideo || + resource.type == PHAssetResourceTypePairedVideo) { + + @try { + id fileSizeValue = [resource valueForKey:@"fileSize"]; + if ([fileSizeValue isKindOfClass:[NSNumber class]]) { + fileSize = [fileSizeValue longLongValue]; + break; + } + } @catch (NSException *exception) { + // Continue to next resource if fileSize is not available + } + } + } + + if ((fileSize / (1024.0 * 1024.0)) > self.maxMB) { + self.videoSizeLimitExceeded = YES; + return YES; + } + return NO; +} + - (CGSize)getVideoDimensionsAtPath:(NSString *)path { AVAsset *asset = [AVAsset assetWithURL:[NSURL fileURLWithPath:path]]; NSArray *tracks = [asset tracksWithMediaType:AVMediaTypeVideo]; @@ -438,42 +484,73 @@ - (void) closeImagePicker:(CDVInvokedUrlCommand *)command { [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } -#pragma mark - GMImagePickerControllerDelegate - Asset Filtering - -- (BOOL)assetsPickerController:(GMImagePickerController *)picker shouldShowAsset:(PHAsset *)asset { - if (asset.mediaType != PHAssetMediaTypeVideo) { - return YES; - } - - NSArray *resources = [PHAssetResource assetResourcesForAsset:asset]; - long long fileSize = 0; - - for (PHAssetResource *resource in resources) { - if (resource.type == PHAssetResourceTypeVideo || - resource.type == PHAssetResourceTypeFullSizeVideo || - resource.type == PHAssetResourceTypePairedVideo) { - - @try { - id fileSizeValue = [resource valueForKey:@"fileSize"]; - if ([fileSizeValue isKindOfClass:[NSNumber class]]) { - fileSize = [fileSizeValue longLongValue]; - break; +- (void)showToastMessage:(NSString *)toastMsg { + dispatch_async(dispatch_get_main_queue(), ^{ + UIViewController *rootViewController = self.viewController; + if (rootViewController == nil) { + // Use modern approach for iOS 13+ with multiple scenes support + if (@available(iOS 13.0, *)) { + NSSet *connectedScenes = [UIApplication sharedApplication].connectedScenes; + for (UIScene *scene in connectedScenes) { + if ([scene isKindOfClass:[UIWindowScene class]]) { + UIWindowScene *windowScene = (UIWindowScene *)scene; + for (UIWindow *window in windowScene.windows) { + if (window.isKeyWindow) { + rootViewController = window.rootViewController; + break; + } + } + if (rootViewController != nil) break; + } } - } @catch (NSException *exception) { - // Continue to next resource if fileSize is not available + } else { + // Fallback for iOS 12 and earlier + rootViewController = [[[UIApplication sharedApplication] windows] firstObject].rootViewController; } } - } - - if (fileSize > 0) { - CGFloat mb = fileSize / (1024.0 * 1024.0); - if (mb > self.maxMB) { - return NO; + if (rootViewController == nil) { + return; // Cannot show toast without a view controller } - return YES; - } - return YES; + + UILabel *toastLabel = [[UILabel alloc] init]; + toastLabel.text = toastMsg; + toastLabel.textColor = [UIColor whiteColor]; + toastLabel.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.8]; + toastLabel.textAlignment = NSTextAlignmentCenter; + toastLabel.font = [UIFont systemFontOfSize:14]; + toastLabel.numberOfLines = 0; + toastLabel.layer.cornerRadius = 8.0; + toastLabel.clipsToBounds = YES; + + CGSize maxSize = CGSizeMake(rootViewController.view.bounds.size.width - 40, CGFLOAT_MAX); + CGSize expectedSize = [toastLabel.text boundingRectWithSize:maxSize + options:NSStringDrawingUsesLineFragmentOrigin + attributes:@{NSFontAttributeName: toastLabel.font} + context:nil].size; + + CGFloat padding = 16.0; + CGFloat labelWidth = expectedSize.width + padding * 2; + CGFloat labelHeight = expectedSize.height + padding * 2; + + CGFloat x = (rootViewController.view.bounds.size.width - labelWidth) / 2; + CGFloat y = rootViewController.view.bounds.size.height - labelHeight - 100; + + toastLabel.frame = CGRectMake(x, y, labelWidth, labelHeight); + toastLabel.alpha = 0.0; + + [rootViewController.view addSubview:toastLabel]; + + [UIView animateWithDuration:0.3 animations:^{ + toastLabel.alpha = 1.0; + } completion:^(BOOL finished) { + [UIView animateWithDuration:0.3 delay:2.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ + toastLabel.alpha = 0.0; + } completion:^(BOOL finished) { + [toastLabel removeFromSuperview]; + }]; + }]; + }); } @end From a9d02d19fa981fb39aabacb89ce36afe659cdc80 Mon Sep 17 00:00:00 2001 From: Hashir Rajah Date: Mon, 1 Dec 2025 10:41:14 +0400 Subject: [PATCH 09/16] Max 5MB --- src/android/ImagePicker.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/android/ImagePicker.java b/src/android/ImagePicker.java index 99c91a19..426b0415 100644 --- a/src/android/ImagePicker.java +++ b/src/android/ImagePicker.java @@ -77,7 +77,7 @@ public boolean execute(String action, final JSONArray args, final CallbackContex } else if (ACTION_GET_PICTURES.equals(action)) { final JSONObject params = args.getJSONObject(0); this.maxImageCount = params.has("maximumImagesCount") ? params.getInt("maximumImagesCount") : 20; - this.maxFileSize = params.has("maxFileSize") ? params.getInt("maxFileSize") : 50; + this.maxFileSize = params.has("maxFileSize") ? params.getInt("maxFileSize") : 5; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && SdkExtensions.getExtensionVersion(Build.VERSION_CODES.R) >= 2) { int deviceMaxLimit = MediaStore.getPickImagesMaxLimit(); if (this.maxImageCount > deviceMaxLimit) { From 1faa1d2ac1544477a5827c5ce0a9695a1ea66e53 Mon Sep 17 00:00:00 2001 From: Hashir Rajah Date: Mon, 1 Dec 2025 10:48:37 +0400 Subject: [PATCH 10/16] Add new method showMaxFileSizeWarning --- src/android/ImagePicker.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/android/ImagePicker.java b/src/android/ImagePicker.java index 426b0415..4cce4a42 100644 --- a/src/android/ImagePicker.java +++ b/src/android/ImagePicker.java @@ -107,6 +107,7 @@ public boolean execute(String action, final JSONArray args, final CallbackContex cordova.startActivityForResult(this, imagePickerIntent, SELECT_PICTURE); this.showMaxLimitWarning(useFilePicker); + this.showMaxFileSizeWarning(); return true; } return false; @@ -421,6 +422,7 @@ private void showMaxLimitWarning(boolean useFilePicker) { } (Toast.makeText(cordova.getContext(), toastMsg, Toast.LENGTH_LONG)).show(); } + private void showMaxLimitWarning(int deviceMaxLimit) { String toastMsg = "The maximumImagesCount:" + this.maxImageCount + " is greater than the device's max limit of images that can be selected from the MediaStore: " + deviceMaxLimit + @@ -428,6 +430,12 @@ private void showMaxLimitWarning(int deviceMaxLimit) { (Toast.makeText(cordova.getContext(), toastMsg, Toast.LENGTH_LONG)).show(); } + + private void showMaxFileSizeWarning() { + String toastMsg = "You can only select Media(s) up to max limit of " + this.maxFileSize + "MB"; + cordova.getActivity().runOnUiThread(() -> (Toast.makeText(cordova.getContext(), toastMsg, Toast.LENGTH_LONG)).show()); + } + private void showMaxFileSizeExceededWarning() { String toastMsg = "Media(s) above max limit " + this.maxFileSize + "MB not selected"; cordova.getActivity().runOnUiThread(() -> (Toast.makeText(cordova.getContext(), toastMsg, Toast.LENGTH_LONG)).show()); From a2370898e8a426cbd2642c73c0edfb399b5a1885 Mon Sep 17 00:00:00 2001 From: Hashir Rajah Date: Mon, 1 Dec 2025 16:05:25 +0400 Subject: [PATCH 11/16] Max file size 5 --- www/imagepicker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/imagepicker.js b/www/imagepicker.js index a99f2807..3c033212 100644 --- a/www/imagepicker.js +++ b/www/imagepicker.js @@ -59,7 +59,7 @@ ImagePicker.prototype.getPictures = function(success, fail, options) { var params = { maximumImagesCount: options.maximumImagesCount ? options.maximumImagesCount : 15, - maxFileSize: options.maxFileSize ? options.maxFileSize : 50, + maxFileSize: options.maxFileSize ? options.maxFileSize : 5, useFilePicker: options.useFilePicker ? options.useFilePicker : false, width: options.width ? options.width : 0, height: options.height ? options.height : 0, From a6ae555906302c1feabcd7fedd70c8337d960f71 Mon Sep 17 00:00:00 2001 From: Hashir Rajah Date: Tue, 2 Dec 2025 14:37:35 +0400 Subject: [PATCH 12/16] New limits --- src/android/ImagePicker.java | 25 ++++++++++++++++--------- www/imagepicker.js | 3 ++- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/android/ImagePicker.java b/src/android/ImagePicker.java index 4cce4a42..1c15689a 100644 --- a/src/android/ImagePicker.java +++ b/src/android/ImagePicker.java @@ -63,7 +63,8 @@ public class ImagePicker extends CordovaPlugin { private CallbackContext callbackContext; private int maxImageCount; - private int maxFileSize; + private int maxPhotoSize; + private int maxVideoSize; private LinearLayout layout = null; public boolean execute(String action, final JSONArray args, final CallbackContext callbackContext) throws JSONException { @@ -77,7 +78,8 @@ public boolean execute(String action, final JSONArray args, final CallbackContex } else if (ACTION_GET_PICTURES.equals(action)) { final JSONObject params = args.getJSONObject(0); this.maxImageCount = params.has("maximumImagesCount") ? params.getInt("maximumImagesCount") : 20; - this.maxFileSize = params.has("maxFileSize") ? params.getInt("maxFileSize") : 5; + this.maxPhotoSize = params.has("maxPhotoSize") ? params.getInt("maxPhotoSize") : 10; + this.maxVideoSize = params.has("maxVideoSize") ? params.getInt("maxVideoSize") : 5; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && SdkExtensions.getExtensionVersion(Build.VERSION_CODES.R) >= 2) { int deviceMaxLimit = MediaStore.getPickImagesMaxLimit(); if (this.maxImageCount > deviceMaxLimit) { @@ -107,7 +109,7 @@ public boolean execute(String action, final JSONArray args, final CallbackContex cordova.startActivityForResult(this, imagePickerIntent, SELECT_PICTURE); this.showMaxLimitWarning(useFilePicker); - this.showMaxFileSizeWarning(); + this.showMaxFileSizeWarning(allowVideo); return true; } return false; @@ -127,11 +129,13 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { String path; if (resultCode == Activity.RESULT_OK && data != null) { ArrayList fileURIs = new ArrayList<>(); + boolean isVideo = false; + int maxFileSize = this.maxPhotoSize; if (requestCode == SELECT_PICTURE) { if (data.getData() != null) { uri = data.getData(); double size = this.getFileSizeFromUri(uri); - if (size > this.maxFileSize) { + if (size > maxFileSize) { sizeLimitExceeded = true; } else { path = ImagePicker.this.copyFileToInternalStorage(uri, ""); @@ -146,7 +150,7 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { for (int i = 0; i < clip.getItemCount(); i++) { uri = clip.getItemAt(i).getUri(); double size = this.getFileSizeFromUri(uri); - if (size > this.maxFileSize) { + if (size > maxFileSize) { sizeLimitExceeded = true; if (i + 1 >= ImagePicker.this.maxImageCount) { break; @@ -168,7 +172,7 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { } for (int i=0; i < fileURIs.size(); i++) { String fileUri = fileURIs.get(i); - boolean isVideo = this.isVideo(fileUri); + isVideo = this.isVideo(fileUri); BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; Uri ImageUri = Uri.parse(fileUri); @@ -431,13 +435,16 @@ private void showMaxLimitWarning(int deviceMaxLimit) { (Toast.makeText(cordova.getContext(), toastMsg, Toast.LENGTH_LONG)).show(); } - private void showMaxFileSizeWarning() { - String toastMsg = "You can only select Media(s) up to max limit of " + this.maxFileSize + "MB"; + private void showMaxFileSizeWarning(boolean allowVideo) { + String toastMsg = "Image size limit is " + this.maxPhotoSize + "MB"; + if (allowVideo) { + toastMsg += "\nVideo size limit is " + this.maxVideoSize + "MB"; + } cordova.getActivity().runOnUiThread(() -> (Toast.makeText(cordova.getContext(), toastMsg, Toast.LENGTH_LONG)).show()); } private void showMaxFileSizeExceededWarning() { - String toastMsg = "Media(s) above max limit " + this.maxFileSize + "MB not selected"; + String toastMsg = "Media(s) above max limit not selected"; cordova.getActivity().runOnUiThread(() -> (Toast.makeText(cordova.getContext(), toastMsg, Toast.LENGTH_LONG)).show()); } } diff --git a/www/imagepicker.js b/www/imagepicker.js index 3c033212..faa2f00c 100644 --- a/www/imagepicker.js +++ b/www/imagepicker.js @@ -59,7 +59,8 @@ ImagePicker.prototype.getPictures = function(success, fail, options) { var params = { maximumImagesCount: options.maximumImagesCount ? options.maximumImagesCount : 15, - maxFileSize: options.maxFileSize ? options.maxFileSize : 5, + maxPhotoSize: options.maxPhotoSize ? options.maxPhotoSize : 10, + maxVideoSize: options.maxVideoSize ? options.maxVideoSize : 5, useFilePicker: options.useFilePicker ? options.useFilePicker : false, width: options.width ? options.width : 0, height: options.height ? options.height : 0, From e038c88068ad523455bf2a5d72b7fcd9d9ecf5bd Mon Sep 17 00:00:00 2001 From: Hashir Rajah Date: Tue, 2 Dec 2025 14:52:05 +0400 Subject: [PATCH 13/16] Java error --- src/android/ImagePicker.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/android/ImagePicker.java b/src/android/ImagePicker.java index 1c15689a..b7cd7f0e 100644 --- a/src/android/ImagePicker.java +++ b/src/android/ImagePicker.java @@ -436,10 +436,8 @@ private void showMaxLimitWarning(int deviceMaxLimit) { } private void showMaxFileSizeWarning(boolean allowVideo) { - String toastMsg = "Image size limit is " + this.maxPhotoSize + "MB"; - if (allowVideo) { - toastMsg += "\nVideo size limit is " + this.maxVideoSize + "MB"; - } + String toastMsg = allowVideo ? "Image size limit is " + this.maxPhotoSize + "MB\nVideo size limit is " + this.maxVideoSize + "MB" + : "Image size limit is " + this.maxPhotoSize + "MB"; cordova.getActivity().runOnUiThread(() -> (Toast.makeText(cordova.getContext(), toastMsg, Toast.LENGTH_LONG)).show()); } From cbf37b7a4a0564840fc9c95ffabcb2e695ad7b99 Mon Sep 17 00:00:00 2001 From: Hashir Rajah Date: Tue, 2 Dec 2025 15:14:05 +0400 Subject: [PATCH 14/16] Fix --- src/android/ImagePicker.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/android/ImagePicker.java b/src/android/ImagePicker.java index b7cd7f0e..7365b0a0 100644 --- a/src/android/ImagePicker.java +++ b/src/android/ImagePicker.java @@ -134,6 +134,10 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == SELECT_PICTURE) { if (data.getData() != null) { uri = data.getData(); + isVideo = this.isVideo(uri); + if (isVideo) { + maxFileSize = this.maxVideoSize; + } double size = this.getFileSizeFromUri(uri); if (size > maxFileSize) { sizeLimitExceeded = true; @@ -149,6 +153,10 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { ClipData clip = data.getClipData(); for (int i = 0; i < clip.getItemCount(); i++) { uri = clip.getItemAt(i).getUri(); + isVideo = this.isVideo(uri); + if (isVideo) { + maxFileSize = this.maxVideoSize; + } double size = this.getFileSizeFromUri(uri); if (size > maxFileSize) { sizeLimitExceeded = true; @@ -261,6 +269,10 @@ public boolean isVideo(String filePath) { return mime != null && mime.startsWith("video/"); } + public boolean isVideo(Uri uri) { + String mime = cordova.getActivity().getContentResolver().getType(uri); + return mime != null && mime.startsWith("video/"); + } @Override public void onRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults) throws JSONException { From 2177c5f9bb84c0d4f0c17fdbf06d0e1641bec878 Mon Sep 17 00:00:00 2001 From: parveshneedhoo Date: Thu, 4 Dec 2025 12:55:09 +0400 Subject: [PATCH 15/16] check size for images also and display warning toast when opening roll --- src/ios/SOSPicker.h | 5 ++-- src/ios/SOSPicker.m | 72 +++++++++++++++++++++++++++++++++++---------- 2 files changed, 60 insertions(+), 17 deletions(-) diff --git a/src/ios/SOSPicker.h b/src/ios/SOSPicker.h index e4fe306d..3fd1171b 100644 --- a/src/ios/SOSPicker.h +++ b/src/ios/SOSPicker.h @@ -25,8 +25,9 @@ @property (nonatomic, assign) NSInteger height; @property (nonatomic, assign) NSInteger quality; @property (nonatomic, assign) NSInteger outputType; -@property (nonatomic, assign) NSInteger maxMB; -@property (nonatomic, assign) BOOL videoSizeLimitExceeded; +@property (nonatomic, assign) NSInteger maxVideoSize; +@property (nonatomic, assign) NSInteger maxPhotoSize; +@property (nonatomic, assign) BOOL mediaSizeLimitExceeded; @property (nonatomic, assign) BOOL videoExportFailed; @end diff --git a/src/ios/SOSPicker.m b/src/ios/SOSPicker.m index 99ea0ba6..9222443d 100644 --- a/src/ios/SOSPicker.m +++ b/src/ios/SOSPicker.m @@ -67,8 +67,9 @@ - (void) getPictures:(CDVInvokedUrlCommand *)command { self.width = [[options objectForKey:@"width"] integerValue]; self.height = [[options objectForKey:@"height"] integerValue]; self.quality = [[options objectForKey:@"quality"] integerValue]; - self.maxMB = [[options objectForKey:@"maxFileSize"] integerValue]; - self.videoSizeLimitExceeded = NO; + self.maxVideoSize = [[options objectForKey:@"maxVideoSize"] integerValue]; + self.maxPhotoSize = [[options objectForKey:@"maxPhotoSize"] integerValue]; + self.mediaSizeLimitExceeded = NO; self.videoExportFailed = NO; self.callbackId = command.callbackId; @@ -97,6 +98,20 @@ - (void)launchGMImagePicker:(bool)allow_video title:(NSString *)title message:(N } [self.viewController showViewController:picker sender:nil]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + NSString *toastMsg; + if (allow_video) { + toastMsg = [NSString stringWithFormat:@"Image size limit is %ldMB\nVideo size limit is %ldMB", + (long)self.maxPhotoSize, + (long)self.maxVideoSize]; + } else { + toastMsg = [NSString stringWithFormat:@"Image size limit is %ldMB", + (long)self.maxPhotoSize]; + } + [self showToastMessage:toastMsg onViewController:picker]; + }); + } @@ -216,7 +231,7 @@ - (void)assetsPickerController:(GMImagePickerController *)picker didFinishPickin } while ([fileMgr fileExistsAtPath:filePath]); if (isVideo && phAsset) { - NSInteger exceeded = [self checkVideoSize:phAsset]; + BOOL exceeded = [self checkMediaSize:phAsset]; if (exceeded) continue; @@ -254,6 +269,11 @@ - (void)assetsPickerController:(GMImagePickerController *)picker didFinishPickin continue; } + if (phAsset && phAsset.mediaType == PHAssetMediaTypeImage) { + BOOL exceeded = [self checkMediaSize:phAsset]; + if (exceeded) continue; + } + // Handle images NSData* data = nil; if (self.width == 0 && self.height == 0) { @@ -318,14 +338,14 @@ - (void)assetsPickerController:(GMImagePickerController *)picker didFinishPickin } // if videoSizeLimitExceeded is YES, display toast message - if (self.videoSizeLimitExceeded == YES && self.videoExportFailed == YES) { - NSString *toastMsg = [NSString stringWithFormat:@"Video(s) above max limit %ldMB not selected and some videos failed to be exported", (long)self.maxMB]; + if (self.mediaSizeLimitExceeded == YES && self.videoExportFailed == YES) { + NSString *toastMsg = [NSString stringWithFormat:@"Media(s) above max limit not selected and some videos failed to be exported"]; [self showToastMessage:toastMsg]; - } else if (self.videoSizeLimitExceeded == YES) { - NSString *toastMsg = [NSString stringWithFormat:@"Video(s) above max limit %ldMB not selected", (long)self.maxMB]; + } else if (self.mediaSizeLimitExceeded == YES) { + NSString *toastMsg = [NSString stringWithFormat:@"Media(s) above max limit not selected"]; [self showToastMessage:toastMsg]; } else if (self.videoExportFailed == YES) { - NSString *toastMsg = [NSString stringWithFormat:@"Some videos failed to be exported", (long)self.maxMB]; + NSString *toastMsg = [NSString stringWithFormat:@"Some videos failed to be exported"]; [self showToastMessage:toastMsg]; } @@ -334,15 +354,33 @@ - (void)assetsPickerController:(GMImagePickerController *)picker didFinishPickin } -- (BOOL)checkVideoSize:(PHAsset *) asset { +- (BOOL)checkMediaSize:(PHAsset *) asset { NSArray *resources = [PHAssetResource assetResourcesForAsset:asset]; long long fileSize = 0; + NSInteger maxSize = 0; + BOOL isVideo = (asset.mediaType == PHAssetMediaTypeVideo); + + if (isVideo) { + maxSize = self.maxVideoSize; + } else { + maxSize = self.maxPhotoSize; + } for (PHAssetResource *resource in resources) { - if (resource.type == PHAssetResourceTypeVideo || - resource.type == PHAssetResourceTypeFullSizeVideo || - resource.type == PHAssetResourceTypePairedVideo) { + PHAssetResourceType resourceType = resource.type; + BOOL matchesType = NO; + if (isVideo) { + matchesType = (resourceType == PHAssetResourceTypeVideo || + resourceType == PHAssetResourceTypeFullSizeVideo || + resourceType == PHAssetResourceTypePairedVideo); + } else { + matchesType = (resourceType == PHAssetResourceTypePhoto || + resourceType == PHAssetResourceTypeFullSizePhoto || + resourceType == PHAssetResourceTypeAlternatePhoto); + } + + if (matchesType) { @try { id fileSizeValue = [resource valueForKey:@"fileSize"]; if ([fileSizeValue isKindOfClass:[NSNumber class]]) { @@ -355,8 +393,8 @@ - (BOOL)checkVideoSize:(PHAsset *) asset { } } - if ((fileSize / (1024.0 * 1024.0)) > self.maxMB) { - self.videoSizeLimitExceeded = YES; + if ((fileSize / (1024.0 * 1024.0)) > maxSize) { + self.mediaSizeLimitExceeded = YES; return YES; } return NO; @@ -485,8 +523,12 @@ - (void) closeImagePicker:(CDVInvokedUrlCommand *)command { } - (void)showToastMessage:(NSString *)toastMsg { + [self showToastMessage:toastMsg onViewController:nil]; +} + +- (void)showToastMessage:(NSString *)toastMsg onViewController:(UIViewController *)targetViewController { dispatch_async(dispatch_get_main_queue(), ^{ - UIViewController *rootViewController = self.viewController; + UIViewController *rootViewController = targetViewController ?: self.viewController; if (rootViewController == nil) { // Use modern approach for iOS 13+ with multiple scenes support if (@available(iOS 13.0, *)) { From 7a190a7edbad298a9f07cf49574f926ad4137ceb Mon Sep 17 00:00:00 2001 From: Hashir Rajah Date: Thu, 4 Dec 2025 17:08:38 +0400 Subject: [PATCH 16/16] Change max limit images --- src/android/ImagePicker.java | 2 +- www/imagepicker.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/android/ImagePicker.java b/src/android/ImagePicker.java index 7365b0a0..376cd34d 100644 --- a/src/android/ImagePicker.java +++ b/src/android/ImagePicker.java @@ -78,7 +78,7 @@ public boolean execute(String action, final JSONArray args, final CallbackContex } else if (ACTION_GET_PICTURES.equals(action)) { final JSONObject params = args.getJSONObject(0); this.maxImageCount = params.has("maximumImagesCount") ? params.getInt("maximumImagesCount") : 20; - this.maxPhotoSize = params.has("maxPhotoSize") ? params.getInt("maxPhotoSize") : 10; + this.maxPhotoSize = params.has("maxPhotoSize") ? params.getInt("maxPhotoSize") : 15; this.maxVideoSize = params.has("maxVideoSize") ? params.getInt("maxVideoSize") : 5; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && SdkExtensions.getExtensionVersion(Build.VERSION_CODES.R) >= 2) { int deviceMaxLimit = MediaStore.getPickImagesMaxLimit(); diff --git a/www/imagepicker.js b/www/imagepicker.js index faa2f00c..664469e0 100644 --- a/www/imagepicker.js +++ b/www/imagepicker.js @@ -59,7 +59,7 @@ ImagePicker.prototype.getPictures = function(success, fail, options) { var params = { maximumImagesCount: options.maximumImagesCount ? options.maximumImagesCount : 15, - maxPhotoSize: options.maxPhotoSize ? options.maxPhotoSize : 10, + maxPhotoSize: options.maxPhotoSize ? options.maxPhotoSize : 15, maxVideoSize: options.maxVideoSize ? options.maxVideoSize : 5, useFilePicker: options.useFilePicker ? options.useFilePicker : false, width: options.width ? options.width : 0,