From 00a40a24493a90dad24ef1066cd5c77dbabd4c7c Mon Sep 17 00:00:00 2001 From: Caijinglong Date: Thu, 28 Nov 2024 15:54:21 +0800 Subject: [PATCH 01/10] refactor: Use PMResultHandler to replace the old one Signed-off-by: Caijinglong --- ios/Classes/PMPlugin.m | 29 ++++----- .../{ResultHandler.h => PMResultHandler.h} | 12 +++- .../{ResultHandler.m => PMResultHandler.m} | 9 +-- ios/Classes/core/PMManager.h | 13 ++-- ios/Classes/core/PMManager.m | 59 ++++++++++++++----- ios/Classes/core/PMProgressHandlerProtocol.h | 1 + ios/Classes/core/PMResultHandler.h | 13 ---- lib/src/internal/enums.dart | 2 +- 8 files changed, 80 insertions(+), 58 deletions(-) rename ios/Classes/{ResultHandler.h => PMResultHandler.h} (63%) rename ios/Classes/{ResultHandler.m => PMResultHandler.m} (97%) delete mode 100644 ios/Classes/core/PMResultHandler.h diff --git a/ios/Classes/PMPlugin.m b/ios/Classes/PMPlugin.m index 60d08c67..e4ccaf23 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" @@ -29,8 +29,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 +120,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 +131,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,7 +155,7 @@ - (void)handleNotNeedPermissionMethod:(ResultHandler *)handler { } } -- (void)getPermissionState:(ResultHandler *)handler { +- (void)getPermissionState:(PMResultHandler *)handler { int requestAccessLevel = [handler.call.arguments[@"iosAccessLevel"] intValue]; #if __IPHONE_14_0 if (@available(iOS 14, *)) { @@ -170,7 +171,7 @@ - (void)getPermissionState:(ResultHandler *)handler { #endif } -- (void)handleAboutPermissionMethod:(ResultHandler *)handler { +- (void)handleAboutPermissionMethod:(PMResultHandler *)handler { FlutterMethodCall *call = handler.call; PMManager *manager = self.manager; @@ -182,7 +183,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 +208,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 +246,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]; @@ -326,7 +327,7 @@ - (void)presentLimited:(ResultHandler*)handler { #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); } @@ -367,7 +368,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 +382,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"]) { @@ -692,7 +693,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 +710,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 63% rename from ios/Classes/ResultHandler.h rename to ios/Classes/PMResultHandler.h index 532a9ce6..927fe8b8 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,12 @@ + (instancetype)handlerWithCall:(FlutterMethodCall *)call result:(FlutterResult)result; +- (void)replyError:(NSObject *)value; + +- (void)reply:(id)obj; + +- (void)notImplemented; + +- (BOOL)isReplied; @end diff --git a/ios/Classes/ResultHandler.m b/ios/Classes/PMResultHandler.m similarity index 97% rename from ios/Classes/ResultHandler.m rename to ios/Classes/PMResultHandler.m index e981eb25..ab1462f7 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,10 @@ - (void)notImplemented { dispatch_async(dispatch_get_main_queue(), ^{ self.result(FlutterMethodNotImplemented); }); - } - (BOOL)isReplied { return isReply; } + @end diff --git a/ios/Classes/core/PMManager.h b/ios/Classes/core/PMManager.h index ae9021e2..ed998c92 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; diff --git a/ios/Classes/core/PMManager.m b/ios/Classes/core/PMManager.m index 9612f947..555e70c1 100644 --- a/ios/Classes/core/PMManager.m +++ b/ios/Classes/core/PMManager.m @@ -10,6 +10,7 @@ #import "PMManager.h" #import "PMMD5Utils.h" #import "PMPathFilterOption.h" +#import "PMResultHandler.h" @implementation PMManager { PMCacheContainer *cacheContainer; @@ -424,7 +425,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 +435,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 +457,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,6 +466,12 @@ - (void)fetchThumb:(PHAsset *)asset option:(PMThumbLoadOption *)option resultHan return; } + PHImageRequestID currentReqID = [[info objectForKey:PHImageResultRequestIDKey] intValue]; + if (currentReqID == PHInvalidImageRequestID) { + [self handleCancelRequest:handler progressHandler:progressHandler]; + return; + } + NSObject *error = info[PHImageErrorKey]; if (error) { [handler replyError:error]; @@ -495,7 +502,7 @@ - (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 +538,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 +564,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,7 +588,7 @@ - (void)fetchOriginVideoFile:(PHAsset *)asset } - (void)fetchFullSizeVideo:(PHAsset *)asset - handler:(NSObject *)handler + handler:(PMResultHandler *)handler progressHandler:(NSObject *)progressHandler withScheme:(BOOL)withScheme fileType:(AVFileType)fileType { @@ -669,7 +676,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 @@ -746,7 +753,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) { @@ -966,7 +973,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 +1020,7 @@ - (void)fetchOriginImageFile:(PHAsset *)asset resultHandler:(NSObject *)handler + resultHandler:(PMResultHandler *)handler progressHandler:(NSObject *)progressHandler { PHImageRequestOptions *options = [PHImageRequestOptions new]; [options setDeliveryMode:PHImageRequestOptionsDeliveryModeOpportunistic]; @@ -1036,7 +1043,7 @@ - (void)fetchFullSizeImageFile:(PHAsset *)asset } }]; - [self.cachingManager requestImageForAsset:asset + PHImageRequestID requestId = [self.cachingManager requestImageForAsset:asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeDefault options:options @@ -1045,6 +1052,14 @@ - (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]; + return; + } + NSObject *error = info[PHImageErrorKey]; if (error) { [handler replyError:error]; @@ -1110,7 +1125,7 @@ - (PHFetchOptions *)getAssetOptions:(int)type filterOption:(NSObject*)result { ++ (void)openSetting:(ResultHandler*)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 +1137,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 +1303,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 +1361,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 +1775,18 @@ - (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:(PHImageRequestID)requestId { +} + +# 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/src/internal/enums.dart b/lib/src/internal/enums.dart index 3f91ab19..13fd5c75 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. /// From 57f7c5ddb943a2208fb36abd304c826a774e29b1 Mon Sep 17 00:00:00 2001 From: Caijinglong Date: Thu, 28 Nov 2024 15:58:46 +0800 Subject: [PATCH 02/10] fix: fix a warning Signed-off-by: Caijinglong --- ios/Classes/PMPlugin.h | 2 +- ios/Classes/PMPlugin.m | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) 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 e4ccaf23..50376c9a 100644 --- a/ios/Classes/PMPlugin.m +++ b/ios/Classes/PMPlugin.m @@ -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]; From 8661be6b160f9eeee688f40bdead9f5752f5a127 Mon Sep 17 00:00:00 2001 From: Caijinglong Date: Thu, 28 Nov 2024 16:07:45 +0800 Subject: [PATCH 03/10] fix: fix an log error Signed-off-by: Caijinglong --- ios/Classes/core/PMManager.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/Classes/core/PMManager.m b/ios/Classes/core/PMManager.m index 555e70c1..841da16a 100644 --- a/ios/Classes/core/PMManager.m +++ b/ios/Classes/core/PMManager.m @@ -116,7 +116,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); From c49c4005f350015b34cc65e931296e44d4b87b77 Mon Sep 17 00:00:00 2001 From: Caijinglong Date: Thu, 28 Nov 2024 17:18:37 +0800 Subject: [PATCH 04/10] feat: add cancel token for iOS Signed-off-by: Caijinglong --- ios/Classes/PMPlugin.m | 11 +++++++- ios/Classes/PMResultHandler.h | 2 ++ ios/Classes/PMResultHandler.m | 4 +++ ios/Classes/core/PMManager.h | 5 ++++ ios/Classes/core/PMManager.m | 50 ++++++++++++++++++++++++++++++++- lib/src/internal/constants.dart | 4 +++ lib/src/internal/plugin.dart | 37 ++++++++++++++++++++++++ lib/src/types/cancel_token.dart | 0 8 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 lib/src/types/cancel_token.dart diff --git a/ios/Classes/PMPlugin.m b/ios/Classes/PMPlugin.m index 50376c9a..c113ef01 100644 --- a/ios/Classes/PMPlugin.m +++ b/ios/Classes/PMPlugin.m @@ -341,7 +341,9 @@ - (void)runInBackground:(dispatch_block_t)block withHandler:(PMResultHandler *)h - (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; } @@ -676,6 +678,13 @@ - (void)handleMethodResultHandler:(PMResultHandler *)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]; } diff --git a/ios/Classes/PMResultHandler.h b/ios/Classes/PMResultHandler.h index 927fe8b8..9d1a1036 100644 --- a/ios/Classes/PMResultHandler.h +++ b/ios/Classes/PMResultHandler.h @@ -20,4 +20,6 @@ - (BOOL)isReplied; +- (NSString *)getCancelToken; + @end diff --git a/ios/Classes/PMResultHandler.m b/ios/Classes/PMResultHandler.m index ab1462f7..ef814bcf 100644 --- a/ios/Classes/PMResultHandler.m +++ b/ios/Classes/PMResultHandler.m @@ -87,4 +87,8 @@ - (BOOL)isReplied { return isReply; } +- (NSString *)getCancelToken { + return self.call.arguments[@"cancelToken"]; +} + @end diff --git a/ios/Classes/core/PMManager.h b/ios/Classes/core/PMManager.h index ed998c92..039ba679 100644 --- a/ios/Classes/core/PMManager.h +++ b/ios/Classes/core/PMManager.h @@ -128,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 841da16a..650d9aec 100644 --- a/ios/Classes/core/PMManager.m +++ b/ios/Classes/core/PMManager.m @@ -16,12 +16,16 @@ @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; } @@ -469,6 +473,7 @@ - (void)fetchThumb:(PHAsset *)asset option:(PMThumbLoadOption *)option resultHan PHImageRequestID currentReqID = [[info objectForKey:PHImageResultRequestIDKey] intValue]; if (currentReqID == PHInvalidImageRequestID) { [self handleCancelRequest:handler progressHandler:progressHandler]; + [self removeRequstIdWithCancelToken:[handler getCancelToken]]; return; } @@ -476,11 +481,13 @@ - (void)fetchThumb:(PHAsset *)asset option:(PMThumbLoadOption *)option resultHan 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; } @@ -496,6 +503,7 @@ - (void)fetchThumb:(PHAsset *)asset option:(PMThumbLoadOption *)option resultHan }]; + [self addRequstId:[handler getCancelToken] requestId:requestId]; } - (void)getFullSizeFileWithId:(NSString *)assetId @@ -593,6 +601,7 @@ - (void)fetchFullSizeVideo:(PHAsset *)asset withScheme:(BOOL)withScheme fileType:(AVFileType)fileType { [self exportAssetToFile:asset + resultHandler:handler progressHandler:progressHandler withScheme:withScheme fileType:fileType @@ -720,6 +729,7 @@ - (void)fetchVideoResourceToFile:(PHAsset *)asset } - (void)exportAssetToFile:(PHAsset *)asset + resultHandler:(PMResultHandler *)handler progressHandler:(NSObject *)progressHandler withScheme:(BOOL)withScheme fileType:(AVFileType)fileType @@ -761,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; } @@ -811,6 +823,7 @@ - (void)exportAssetToFile:(PHAsset *)asset block(path, nil); } [self notifySuccess:progressHandler]; + [self removeRequstIdWithCancelToken:[handler getCancelToken]]; return; } @@ -829,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 @@ -1057,6 +1073,7 @@ - (void)fetchFullSizeImageFile:(PHAsset *)asset if (currentReqID == PHInvalidImageRequestID) { [handler replyError:@"Failed to fetch full size image."]; [self notifyProgress:progressHandler progress:lastProgress state:PMProgressStateFailed]; + [self removeRequstIdWithCancelToken:[handler getCancelToken]]; return; } @@ -1064,11 +1081,13 @@ - (void)fetchFullSizeImageFile:(PHAsset *)asset 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; } @@ -1081,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 { @@ -1783,7 +1805,33 @@ - (void)handleCancelRequest:(PMResultHandler *)handler [handler replyError:@"Request canceled"]; } -- (void) addRequstId:(PHImageRequestID)requestId { +- (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 diff --git a/lib/src/internal/constants.dart b/lib/src/internal/constants.dart index 87ca2f00..fc847230 100644 --- a/lib/src/internal/constants.dart +++ b/lib/src/internal/constants.dart @@ -64,6 +64,9 @@ 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 +77,5 @@ class PMConstants { 'ohos.permission.READ_IMAGEVIDEO', 'ohos.permission.WRITE_IMAGEVIDEO', ]; + } diff --git a/lib/src/internal/plugin.dart b/lib/src/internal/plugin.dart index 91b2a071..806548be 100644 --- a/lib/src/internal/plugin.dart +++ b/lib/src/internal/plugin.dart @@ -22,6 +22,31 @@ import 'progress_handler.dart'; PhotoManagerPlugin plugin = PhotoManagerPlugin(); +/// The cancel token is used to cancel the request. +class PMCancelToken { + PMCancelToken({this.debugLabel}) : index = getIndex(); + + static int _index = 0; + + static int getIndex() { + final res = _index; + _index++; + return res; + } + + final String? debugLabel; + final int index; + + /// The key of cancel token, usually to use by [PhotoManagerPlugin.cancelRequest]. + /// User don't need to use this. + String get key => _index.toString(); + + /// Cancel the request. + Future cancelRequest() async { + await plugin.cancelRequest(this); + } +} + mixin BasePlugin { MethodChannel _channel = const MethodChannel(PMConstants.channelPrefix); @@ -693,6 +718,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/types/cancel_token.dart b/lib/src/types/cancel_token.dart new file mode 100644 index 00000000..e69de29b From f237252ce1e454a25f40156a09674b54e76ad43f Mon Sep 17 00:00:00 2001 From: Caijinglong Date: Thu, 28 Nov 2024 17:27:50 +0800 Subject: [PATCH 05/10] feat: add cancel token for iOS Signed-off-by: Caijinglong --- lib/src/internal/constants.dart | 1 + lib/src/internal/plugin.dart | 35 +++++++++++++++++++++++++++++++-- lib/src/types/entity.dart | 28 +++++++++++++++++++++++--- 3 files changed, 59 insertions(+), 5 deletions(-) diff --git a/lib/src/internal/constants.dart b/lib/src/internal/constants.dart index fc847230..ebc1411e 100644 --- a/lib/src/internal/constants.dart +++ b/lib/src/internal/constants.dart @@ -78,4 +78,5 @@ class PMConstants { 'ohos.permission.WRITE_IMAGEVIDEO', ]; + static const cancelTokenKey = 'cancelToken'; } diff --git a/lib/src/internal/plugin.dart b/lib/src/internal/plugin.dart index 806548be..71db838f 100644 --- a/lib/src/internal/plugin.dart +++ b/lib/src/internal/plugin.dart @@ -48,13 +48,29 @@ class PMCancelToken { } mixin BasePlugin { - MethodChannel _channel = const MethodChannel(PMConstants.channelPrefix); + MethodChannel _channel = const PMMethodChannel(PMConstants.channelPrefix); final Map onlyAddPermission = { 'onlyAddPermission': true, }; } +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); + } + final cancelToken = arguments[PMConstants.cancelTokenKey]; + if (cancelToken == null) { + arguments[PMConstants.cancelTokenKey] = PMCancelToken().key; + } + return super.invokeMethod(method, arguments); + } +} + class VerboseLogMethodChannel extends MethodChannel { VerboseLogMethodChannel({ required String name, @@ -154,7 +170,7 @@ class PhotoManagerPlugin with BasePlugin, IosPlugin, AndroidPlugin, OhosPlugin { logFilePath: logPath, ); } else { - _channel = const MethodChannel(PMConstants.channelPrefix); + _channel = const PMMethodChannel(PMConstants.channelPrefix); } } @@ -265,17 +281,26 @@ 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); } @@ -283,12 +308,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); } @@ -305,6 +332,7 @@ class PhotoManagerPlugin with BasePlugin, IosPlugin, AndroidPlugin, OhosPlugin { PMProgressHandler? progressHandler, int subtype = 0, PMDarwinAVFileType? darwinFileType, + PMCancelToken? cancelToken, }) async { final params = { 'id': id, @@ -313,6 +341,7 @@ class PhotoManagerPlugin with BasePlugin, IosPlugin, AndroidPlugin, OhosPlugin { 'darwinFileType': darwinFileType?.value ?? 0, }; _injectProgressHandlerParams(params, progressHandler); + _setCancelToken(params, cancelToken); return _channel.invokeMethod(PMConstants.mGetFullFile, params); } @@ -512,6 +541,7 @@ class PhotoManagerPlugin with BasePlugin, IosPlugin, AndroidPlugin, OhosPlugin { Future getMediaUrl( AssetEntity entity, { PMProgressHandler? progressHandler, + PMCancelToken? cancelToken, }) async { if (PlatformUtils.isOhos) { return entity.id; @@ -521,6 +551,7 @@ class PhotoManagerPlugin with BasePlugin, IosPlugin, AndroidPlugin, OhosPlugin { 'type': entity.typeInt, }; _injectProgressHandlerParams(params, progressHandler); + _setCancelToken(params, cancelToken); return _channel.invokeMethod(PMConstants.mGetMediaUrl, params); } diff --git a/lib/src/types/entity.dart b/lib/src/types/entity.dart index 8162bcb4..34f76450 100644 --- a/lib/src/types/entity.dart +++ b/lib/src/types/entity.dart @@ -583,6 +583,7 @@ class AssetEntity { bool isOrigin = true, bool withSubtype = false, PMProgressHandler? progressHandler, + PMCancelToken? cancelToken, PMDarwinAVFileType? darwinFileType, }) { return _getFile( @@ -590,6 +591,7 @@ class AssetEntity { subtype: withSubtype ? subtype : 0, progressHandler: progressHandler, darwinFileType: darwinFileType, + cancelToken: cancelToken, ); } @@ -625,6 +627,7 @@ class AssetEntity { ThumbnailFormat format = ThumbnailFormat.jpeg, int quality = 100, PMProgressHandler? progressHandler, + PMCancelToken? cancelToken, int frame = 0, }) { assert(() { @@ -655,7 +658,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]. @@ -666,6 +673,7 @@ class AssetEntity { Future thumbnailDataWithOption( ThumbnailOption option, { PMProgressHandler? progressHandler, + PMCancelToken? cancelToken, }) { assert(() { _checkThumbnailAssertion(); @@ -683,6 +691,7 @@ class AssetEntity { id: id, option: option, progressHandler: progressHandler, + cancelToken: cancelToken, ); } @@ -736,10 +745,12 @@ class AssetEntity { /// * https://developer.apple.com/documentation/avfoundation/avurlasset Future getMediaUrl({ PMProgressHandler? progressHandler, + PMCancelToken? cancelToken, }) { return plugin.getMediaUrl( this, progressHandler: progressHandler, + cancelToken: cancelToken, ); } @@ -754,6 +765,7 @@ class AssetEntity { PMProgressHandler? progressHandler, int subtype = 0, PMDarwinAVFileType? darwinFileType, + PMCancelToken? cancelToken, }) async { assert( _platformMatched, @@ -768,6 +780,7 @@ class AssetEntity { progressHandler: progressHandler, subtype: subtype, darwinFileType: darwinFileType, + cancelToken: cancelToken, ); if (path == null) { return null; @@ -777,6 +790,7 @@ class AssetEntity { Future _getOriginBytes({ PMProgressHandler? progressHandler, + PMCancelToken? cancelToken, }) async { assert( _platformMatched, @@ -788,11 +802,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(); From 80ff5fb0f2a7893789c8e529685335060a965493 Mon Sep 17 00:00:00 2001 From: Caijinglong Date: Thu, 28 Nov 2024 17:42:15 +0800 Subject: [PATCH 06/10] feat: Add cancel token for darwin Signed-off-by: Caijinglong --- CHANGELOG.md | 8 +++++- README-ZH.md | 19 +++++++++++++ README.md | 17 ++++++++++++ lib/photo_manager.dart | 1 + lib/src/internal/constants.dart | 3 ++- lib/src/internal/plugin.dart | 26 +----------------- lib/src/managers/photo_manager.dart | 9 +++++++ lib/src/types/cancel_token.dart | 26 ++++++++++++++++++ lib/src/types/entity.dart | 41 ++++++++++++++++++++++------- pubspec.yaml | 2 +- 10 files changed, 114 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5a921b6..f497f5c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,13 @@ To know more about breaking changes, see the [Migration Guide][]. *None.* -## 3.6.3 +## 3.7.0 + +### Features + +- Add `cancelToken` parameter to `AssetEntity.loadFile`. +- Add `cancelAllRequest` method to `PhotoManager`. +- The `getFile` and `getOriginBytes` methods are public. ### Improvements 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/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 ebc1411e..b71eed8c 100644 --- a/lib/src/internal/constants.dart +++ b/lib/src/internal/constants.dart @@ -64,7 +64,8 @@ class PMConstants { static const String mGetAssetCount = 'getAssetCount'; static const String mGetAssetsByRange = 'getAssetsByRange'; - static const String mCancelRequestWithCancelToken = 'cancelRequestWithCancelToken'; + static const String mCancelRequestWithCancelToken = + 'cancelRequestWithCancelToken'; static const String mCancelAllRequest = 'cancelAllRequest'; /// Constant value. diff --git a/lib/src/internal/plugin.dart b/lib/src/internal/plugin.dart index 71db838f..f38565d1 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'; @@ -22,31 +23,6 @@ import 'progress_handler.dart'; PhotoManagerPlugin plugin = PhotoManagerPlugin(); -/// The cancel token is used to cancel the request. -class PMCancelToken { - PMCancelToken({this.debugLabel}) : index = getIndex(); - - static int _index = 0; - - static int getIndex() { - final res = _index; - _index++; - return res; - } - - final String? debugLabel; - final int index; - - /// The key of cancel token, usually to use by [PhotoManagerPlugin.cancelRequest]. - /// User don't need to use this. - String get key => _index.toString(); - - /// Cancel the request. - Future cancelRequest() async { - await plugin.cancelRequest(this); - } -} - mixin BasePlugin { MethodChannel _channel = const PMMethodChannel(PMConstants.channelPrefix); 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 index e69de29b..01a07ea6 100644 --- a/lib/src/types/cancel_token.dart +++ b/lib/src/types/cancel_token.dart @@ -0,0 +1,26 @@ +import '../internal/plugin.dart'; + +/// The cancel token is used to cancel the request. +class PMCancelToken { + PMCancelToken({this.debugLabel}) : index = getIndex(); + + static int _index = 0; + + static int getIndex() { + final res = _index; + _index++; + return res; + } + + final String? debugLabel; + final int index; + + /// The key of cancel token, usually to use by [PhotoManagerPlugin.cancelRequest]. + /// User don't need to use this. + String get key => _index.toString(); + + /// Cancel the request. + Future cancelRequest() async { + await plugin.cancelRequest(this); + } +} diff --git a/lib/src/types/entity.dart b/lib/src/types/entity.dart index 34f76450..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,6 +579,7 @@ 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, @@ -586,7 +587,7 @@ class AssetEntity { PMCancelToken? cancelToken, PMDarwinAVFileType? darwinFileType, }) { - return _getFile( + return getFile( isOrigin: isOrigin, subtype: withSubtype ? subtype : 0, progressHandler: progressHandler, @@ -599,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. @@ -622,6 +623,7 @@ 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, @@ -670,6 +672,7 @@ 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, @@ -740,6 +743,9 @@ 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 @@ -760,7 +766,15 @@ 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, @@ -788,7 +802,14 @@ 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 { diff --git a/pubspec.yaml b/pubspec.yaml index 06136085..034abadd 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.3 +version: 3.7.0 environment: sdk: ">=2.13.0 <4.0.0" From 2dc969d19c69f77f3708496d855fd5dd2cd23131 Mon Sep 17 00:00:00 2001 From: Caijinglong Date: Thu, 28 Nov 2024 17:44:44 +0800 Subject: [PATCH 07/10] style: format code Signed-off-by: Caijinglong --- lib/src/internal/plugin.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/src/internal/plugin.dart b/lib/src/internal/plugin.dart index f38565d1..14f3825c 100644 --- a/lib/src/internal/plugin.dart +++ b/lib/src/internal/plugin.dart @@ -258,7 +258,9 @@ class PhotoManagerPlugin with BasePlugin, IosPlugin, AndroidPlugin, OhosPlugin { } void _setCancelToken( - Map params, PMCancelToken? cancelToken) { + Map params, + PMCancelToken? cancelToken, + ) { if (cancelToken != null) { params[PMConstants.cancelTokenKey] = cancelToken.key; } From e15e0d53d6e22a5ead2d3cb093530034741fc5c8 Mon Sep 17 00:00:00 2001 From: Caijinglong Date: Fri, 29 Nov 2024 08:31:57 +0800 Subject: [PATCH 08/10] fix: edit some macOS macro Signed-off-by: Caijinglong --- ios/Classes/PMPlugin.m | 15 ++++++++++----- ios/Classes/core/PHAssetResource+PM_COMMON.m | 6 +++++- ios/Classes/core/PMManager.m | 2 +- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/ios/Classes/PMPlugin.m b/ios/Classes/PMPlugin.m index c113ef01..81473397 100644 --- a/ios/Classes/PMPlugin.m +++ b/ios/Classes/PMPlugin.m @@ -163,7 +163,7 @@ - (void)handleNotNeedPermissionMethod:(PMResultHandler *)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)]; @@ -172,8 +172,13 @@ - (void)getPermissionState:(PMResultHandler *)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 } @@ -289,7 +294,7 @@ - (void)presentLimited:(PMResultHandler *)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, *)) { @@ -327,7 +332,7 @@ - (void)requestPermissionStatus:(int)requestAccessLevel #endif } -- (void)presentLimited:(ResultHandler*)handler { +- (void)presentLimited:(PMResultHandler*)handler { [handler replyError:@"Not supported on macOS."]; } 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.m b/ios/Classes/core/PMManager.m index 650d9aec..cfccf3f0 100644 --- a/ios/Classes/core/PMManager.m +++ b/ios/Classes/core/PMManager.m @@ -1147,7 +1147,7 @@ - (PHFetchOptions *)getAssetOptions:(int)type filterOption:(NSObject Date: Sat, 28 Dec 2024 17:31:33 +0800 Subject: [PATCH 09/10] =?UTF-8?q?=F0=9F=8E=A8=20Improve=20`PMCancelToken`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/src/types/cancel_token.dart | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/lib/src/types/cancel_token.dart b/lib/src/types/cancel_token.dart index 01a07ea6..78180a93 100644 --- a/lib/src/types/cancel_token.dart +++ b/lib/src/types/cancel_token.dart @@ -1,26 +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 = getIndex(); + PMCancelToken({this.debugLabel}) : index = _index++; static int _index = 0; - static int getIndex() { - final res = _index; - _index++; - return res; - } - - final String? debugLabel; final int index; + final String? debugLabel; - /// The key of cancel token, usually to use by [PhotoManagerPlugin.cancelRequest]. + /// 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() async { - await plugin.cancelRequest(this); - } + Future cancelRequest() => plugin.cancelRequest(this); + + @override + String toString() => 'PMCancelToken($key)'; } From 417f31feee2866a8f6e33c25d493ce33ed52b93a Mon Sep 17 00:00:00 2001 From: Alex Li Date: Sat, 28 Dec 2024 17:34:15 +0800 Subject: [PATCH 10/10] =?UTF-8?q?=F0=9F=8E=A8=20Make=20`VerboseLogMethodCh?= =?UTF-8?q?annel`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/src/internal/plugin.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/src/internal/plugin.dart b/lib/src/internal/plugin.dart index 14f3825c..47acbd7e 100644 --- a/lib/src/internal/plugin.dart +++ b/lib/src/internal/plugin.dart @@ -39,15 +39,15 @@ class PMMethodChannel extends MethodChannel { if (arguments is! Map) { return super.invokeMethod(method, arguments); } - final cancelToken = arguments[PMConstants.cancelTokenKey]; - if (cancelToken == null) { - arguments[PMConstants.cancelTokenKey] = PMCancelToken().key; - } + arguments.putIfAbsent( + PMConstants.cancelTokenKey, + () => PMCancelToken().key, + ); return super.invokeMethod(method, arguments); } } -class VerboseLogMethodChannel extends MethodChannel { +class VerboseLogMethodChannel extends PMMethodChannel { VerboseLogMethodChannel({ required String name, required this.logFilePath,