diff --git a/CHANGELOG.md b/CHANGELOG.md index 6982a27d..9af05f29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,9 +8,11 @@ To know more about breaking changes, see the [Migration Guide][]. ## Unreleased -*None.* +### Features -## 3.6.4 +- Add `cancelToken` parameter to `AssetEntity.loadFile`. +- Add `cancelAllRequest` method to `PhotoManager`. +- The `getFile` and `getOriginBytes` methods are public. ### Fixes diff --git a/README-ZH.md b/README-ZH.md index 9a63a7d5..f2f5fcd9 100644 --- a/README-ZH.md +++ b/README-ZH.md @@ -426,6 +426,25 @@ iCloud 文件只能在设备上的 Apple ID 正常登录时获取。 当账号要求重新输入密码验证时,未缓存在本地的 iCloud 文件将无法访问, 此时相关方法会抛出 `CloudPhotoLibraryErrorDomain` 错误。 +**取消加载** (Since 3.7.0) + +上述的 `AssetEntity` 方法均添加了 `cancelToken` 参数, +可以用于取消加载过程。 + +其他方法如果也添加了 `cancelToken` 参数,同样可以用于取消加载过程。 + +```dart +final PMCancelToken cancelToken = PMCancelToken(); +final File? file = await yourAssetEntity.loadFile(cancelToken: cancelToken); +await cancelToken.cancel(); +``` + +`PhotoManager` 也有一个方法可以取消所有加载: + +```dart +await PhotoManager.cancelAllRequest(); +``` + #### 展示资源 从 v3.0.0 开始,插件不再提供任何 UI 组件。 diff --git a/README.md b/README.md index 1dc4fcf4..17e16e91 100644 --- a/README.md +++ b/README.md @@ -462,6 +462,23 @@ When the account requires to re-enter the password to verify, iCloud files that locally available are not allowed to be fetched. The photo library will throws `CloudPhotoLibraryErrorDomain` in this circumstance. +**Cancel loading** (Since 3.7.0) + +The methods in `AssetEntity` to add `cancelToken` parameter, +which can be used to cancel the loading process. + +```dart +final PMCancelToken cancelToken = PMCancelToken(); +final File? file = await yourAssetEntity.loadFile(cancelToken: cancelToken); +await cancelToken.cancel(); +``` + +The `PhotoManager` also has a method to cancel all loading: + +```dart +await PhotoManager.cancelAllRequest(); +``` + #### Display assets > Starts from v3.0.0, `AssetEntityImage` and `AssetEntityImageProvider` diff --git a/ios/Classes/PMPlugin.h b/ios/Classes/PMPlugin.h index 246c538f..5002c848 100644 --- a/ios/Classes/PMPlugin.h +++ b/ios/Classes/PMPlugin.h @@ -5,7 +5,7 @@ @class PMManager; @class PMNotificationManager; -@interface PMPlugin : NSObject +@interface PMPlugin : NSObject @property(nonatomic, strong) PMManager *manager; @property(nonatomic, strong) PMNotificationManager *notificationManager; - (void)registerPlugin:(NSObject *)registrar; diff --git a/ios/Classes/PMPlugin.m b/ios/Classes/PMPlugin.m index 60d08c67..81473397 100644 --- a/ios/Classes/PMPlugin.m +++ b/ios/Classes/PMPlugin.m @@ -4,7 +4,7 @@ #import "PMLogUtils.h" #import "PMManager.h" #import "PMNotificationManager.h" -#import "ResultHandler.h" +#import "PMResultHandler.h" #import "PMThumbLoadOption.h" #import "PMProgressHandler.h" #import "PMConverter.h" @@ -19,6 +19,12 @@ @implementation PMPlugin { BOOL isDetach; } + ++ (void)registerWithRegistrar:(nonnull NSObject *)registrar { + PMPlugin *plugin = [PMPlugin new]; + [plugin registerPlugin:registrar]; +} + - (void)registerPlugin:(NSObject *)registrar { privateRegistrar = registrar; [self initNotificationManager:registrar]; @@ -29,8 +35,9 @@ - (void)registerPlugin:(NSObject *)registrar { manager.converter = [PMConverter new]; [self setManager:manager]; + __block PMPlugin *weakSelf = self; // avoid retain cycle [channel setMethodCallHandler:^(FlutterMethodCall *call, FlutterResult result) { - [self onMethodCall:call result:result]; + [weakSelf onMethodCall:call result:result]; }]; } @@ -119,7 +126,7 @@ - (void)onMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { return; } - ResultHandler *handler = [ResultHandler handlerWithCall:call result:result]; + PMResultHandler *handler = [PMResultHandler handlerWithCall:call result:result]; if ([self isNotNeedPermissionMethod:call.method]) { [self handleNotNeedPermissionMethod:handler]; @@ -130,7 +137,7 @@ - (void)onMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { } } -- (void)handleNotNeedPermissionMethod:(ResultHandler *)handler { +- (void)handleNotNeedPermissionMethod:(PMResultHandler *)handler { FlutterMethodCall *call = handler.call; NSString *method = call.method; PMManager *manager = self.manager; @@ -154,9 +161,9 @@ - (void)handleNotNeedPermissionMethod:(ResultHandler *)handler { } } -- (void)getPermissionState:(ResultHandler *)handler { +- (void)getPermissionState:(PMResultHandler *)handler { int requestAccessLevel = [handler.call.arguments[@"iosAccessLevel"] intValue]; -#if __IPHONE_14_0 +#if TARGET_OS_IOS if (@available(iOS 14, *)) { PHAuthorizationStatus result = [PHPhotoLibrary authorizationStatusForAccessLevel: requestAccessLevel]; [handler reply: @(result)]; @@ -165,12 +172,17 @@ - (void)getPermissionState:(ResultHandler *)handler { [handler reply:@(status)]; } #else - PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus]; - [handler reply:@(status)]; + if (@available(macOS 11.0, *)) { + PHAuthorizationStatus result = [PHPhotoLibrary authorizationStatusForAccessLevel: requestAccessLevel]; + [handler reply: @(result)]; + } else { + PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus]; + [handler reply:@(status)]; + } #endif } -- (void)handleAboutPermissionMethod:(ResultHandler *)handler { +- (void)handleAboutPermissionMethod:(PMResultHandler *)handler { FlutterMethodCall *call = handler.call; PMManager *manager = self.manager; @@ -182,7 +194,7 @@ - (void)handleAboutPermissionMethod:(ResultHandler *)handler { } } -- (void)replyPermssionResult:(ResultHandler *)handler status:(PHAuthorizationStatus)status isOnlyAdd:(BOOL)isOnlyAdd { +- (void)replyPermssionResult:(PMResultHandler *)handler status:(PHAuthorizationStatus)status isOnlyAdd:(BOOL)isOnlyAdd { [handler reply:@(status)]; } @@ -207,7 +219,7 @@ - (UIViewController *)getCurrentViewController { #endif - (void)handlePermission:(PMManager *)manager - handler:(ResultHandler *)handler + handler:(PMResultHandler *)handler requestAccessLevel:(int)requestAccessLevel { #if __IPHONE_14_0 if (@available(iOS 14, *)) { @@ -245,7 +257,7 @@ - (void)requestPermissionStatus:(int)requestAccessLevel #endif } -- (void)presentLimited:(ResultHandler *)handler { +- (void)presentLimited:(PMResultHandler *)handler { #if __IPHONE_14_0 if (@available(iOS 14, *)) { UIViewController *controller = [self getCurrentViewController]; @@ -282,7 +294,7 @@ - (void)presentLimited:(ResultHandler *)handler { #if TARGET_OS_OSX - (void)handlePermission:(PMManager *)manager - handler:(ResultHandler*)handler + handler:(PMResultHandler*)handler requestAccessLevel:(int)requestAccessLevel { #if __MAC_11_0 if (@available(macOS 11.0, *)) { @@ -320,13 +332,13 @@ - (void)requestPermissionStatus:(int)requestAccessLevel #endif } -- (void)presentLimited:(ResultHandler*)handler { +- (void)presentLimited:(PMResultHandler*)handler { [handler replyError:@"Not supported on macOS."]; } #endif -- (void)runInBackground:(dispatch_block_t)block withHandler:(ResultHandler *)handler { +- (void)runInBackground:(dispatch_block_t)block withHandler:(PMResultHandler *)handler { dispatch_qos_class_t priority = [self getQosPriorityForMethod:handler.call.method]; dispatch_async(dispatch_get_global_queue(priority, 0), block); } @@ -334,7 +346,9 @@ - (void)runInBackground:(dispatch_block_t)block withHandler:(ResultHandler *)han - (dispatch_qos_class_t)getQosPriorityForMethod:(NSString *)method { if ([method isEqualToString:@"getThumb"] || [method isEqualToString:@"assetExists"] || - [method isEqualToString:@"isLocallyAvailable"]) { + [method isEqualToString:@"isLocallyAvailable"] || + [method isEqualToString:@"cancelRequestWithCancelToken"] || + [method isEqualToString:@"cancelAllRequest"]) { return QOS_CLASS_USER_INTERACTIVE; } @@ -367,7 +381,7 @@ - (dispatch_qos_class_t)getQosPriorityForMethod:(NSString *)method { return QOS_CLASS_DEFAULT; } -- (void)onAuth:(ResultHandler *)handler { +- (void)onAuth:(PMResultHandler *)handler { PMManager *manager = self.manager; __block PMNotificationManager *notificationManager = self.notificationManager; @@ -381,7 +395,7 @@ - (void)onAuth:(ResultHandler *)handler { } withHandler:handler]; } -- (void)handleMethodResultHandler:(ResultHandler *)handler manager:(PMManager *)manager notificationManager:(PMNotificationManager *)notificationManager { +- (void)handleMethodResultHandler:(PMResultHandler *)handler manager:(PMManager *)manager notificationManager:(PMNotificationManager *)notificationManager { FlutterMethodCall *call = handler.call; if ([call.method isEqualToString:@"getAssetPathList"]) { @@ -669,6 +683,13 @@ - (void)handleMethodResultHandler:(ResultHandler *)handler manager:(PMManager *) } else if ([@"cancelCacheRequests" isEqualToString:call.method]) { [manager cancelCacheRequests]; [handler reply:@YES]; + } else if ([@"cancelRequestWithCancelToken" isEqualToString:call.method]) { + NSString *cancelToken = call.arguments[@"cancelToken"]; + [manager cancelRequestWithCancelToken:cancelToken]; + [handler reply:@YES]; + } else if ([@"cancelAllRequest" isEqualToString:call.method]) { + [manager cancelAllRequest]; + [handler reply:@YES]; } else { [handler notImplemented]; } @@ -692,7 +713,7 @@ - (PMProgressHandler *)getProgressHandlerFromDict:(NSDictionary *)dict { return handler; } -- (void)createFolder:(FlutterMethodCall *)call manager:(PMManager *)manager handler:(ResultHandler *)handler { +- (void)createFolder:(FlutterMethodCall *)call manager:(PMManager *)manager handler:(PMResultHandler *)handler { NSString *name = call.arguments[@"name"]; BOOL isRoot = [call.arguments[@"isRoot"] boolValue]; NSString *parentId = call.arguments[@"folderId"]; @@ -709,7 +730,7 @@ - (void)createFolder:(FlutterMethodCall *)call manager:(PMManager *)manager hand }]; } -- (void)createAlbum:(FlutterMethodCall *)call manager:(PMManager *)manager handler:(ResultHandler *)handler { +- (void)createAlbum:(FlutterMethodCall *)call manager:(PMManager *)manager handler:(PMResultHandler *)handler { NSString *name = call.arguments[@"name"]; BOOL isRoot = [call.arguments[@"isRoot"] boolValue]; NSString *parentId = call.arguments[@"folderId"]; diff --git a/ios/Classes/ResultHandler.h b/ios/Classes/PMResultHandler.h similarity index 60% rename from ios/Classes/ResultHandler.h rename to ios/Classes/PMResultHandler.h index 532a9ce6..9d1a1036 100644 --- a/ios/Classes/ResultHandler.h +++ b/ios/Classes/PMResultHandler.h @@ -1,10 +1,9 @@ #import #import "PMImport.h" -#import "PMResultHandler.h" -@interface ResultHandler : NSObject +@interface PMResultHandler : NSObject -@property (nonatomic, strong) FlutterMethodCall* call; +@property(nonatomic, strong) FlutterMethodCall* call; @property(nonatomic, strong) FlutterResult result; - (instancetype)initWithResult:(FlutterResult)result; @@ -13,5 +12,14 @@ + (instancetype)handlerWithCall:(FlutterMethodCall *)call result:(FlutterResult)result; +- (void)replyError:(NSObject *)value; + +- (void)reply:(id)obj; + +- (void)notImplemented; + +- (BOOL)isReplied; + +- (NSString *)getCancelToken; @end diff --git a/ios/Classes/ResultHandler.m b/ios/Classes/PMResultHandler.m similarity index 94% rename from ios/Classes/ResultHandler.m rename to ios/Classes/PMResultHandler.m index 95ea6697..631107e2 100644 --- a/ios/Classes/ResultHandler.m +++ b/ios/Classes/PMResultHandler.m @@ -1,8 +1,9 @@ -#import "ResultHandler.h" +#import "PMResultHandler.h" -@implementation ResultHandler { +@implementation PMResultHandler { BOOL isReply; } + - (instancetype)initWithResult:(FlutterResult)result { self = [super init]; if (self) { @@ -27,7 +28,6 @@ + (instancetype)handlerWithCall:(FlutterMethodCall *)call result:(FlutterResult) return [[self alloc] initWithCall:call result:result]; } - - (void)reply:(id)obj { if (isReply) { return; @@ -63,6 +63,7 @@ - (void)replyError:(NSObject *)value { NSString *message = [NSString stringWithFormat:@"%@", [value description]]; flutterError = [FlutterError errorWithCode:code message:message details:nil]; } + if ([NSThread isMainThread]) { self.result(flutterError); } else { @@ -80,10 +81,14 @@ - (void)notImplemented { dispatch_async(dispatch_get_main_queue(), ^{ self.result(FlutterMethodNotImplemented); }); - } - (BOOL)isReplied { return isReply; } + +- (NSString *)getCancelToken { + return self.call.arguments[@"cancelToken"]; +} + @end diff --git a/ios/Classes/core/PHAssetResource+PM_COMMON.m b/ios/Classes/core/PHAssetResource+PM_COMMON.m index adddf792..ca3051f4 100644 --- a/ios/Classes/core/PHAssetResource+PM_COMMON.m +++ b/ios/Classes/core/PHAssetResource+PM_COMMON.m @@ -40,10 +40,14 @@ - (bool)isImageOrVideo { - (bool)isValid { bool isResource = self.type != PHAssetResourceTypeAdjustmentData; -#if __IPHONE_17_0 +#if TARGET_OS_IOS if (@available(iOS 17.0, *)) { isResource = isResource && self.type != PHAssetResourceTypePhotoProxy; } +#elif TARGET_OS_OSX + if (@available(macOS 14.0, *)) { + isResource = isResource && self.type != PHAssetResourceTypePhotoProxy; + } #endif return isResource; } diff --git a/ios/Classes/core/PMManager.h b/ios/Classes/core/PMManager.h index ae9021e2..039ba679 100644 --- a/ios/Classes/core/PMManager.h +++ b/ios/Classes/core/PMManager.h @@ -7,14 +7,13 @@ typedef void (^ChangeIds)(NSArray *); @class PMAssetPathEntity; @class PMAssetEntity; -@class ResultHandler; +@class PMResultHandler; @class PMFilterOption; @class PMFilterOptionGroup; @class PMThumbLoadOption; @class PMPathFilterOption; #import "PMProgressHandlerProtocol.h" -#import "PMResultHandler.h" #import "PMConvertProtocol.h" #define PM_IMAGE_CACHE_PATH @".image" @@ -30,7 +29,7 @@ typedef void (^AssetBlockResult)(PMAssetEntity *, NSObject *); @property(nonatomic, strong) NSObject *converter; -+ (void)openSetting:(NSObject*)result; ++ (void)openSetting:(PMResultHandler*)result; - (NSArray *)getAssetPathList:(int)type hasAll:(BOOL)hasAll onlyAll:(BOOL)onlyAll option:(NSObject *)option pathFilterOption:(PMPathFilterOption *)pathFilterOption; @@ -46,13 +45,13 @@ typedef void (^AssetBlockResult)(PMAssetEntity *, NSObject *); - (void)clearCache; -- (void)getThumbWithId:(NSString *)assetId option:(PMThumbLoadOption *)option resultHandler:(NSObject *)handler progressHandler:(NSObject *)progressHandler; +- (void)getThumbWithId:(NSString *)assetId option:(PMThumbLoadOption *)option resultHandler:(PMResultHandler *)handler progressHandler:(NSObject *)progressHandler; - (void)getFullSizeFileWithId:(NSString *)assetId isOrigin:(BOOL)isOrigin subtype:(int)subtype fileType:(AVFileType)fileType - resultHandler:(NSObject *)handler + resultHandler:(PMResultHandler *)handler progressHandler:(NSObject *)progressHandler; - (PMAssetPathEntity *)fetchPathProperties:(NSString *)id type:(int)type filterOption:(NSObject *)filterOption; @@ -90,7 +89,7 @@ typedef void (^AssetBlockResult)(PMAssetEntity *, NSObject *); - (void)getDurationWithOptions:(NSString *)assetId subtype:(int)subtype - resultHandler:(NSObject *)handler; + resultHandler:(PMResultHandler *)handler; - (NSString*)getTitleAsyncWithAssetId:(NSString *)assetId subtype:(int)subtype @@ -100,7 +99,7 @@ typedef void (^AssetBlockResult)(PMAssetEntity *, NSObject *); - (NSString*)getMimeTypeAsyncWithAssetId: (NSString *) assetId; - (void)getMediaUrl:(NSString *)assetId - resultHandler:(NSObject *)handler + resultHandler:(PMResultHandler *)handler progressHandler:(NSObject *)progressHandler; - (NSArray *)getSubPathWithId:(NSString *)id type:(int)type albumType:(int)albumType option:(NSObject *)option; @@ -129,4 +128,9 @@ typedef void (^AssetBlockResult)(PMAssetEntity *, NSObject *); - (NSArray*) getAssetsWithType:(int)type option:(NSObject *)option start:(int)start end:(int)end; +// cancelRequestWithCancelToken +- (void)cancelRequestWithCancelToken:(NSString *)cancelToken; + +// cancelAllRequest +- (void)cancelAllRequest; @end diff --git a/ios/Classes/core/PMManager.m b/ios/Classes/core/PMManager.m index 9612f947..cfccf3f0 100644 --- a/ios/Classes/core/PMManager.m +++ b/ios/Classes/core/PMManager.m @@ -10,17 +10,22 @@ #import "PMManager.h" #import "PMMD5Utils.h" #import "PMPathFilterOption.h" +#import "PMResultHandler.h" @implementation PMManager { PMCacheContainer *cacheContainer; PHCachingImageManager *__cachingManager; + + // dict, key: cancelToken, value: PHImageRequestID + NSMutableDictionary *requestIdMap; } - (instancetype)init { self = [super init]; if (self) { cacheContainer = [PMCacheContainer new]; + requestIdMap = [NSMutableDictionary new]; } return self; } @@ -115,7 +120,7 @@ - (void)logCollections:(PHFetchResult *)collections option:(PHFetchOptions *)opt for (PHCollection *phCollection in collections) { if ([phCollection isKindOfClass:[PHAssetCollection class]]) { PHAssetCollection *collection = (PHAssetCollection *) phCollection; - PHFetchResult *result = [PHAsset fetchKeyAssetsInAssetCollection:collection options:option]; + PHFetchResult *result = [PHAsset fetchAssetsInAssetCollection:collection options:option]; NSLog(@"collection name = %@, count = %lu", collection.localizedTitle, (unsigned long)result.count); } else { NSLog(@"collection name = %@", phCollection.localizedTitle); @@ -424,7 +429,7 @@ - (void)clearCache { [cacheContainer clearCache]; } -- (void)getThumbWithId:(NSString *)assetId option:(PMThumbLoadOption *)option resultHandler:(NSObject *)handler progressHandler:(NSObject *)progressHandler { +- (void)getThumbWithId:(NSString *)assetId option:(PMThumbLoadOption *)option resultHandler:(PMResultHandler *)handler progressHandler:(NSObject *)progressHandler { PMAssetEntity *entity = [self getAssetEntity:assetId]; if (entity && entity.phAsset) { PHAsset *asset = entity.phAsset; @@ -434,7 +439,7 @@ - (void)getThumbWithId:(NSString *)assetId option:(PMThumbLoadOption *)option re } } -- (void)fetchThumb:(PHAsset *)asset option:(PMThumbLoadOption *)option resultHandler:(NSObject *)handler progressHandler:(NSObject *)progressHandler { +- (void)fetchThumb:(PHAsset *)asset option:(PMThumbLoadOption *)option resultHandler:(PMResultHandler *)handler progressHandler:(NSObject *)progressHandler { PHImageRequestOptions *requestOptions = [PHImageRequestOptions new]; requestOptions.deliveryMode = option.deliveryMode; requestOptions.resizeMode = option.resizeMode; @@ -456,7 +461,7 @@ - (void)fetchThumb:(PHAsset *)asset option:(PMThumbLoadOption *)option resultHan int width = option.width; int height = option.height; - [self.cachingManager requestImageForAsset:asset + PHImageRequestID requestId = [self.cachingManager requestImageForAsset:asset targetSize:CGSizeMake(width, height) contentMode:option.contentMode options:requestOptions @@ -465,15 +470,24 @@ - (void)fetchThumb:(PHAsset *)asset option:(PMThumbLoadOption *)option resultHan return; } + PHImageRequestID currentReqID = [[info objectForKey:PHImageResultRequestIDKey] intValue]; + if (currentReqID == PHInvalidImageRequestID) { + [self handleCancelRequest:handler progressHandler:progressHandler]; + [self removeRequstIdWithCancelToken:[handler getCancelToken]]; + return; + } + NSObject *error = info[PHImageErrorKey]; if (error) { [handler replyError:error]; [self notifyProgress:progressHandler progress:lastProgress state:PMProgressStateFailed]; + [self removeRequstIdWithCancelToken:[handler getCancelToken]]; return; } BOOL downloadFinished = [PMManager isDownloadFinish:info]; if (!downloadFinished) { + [self removeRequstIdWithCancelToken:[handler getCancelToken]]; return; } @@ -489,13 +503,14 @@ - (void)fetchThumb:(PHAsset *)asset option:(PMThumbLoadOption *)option resultHan }]; + [self addRequstId:[handler getCancelToken] requestId:requestId]; } - (void)getFullSizeFileWithId:(NSString *)assetId isOrigin:(BOOL)isOrigin subtype:(int)subtype fileType:(AVFileType)fileType - resultHandler:(NSObject *)handler + resultHandler:(PMResultHandler *)handler progressHandler:(NSObject *)progressHandler { PMAssetEntity *entity = [self getAssetEntity:assetId]; if (entity && entity.phAsset) { @@ -531,7 +546,7 @@ - (void)getFullSizeFileWithId:(NSString *)assetId } - (void)fetchLivePhotosFile:(PHAsset *)asset - handler:(NSObject *)handler + handler:(PMResultHandler *)handler progressHandler:(NSObject *)progressHandler withScheme:(BOOL)withScheme fileType:(AVFileType)fileType { @@ -557,7 +572,7 @@ - (void)fetchLivePhotosFile:(PHAsset *)asset } - (void)fetchOriginVideoFile:(PHAsset *)asset - handler:(NSObject *)handler + handler:(PMResultHandler *)handler progressHandler:(NSObject *)progressHandler fileType:(AVFileType)fileType { PHAssetResource *resource = [asset getCurrentResource]; @@ -581,11 +596,12 @@ - (void)fetchOriginVideoFile:(PHAsset *)asset } - (void)fetchFullSizeVideo:(PHAsset *)asset - handler:(NSObject *)handler + handler:(PMResultHandler *)handler progressHandler:(NSObject *)progressHandler withScheme:(BOOL)withScheme fileType:(AVFileType)fileType { [self exportAssetToFile:asset + resultHandler:handler progressHandler:progressHandler withScheme:withScheme fileType:fileType @@ -669,7 +685,7 @@ - (void)fetchVideoResourceToFile:(PHAsset *)asset }]; PHAssetResourceManager *resourceManager = PHAssetResourceManager.defaultManager; - __block NSURL *fileUrl = [NSURL fileURLWithPath:path]; + NSURL *fileUrl = [NSURL fileURLWithPath:path]; [resourceManager writeDataForAssetResource:resource toFile:fileUrl options:options @@ -713,6 +729,7 @@ - (void)fetchVideoResourceToFile:(PHAsset *)asset } - (void)exportAssetToFile:(PHAsset *)asset + resultHandler:(PMResultHandler *)handler progressHandler:(NSObject *)progressHandler withScheme:(BOOL)withScheme fileType:(AVFileType)fileType @@ -746,7 +763,7 @@ - (void)exportAssetToFile:(PHAsset *)asset } }]; - [self.cachingManager + PHImageRequestID requestId = [self.cachingManager requestAVAssetForVideo:asset options:options resultHandler:^(AVAsset *_Nullable asset, AVAudioMix *_Nullable audioMix, NSDictionary *_Nullable info) { @@ -754,11 +771,13 @@ - (void)exportAssetToFile:(PHAsset *)asset if (error) { [self notifyProgress:progressHandler progress:lastProgress state:PMProgressStateFailed]; block(nil, error); + [self removeRequstIdWithCancelToken:[handler getCancelToken]]; return; } BOOL downloadFinished = [PMManager isDownloadFinish:info]; if (!downloadFinished) { + [self removeRequstIdWithCancelToken:[handler getCancelToken]]; return; } @@ -804,6 +823,7 @@ - (void)exportAssetToFile:(PHAsset *)asset block(path, nil); } [self notifySuccess:progressHandler]; + [self removeRequstIdWithCancelToken:[handler getCancelToken]]; return; } @@ -822,8 +842,11 @@ - (void)exportAssetToFile:(PHAsset *)asset } else { block(nil, error); } + [self removeRequstIdWithCancelToken:[handler getCancelToken]]; }]; }]; + + [self addRequstId:[handler getCancelToken] requestId:requestId]; } - (void)exportAVAssetToFile:(AVAsset *)asset @@ -966,7 +989,7 @@ - (BOOL)isImage:(PHAssetResource *)resource { return resource.type == PHAssetResourceTypePhoto || resource.type == PHAssetResourceTypeFullSizePhoto; } -- (void)fetchOriginImageFile:(PHAsset *)asset resultHandler:(NSObject *)handler progressHandler:(NSObject *)progressHandler { +- (void)fetchOriginImageFile:(PHAsset *)asset resultHandler:(PMResultHandler *)handler progressHandler:(NSObject *)progressHandler { PHAssetResource *imageResource = [asset getCurrentResource]; if (!imageResource) { [handler replyError:[NSString stringWithFormat:@"Asset %@ does not have available resources.", asset.localIdentifier]]; @@ -1013,7 +1036,7 @@ - (void)fetchOriginImageFile:(PHAsset *)asset resultHandler:(NSObject *)handler + resultHandler:(PMResultHandler *)handler progressHandler:(NSObject *)progressHandler { PHImageRequestOptions *options = [PHImageRequestOptions new]; [options setDeliveryMode:PHImageRequestOptionsDeliveryModeOpportunistic]; @@ -1036,7 +1059,7 @@ - (void)fetchFullSizeImageFile:(PHAsset *)asset } }]; - [self.cachingManager requestImageForAsset:asset + PHImageRequestID requestId = [self.cachingManager requestImageForAsset:asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeDefault options:options @@ -1045,15 +1068,26 @@ - (void)fetchFullSizeImageFile:(PHAsset *)asset return; } + PHImageRequestID currentReqID = [[info objectForKey:PHImageResultRequestIDKey] intValue]; + + if (currentReqID == PHInvalidImageRequestID) { + [handler replyError:@"Failed to fetch full size image."]; + [self notifyProgress:progressHandler progress:lastProgress state:PMProgressStateFailed]; + [self removeRequstIdWithCancelToken:[handler getCancelToken]]; + return; + } + NSObject *error = info[PHImageErrorKey]; if (error) { [handler replyError:error]; [self notifyProgress:progressHandler progress:lastProgress state:PMProgressStateFailed]; + [self removeRequstIdWithCancelToken:[handler getCancelToken]]; return; } BOOL downloadFinished = [PMManager isDownloadFinish:info]; if (!downloadFinished) { + [self removeRequstIdWithCancelToken:[handler getCancelToken]]; return; } @@ -1066,7 +1100,10 @@ - (void)fetchFullSizeImageFile:(PHAsset *)asset [handler replyError:[NSString stringWithFormat:@"Failed to convert %@ to a JPEG file.", asset.localIdentifier]]; [self notifyProgress:progressHandler progress:lastProgress state:PMProgressStateFailed]; } + [self removeRequstIdWithCancelToken:[handler getCancelToken]]; }]; + + [self addRequstId:[handler getCancelToken] requestId:requestId]; } + (BOOL)isDownloadFinish:(NSDictionary *)info { @@ -1110,7 +1147,7 @@ - (PHFetchOptions *)getAssetOptions:(int)type filterOption:(NSObject*)result { ++ (void)openSetting:(PMResultHandler*)result { NSTask *task = [[NSTask alloc] init]; task.launchPath = @"/bin/sh"; task.arguments = @[@"-c" , @"open x-apple.systempreferences:com.apple.preference.security?Privacy_Photos"]; @@ -1122,7 +1159,7 @@ + (void)openSetting:(NSObject*)result { #if TARGET_OS_IOS -+ (void)openSetting:(NSObject*)result { ++ (void)openSetting:(PMResultHandler*)result { if (@available(iOS 10, *)) { [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString] options:[[NSDictionary alloc] init] @@ -1288,7 +1325,7 @@ - (void)saveLivePhoto:(NSString *)imagePath - (void)getDurationWithOptions:(NSString *)assetId subtype:(int)subtype - resultHandler:(NSObject *)handler { + resultHandler:(PMResultHandler *)handler { PMAssetEntity *entity = [self getAssetEntity:assetId]; if (!entity) { [handler replyError:@"Not exists."]; @@ -1346,7 +1383,7 @@ - (NSString *)getMimeTypeAsyncWithAssetId:(NSString *)assetId { } - (void)getMediaUrl:(NSString *)assetId - resultHandler:(NSObject *)handler + resultHandler:(PMResultHandler *)handler progressHandler:(NSObject *)progressHandler { PHAsset *asset = [PHAsset fetchAssetsWithLocalIdentifiers:@[assetId] options:[self singleFetchOptions]].firstObject; @@ -1760,6 +1797,44 @@ - (void)cancelCacheRequests { [self.cachingManager stopCachingImagesForAllAssets]; } +# pragma mark handle cancel request + +- (void)handleCancelRequest:(PMResultHandler *)handler + progressHandler:(NSObject *)progressHandler { + [self notifyProgress:progressHandler progress:0 state:PMProgressStateCancel]; + [handler replyError:@"Request canceled"]; +} + +- (void) addRequstId:(NSString *)cancelToken + requestId:(PHImageRequestID)requestId { + requestIdMap[cancelToken] = @(requestId); +} + +// When the request is finished +- (void) removeRequstIdWithCancelToken:(NSString *)cancelToken { + NSNumber *requestId = requestIdMap[cancelToken]; + if (requestId) { + [requestIdMap removeObjectForKey:cancelToken]; + } +} + +- (void) cancelRequestWithCancelToken:(NSString *)cancelToken { + NSNumber *requestId = requestIdMap[cancelToken]; + if (requestId) { + [self.cachingManager cancelImageRequest:requestId.intValue]; + [requestIdMap removeObjectForKey:cancelToken]; + } +} + +- (void) cancelAllRequest { + for (NSString *key in requestIdMap) { + NSNumber *requestId = requestIdMap[key]; + [self.cachingManager cancelImageRequest:requestId.intValue]; + } + [requestIdMap removeAllObjects]; +} + +# pragma mark progress - (void)notifyProgress:(NSObject *)handler progress:(double)progress state:(PMProgressState)state { if (!handler) { return; diff --git a/ios/Classes/core/PMProgressHandlerProtocol.h b/ios/Classes/core/PMProgressHandlerProtocol.h index f0079d9f..54325439 100644 --- a/ios/Classes/core/PMProgressHandlerProtocol.h +++ b/ios/Classes/core/PMProgressHandlerProtocol.h @@ -5,6 +5,7 @@ typedef enum PMProgressState{ PMProgressStateLoading = 1, PMProgressStateSuccess = 2, PMProgressStateFailed = 3, + PMProgressStateCancel = 4, } PMProgressState; diff --git a/ios/Classes/core/PMResultHandler.h b/ios/Classes/core/PMResultHandler.h deleted file mode 100644 index 3cdf1df6..00000000 --- a/ios/Classes/core/PMResultHandler.h +++ /dev/null @@ -1,13 +0,0 @@ -#import - -@protocol PMResultHandler - -- (void)replyError:(NSObject *)value; - -- (void)reply:(id)obj; - -- (void)notImplemented; - -- (BOOL)isReplied; - -@end diff --git a/lib/photo_manager.dart b/lib/photo_manager.dart index 0e98ad4c..81de61eb 100644 --- a/lib/photo_manager.dart +++ b/lib/photo_manager.dart @@ -25,6 +25,7 @@ export 'src/managers/caching_manager.dart'; export 'src/managers/notify_manager.dart'; export 'src/managers/photo_manager.dart'; +export 'src/types/cancel_token.dart'; export 'src/types/entity.dart'; export 'src/types/thumbnail.dart'; export 'src/types/types.dart'; diff --git a/lib/src/internal/constants.dart b/lib/src/internal/constants.dart index 87ca2f00..b71eed8c 100644 --- a/lib/src/internal/constants.dart +++ b/lib/src/internal/constants.dart @@ -64,6 +64,10 @@ class PMConstants { static const String mGetAssetCount = 'getAssetCount'; static const String mGetAssetsByRange = 'getAssetsByRange'; + static const String mCancelRequestWithCancelToken = + 'cancelRequestWithCancelToken'; + static const String mCancelAllRequest = 'cancelAllRequest'; + /// Constant value. static const int vDefaultThumbnailSize = 150; static const int vDefaultThumbnailQuality = 95; @@ -74,4 +78,6 @@ class PMConstants { 'ohos.permission.READ_IMAGEVIDEO', 'ohos.permission.WRITE_IMAGEVIDEO', ]; + + static const cancelTokenKey = 'cancelToken'; } diff --git a/lib/src/internal/enums.dart b/lib/src/internal/enums.dart index 3daf8d3f..be6fa5b5 100644 --- a/lib/src/internal/enums.dart +++ b/lib/src/internal/enums.dart @@ -62,7 +62,7 @@ enum OrderOptionType { /// {@template photo_manager.PMRequestState} /// Indicate the current state when an asset is loading with [PMProgressHandler]. /// {@endtemplate} -enum PMRequestState { prepare, loading, success, failed } +enum PMRequestState { prepare, loading, success, failed, cancel } /// Information about app’s authorization to access the user’s photo library. /// diff --git a/lib/src/internal/plugin.dart b/lib/src/internal/plugin.dart index 91b2a071..47acbd7e 100644 --- a/lib/src/internal/plugin.dart +++ b/lib/src/internal/plugin.dart @@ -12,6 +12,7 @@ import 'package:photo_manager/platform_utils.dart'; import '../filter/base_filter.dart'; import '../filter/classical/filter_option_group.dart'; import '../filter/path_filter.dart'; +import '../types/cancel_token.dart'; import '../types/entity.dart'; import '../types/thumbnail.dart'; import '../types/types.dart'; @@ -23,14 +24,30 @@ import 'progress_handler.dart'; PhotoManagerPlugin plugin = PhotoManagerPlugin(); mixin BasePlugin { - MethodChannel _channel = const MethodChannel(PMConstants.channelPrefix); + MethodChannel _channel = const PMMethodChannel(PMConstants.channelPrefix); final Map onlyAddPermission = { 'onlyAddPermission': true, }; } -class VerboseLogMethodChannel extends MethodChannel { +class PMMethodChannel extends MethodChannel { + const PMMethodChannel(String name) : super(name); + + @override + Future invokeMethod(String method, [dynamic arguments]) { + if (arguments is! Map) { + return super.invokeMethod(method, arguments); + } + arguments.putIfAbsent( + PMConstants.cancelTokenKey, + () => PMCancelToken().key, + ); + return super.invokeMethod(method, arguments); + } +} + +class VerboseLogMethodChannel extends PMMethodChannel { VerboseLogMethodChannel({ required String name, required this.logFilePath, @@ -129,7 +146,7 @@ class PhotoManagerPlugin with BasePlugin, IosPlugin, AndroidPlugin, OhosPlugin { logFilePath: logPath, ); } else { - _channel = const MethodChannel(PMConstants.channelPrefix); + _channel = const PMMethodChannel(PMConstants.channelPrefix); } } @@ -240,17 +257,28 @@ class PhotoManagerPlugin with BasePlugin, IosPlugin, AndroidPlugin, OhosPlugin { } } + void _setCancelToken( + Map params, + PMCancelToken? cancelToken, + ) { + if (cancelToken != null) { + params[PMConstants.cancelTokenKey] = cancelToken.key; + } + } + /// Get thumbnail of asset id. Future getThumbnail({ required String id, required ThumbnailOption option, PMProgressHandler? progressHandler, + PMCancelToken? cancelToken, }) { final Map params = { 'id': id, 'option': option.toMap(), }; _injectProgressHandlerParams(params, progressHandler); + _setCancelToken(params, cancelToken); return _channel.invokeMethod(PMConstants.mGetThumb, params); } @@ -258,12 +286,14 @@ class PhotoManagerPlugin with BasePlugin, IosPlugin, AndroidPlugin, OhosPlugin { String id, { PMProgressHandler? progressHandler, PMDarwinAVFileType? darwinFileType, + PMCancelToken? cancelToken, }) { final Map params = { 'id': id, 'darwinFileType': darwinFileType, }; _injectProgressHandlerParams(params, progressHandler); + _setCancelToken(params, cancelToken); return _channel.invokeMethod(PMConstants.mGetOriginBytes, params); } @@ -280,6 +310,7 @@ class PhotoManagerPlugin with BasePlugin, IosPlugin, AndroidPlugin, OhosPlugin { PMProgressHandler? progressHandler, int subtype = 0, PMDarwinAVFileType? darwinFileType, + PMCancelToken? cancelToken, }) async { final params = { 'id': id, @@ -288,6 +319,7 @@ class PhotoManagerPlugin with BasePlugin, IosPlugin, AndroidPlugin, OhosPlugin { 'darwinFileType': darwinFileType?.value ?? 0, }; _injectProgressHandlerParams(params, progressHandler); + _setCancelToken(params, cancelToken); return _channel.invokeMethod(PMConstants.mGetFullFile, params); } @@ -487,6 +519,7 @@ class PhotoManagerPlugin with BasePlugin, IosPlugin, AndroidPlugin, OhosPlugin { Future getMediaUrl( AssetEntity entity, { PMProgressHandler? progressHandler, + PMCancelToken? cancelToken, }) async { if (PlatformUtils.isOhos) { return entity.id; @@ -496,6 +529,7 @@ class PhotoManagerPlugin with BasePlugin, IosPlugin, AndroidPlugin, OhosPlugin { 'type': entity.typeInt, }; _injectProgressHandlerParams(params, progressHandler); + _setCancelToken(params, cancelToken); return _channel.invokeMethod(PMConstants.mGetMediaUrl, params); } @@ -693,6 +727,18 @@ class PhotoManagerPlugin with BasePlugin, IosPlugin, AndroidPlugin, OhosPlugin { ); return PermissionState.values[result]; } + + Future cancelRequest(PMCancelToken pmCancelToken) { + return _channel.invokeMethod(PMConstants.mCancelRequestWithCancelToken, { + 'cancelToken': pmCancelToken.key, + }); + } + + Future cancelAllRequest() { + return _channel.invokeMethod( + PMConstants.mCancelAllRequest, + ); + } } mixin IosPlugin on BasePlugin { diff --git a/lib/src/managers/photo_manager.dart b/lib/src/managers/photo_manager.dart index 640e196d..423102bf 100644 --- a/lib/src/managers/photo_manager.dart +++ b/lib/src/managers/photo_manager.dart @@ -10,6 +10,8 @@ import '../filter/path_filter.dart'; import '../internal/editor.dart'; import '../internal/enums.dart'; import '../internal/plugin.dart' as base; +import '../internal/plugin.dart'; +import '../types/cancel_token.dart'; import '../types/entity.dart'; import '../types/types.dart'; import 'notify_manager.dart'; @@ -289,4 +291,11 @@ class PhotoManager { type: type, ); } + + /// Cancel the request with the given [cancelToken]. + static Future cancelRequest(PMCancelToken cancelToken) => + plugin.cancelRequest(cancelToken); + + /// Cancel all loading. + static Future cancelAllRequest() => plugin.cancelAllRequest(); } diff --git a/lib/src/types/cancel_token.dart b/lib/src/types/cancel_token.dart new file mode 100644 index 00000000..78180a93 --- /dev/null +++ b/lib/src/types/cancel_token.dart @@ -0,0 +1,25 @@ +import 'package:flutter/foundation.dart'; + +import '../internal/plugin.dart'; + +/// The cancel token is used to cancel the request. +class PMCancelToken { + PMCancelToken({this.debugLabel}) : index = _index++; + + static int _index = 0; + + final int index; + final String? debugLabel; + + /// The key of cancel token, usually to use by + /// [PhotoManagerPlugin.cancelRequest]. + /// User don't need to use this. + @nonVirtual + String get key => _index.toString(); + + /// Cancel the request. + Future cancelRequest() => plugin.cancelRequest(this); + + @override + String toString() => 'PMCancelToken($key)'; +} diff --git a/lib/src/types/entity.dart b/lib/src/types/entity.dart index 8162bcb4..ac63c373 100644 --- a/lib/src/types/entity.dart +++ b/lib/src/types/entity.dart @@ -16,6 +16,7 @@ import '../internal/enums.dart'; import '../internal/plugin.dart'; import '../internal/progress_handler.dart'; import '../utils/convert_utils.dart'; +import 'cancel_token.dart'; import 'thumbnail.dart'; import 'types.dart'; @@ -528,7 +529,7 @@ class AssetEntity { /// * [originFile] which can obtain the origin file. /// * [originFileWithSubtype] which can obtain the origin file with subtype. /// * [loadFile] which can obtain file with [PMProgressHandler]. - Future get file => _getFile(); + Future get file => getFile(); /// Obtain the compressed file of the asset with subtype. /// @@ -539,7 +540,7 @@ class AssetEntity { /// * [originFile] which can obtain the origin file. /// * [originFileWithSubtype] which can obtain the origin file with subtype. /// * [loadFile] which can obtain file with [PMProgressHandler]. - Future get fileWithSubtype => _getFile(subtype: subtype); + Future get fileWithSubtype => getFile(subtype: subtype); /// Obtain the original file that contain all EXIF information. /// @@ -552,7 +553,7 @@ class AssetEntity { /// * [fileWithSubtype] which can obtain the compressed file with subtype. /// * [originFileWithSubtype] which can obtain the origin file with subtype. /// * [loadFile] which can obtain file with [PMProgressHandler]. - Future get originFile => _getFile(isOrigin: true); + Future get originFile => getFile(isOrigin: true); /// Obtain the origin file with subtype. /// @@ -563,9 +564,8 @@ class AssetEntity { /// * [fileWithSubtype] which can obtain the compressed file with subtype. /// * [originFile] which can obtain the origin file. /// * [loadFile] which can obtain file with [PMProgressHandler]. - Future get originFileWithSubtype { - return _getFile(isOrigin: true, subtype: subtype); - } + Future get originFileWithSubtype => + getFile(isOrigin: true, subtype: subtype); /// Obtain file of the asset with a [PMProgressHandler]. /// @@ -579,17 +579,20 @@ class AssetEntity { /// * [fileWithSubtype] which can obtain the compressed file with subtype. /// * [originFile] which can obtain the original file. /// * [originFileWithSubtype] which can obtain the origin file with subtype. + /// * [cancelToken] is used to cancel the file loading process. Future loadFile({ bool isOrigin = true, bool withSubtype = false, PMProgressHandler? progressHandler, + PMCancelToken? cancelToken, PMDarwinAVFileType? darwinFileType, }) { - return _getFile( + return getFile( isOrigin: isOrigin, subtype: withSubtype ? subtype : 0, progressHandler: progressHandler, darwinFileType: darwinFileType, + cancelToken: cancelToken, ); } @@ -597,7 +600,7 @@ class AssetEntity { /// /// **Use it with cautious** since the original data might be epic large. /// Generally use this method only for images. - Future get originBytes => _getOriginBytes(); + Future get originBytes => getOriginBytes(); /// Obtain the thumbnail data with [PMConstants.vDefaultThumbnailSize] /// size of the asset, typically use it for preview displays. @@ -620,11 +623,13 @@ class AssetEntity { /// See also: /// * [thumbnailData] which obtain the thumbnail data with fixed size. /// * [thumbnailDataWithOption] which accepts customized [ThumbnailOption]. + /// * [cancelToken] is used to cancel the thumbnail loading process. Future thumbnailDataWithSize( ThumbnailSize size, { ThumbnailFormat format = ThumbnailFormat.jpeg, int quality = 100, PMProgressHandler? progressHandler, + PMCancelToken? cancelToken, int frame = 0, }) { assert(() { @@ -655,7 +660,11 @@ class AssetEntity { return true; }()); - return thumbnailDataWithOption(option, progressHandler: progressHandler); + return thumbnailDataWithOption( + option, + progressHandler: progressHandler, + cancelToken: cancelToken, + ); } /// Obtain the thumbnail data with the given customized [ThumbnailOption]. @@ -663,9 +672,11 @@ class AssetEntity { /// See also: /// * [thumbnailData] which obtain the thumbnail data with fixed size. /// * [thumbnailDataWithSize] which is a common method to obtain thumbnails. + /// * [cancelToken] is used to cancel the thumbnail loading process. Future thumbnailDataWithOption( ThumbnailOption option, { PMProgressHandler? progressHandler, + PMCancelToken? cancelToken, }) { assert(() { _checkThumbnailAssertion(); @@ -683,6 +694,7 @@ class AssetEntity { id: id, option: option, progressHandler: progressHandler, + cancelToken: cancelToken, ); } @@ -731,15 +743,20 @@ class AssetEntity { /// * iOS/macOS: File URL. e.g. /// `file:///var/mobile/Media/DCIM/118APPLE/IMG_8371.MOV`. /// + /// * [progressHandler] is used to handle the progress of the media URL loading process. + /// * [cancelToken] is used to cancel the media URL loading process. + /// /// See also: /// * https://developer.android.com/reference/android/content/ContentUris /// * https://developer.apple.com/documentation/avfoundation/avurlasset Future getMediaUrl({ PMProgressHandler? progressHandler, + PMCancelToken? cancelToken, }) { return plugin.getMediaUrl( this, progressHandler: progressHandler, + cancelToken: cancelToken, ); } @@ -749,11 +766,20 @@ class AssetEntity { Platform.isAndroid || PlatformUtils.isOhos; - Future _getFile({ + /// Obtain the file of the asset. + /// + /// * [isOrigin] is used to obtain the origin file. + /// * [progressHandler] is used to handle the progress of the file loading process. + /// * [subtype] is used to obtain the file with subtype. + /// * [darwinFileType] will try to define the export format when + /// exporting assets, such as exporting a MOV file to MP4. + /// * [cancelToken] is used to cancel the file loading process. + Future getFile({ bool isOrigin = false, PMProgressHandler? progressHandler, int subtype = 0, PMDarwinAVFileType? darwinFileType, + PMCancelToken? cancelToken, }) async { assert( _platformMatched, @@ -768,6 +794,7 @@ class AssetEntity { progressHandler: progressHandler, subtype: subtype, darwinFileType: darwinFileType, + cancelToken: cancelToken, ); if (path == null) { return null; @@ -775,8 +802,16 @@ class AssetEntity { return File(path); } - Future _getOriginBytes({ + /// Obtain the raw data of the asset. + /// + /// **Use it with cautious** since the original data might be epic large. + /// Generally use this method only for images. + /// + /// * [progressHandler] is used to handle the progress of the raw data loading process. + /// * [cancelToken] is used to cancel the raw data loading process. + Future getOriginBytes({ PMProgressHandler? progressHandler, + PMCancelToken? cancelToken, }) async { assert( _platformMatched, @@ -788,11 +823,19 @@ class AssetEntity { if (Platform.isAndroid) { final sdkInt = int.parse(await plugin.getSystemVersion()); if (sdkInt > 29) { - return plugin.getOriginBytes(id, progressHandler: progressHandler); + return plugin.getOriginBytes( + id, + progressHandler: progressHandler, + cancelToken: cancelToken, + ); } } if (PlatformUtils.isOhos) { - return plugin.getOriginBytes(id, progressHandler: progressHandler); + return plugin.getOriginBytes( + id, + progressHandler: progressHandler, + cancelToken: cancelToken, + ); } final File? file = await originFile; return file?.readAsBytes(); diff --git a/pubspec.yaml b/pubspec.yaml index 70b5dd59..06136085 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: photo_manager description: A Flutter plugin that provides album assets abstraction management APIs on Android, iOS, macOS, and OpenHarmony. repository: https://github.com/fluttercandies/flutter_photo_manager -version: 3.6.4 +version: 3.6.3 environment: sdk: ">=2.13.0 <4.0.0"