Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ To know more about breaking changes, see the [Migration Guide][].
- Add optional `latitude`, `longitude`, and `creationDate` parameters to `saveImage`, `saveImageWithPath`, and `saveVideo` methods.
- On iOS: Sets location and creation date metadata for saved assets.
- On Android Q+: Sets DATE_TAKEN field and location metadata for saved assets.
- Add `updateDateTaken` method to `DarwinEditor` and `AndroidEditor` for modifying asset creation time.
- On iOS/macOS: Updates the `creationDate` property of the asset.
- On Android Q (API 29)+: Updates the `DATE_TAKEN` field in MediaStore.

### Improvements

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ class Methods {
const val moveAssetToPath = "moveAssetToPath"
const val removeNoExistsAssets = "removeNoExistsAssets"
const val getColumnNames = "getColumnNames"
const val updateDateTaken = "updateDateTaken"

private val needMediaLocationMethods = arrayOf(
getLatLng,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,16 @@ class PhotoManager(private val context: Context) {
}
}

fun updateDateTaken(assetId: String, timestamp: Long, resultHandler: ResultHandler) {
try {
val result = dbUtils.updateDateTaken(context, assetId, timestamp)
resultHandler.reply(result)
} catch (e: Exception) {
LogUtils.error(e)
resultHandler.reply(false)
}
}

fun removeAllExistsAssets(resultHandler: ResultHandler) {
val result = dbUtils.removeAllExistsAssets(context)
resultHandler.reply(result)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,17 @@ class PhotoManagerPlugin(
favoriteManager.favoriteAsset(photoManager.getUri(assetId), isFavorite, resultHandler)
}

Methods.updateDateTaken -> {
val assetId = call.argument<String>("id")!!
val timestamp = call.argument<Long>("timestamp")!!
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
LogUtils.error("The API 29 or lower do not support updating DATE_TAKEN.")
resultHandler.reply(false)
return
}
photoManager.updateDateTaken(assetId, timestamp, resultHandler)
}

Methods.copyAsset -> {
val assetId = call.argument<String>("assetId")!!
val galleryId = call.argument<String>("galleryId")!!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,17 @@ object DBUtils : IDBUtils {
throwMsg("Cannot update $assetId relativePath")
}

override fun updateDateTaken(context: Context, assetId: String, timestamp: Long): Boolean {
val cr = context.contentResolver
val timestampMillis = timestamp * 1000
val contentValues = ContentValues().apply {
put(DATE_TAKEN, timestampMillis)
}

val count = cr.update(allUri, contentValues, idSelection, arrayOf(assetId))
return count > 0
}

private val deleteLock = ReentrantLock()

override fun removeAllExistsAssets(context: Context): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,8 @@ interface IDBUtils {

fun moveToGallery(context: Context, assetId: String, galleryId: String): AssetEntity

fun updateDateTaken(context: Context, assetId: String, timestamp: Long): Boolean

fun getSomeInfo(context: Context, assetId: String): Pair<String, String?>?

fun getUri(id: Long, type: Int, isOrigin: Boolean = false): Uri {
Expand Down
10 changes: 10 additions & 0 deletions darwin/photo_manager/Sources/photo_manager/PMPlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,16 @@ - (void)handleMethodResultHandler:(PMResultHandler *)handler manager:(PMManager
[handler reply:@(result)];
}
}];
} else if ([@"updateDateTaken" isEqualToString:call.method]) {
NSString *id = call.arguments[@"id"];
NSNumber *timestamp = call.arguments[@"timestamp"];
[manager updateDateTakenWithId:id timestamp:timestamp block:^(BOOL result, NSObject *error) {
if (error) {
[handler replyError:error];
} else {
[handler reply:@(result)];
}
}];
} else if ([@"requestCacheAssetsThumb" isEqualToString:call.method]) {
NSArray *ids = call.arguments[@"ids"];
PMThumbLoadOption *option = [PMThumbLoadOption optionDict:call.arguments[@"option"]];
Expand Down
2 changes: 2 additions & 0 deletions darwin/photo_manager/Sources/photo_manager/core/PMManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ typedef void (^AssetBlockResult)(PMAssetEntity *, NSObject *);

- (void)favoriteWithId:(NSString *)id favorite:(BOOL)favorite block:(void (^)(BOOL result, NSObject *))block;

- (void)updateDateTakenWithId:(NSString *)id timestamp:(NSNumber *)timestamp block:(void (^)(BOOL result, NSObject *))block;

- (void)clearFileCache;

- (void)requestCacheAssetsThumb:(NSArray *)ids option:(PMThumbLoadOption *)option;
Expand Down
32 changes: 32 additions & 0 deletions darwin/photo_manager/Sources/photo_manager/core/PMManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -1880,6 +1880,38 @@ - (void)favoriteWithId:(NSString *)id favorite:(BOOL)favorite block:(void (^)(BO
block(YES, nil);
}

- (void)updateDateTakenWithId:(NSString *)id timestamp:(NSNumber *)timestamp block:(void (^)(BOOL result, NSObject *error))block {
PHFetchResult *fetchResult = [PHAsset fetchAssetsWithLocalIdentifiers:@[id] options:[self singleFetchOptions]];
PHAsset *asset = [self getFirstObjFromFetchResult:fetchResult];
if (!asset) {
block(NO, [NSString stringWithFormat:@"Asset %@ not found.", id]);
return;
}

if (![asset canPerformEditOperation:PHAssetEditOperationProperties]) {
block(NO, [NSString stringWithFormat:@"The asset %@ cannot perform edit operation.", id]);
return;
}

NSTimeInterval timeInterval = [timestamp doubleValue];
NSDate *newDate = [NSDate dateWithTimeIntervalSince1970:timeInterval];

NSError *error;
BOOL succeed = [PHPhotoLibrary.sharedPhotoLibrary performChangesAndWait:^{
PHAssetChangeRequest *request = [PHAssetChangeRequest changeRequestForAsset:asset];
request.creationDate = newDate;
} error:&error];
if (!succeed) {
block(NO, [NSString stringWithFormat:@"Updating creation date for asset %@ failed: Request not succeed.", id]);
return;
}
if (error) {
block(NO, error);
return;
}
block(YES, nil);
}

- (NSString *)getCachePath:(NSString *)type {
NSString *homePath = NSTemporaryDirectory();
NSString *cachePath = type;
Expand Down
1 change: 1 addition & 0 deletions lib/src/internal/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class PMConstants {
static const String mRemoveInAlbum = 'removeInAlbum';
static const String mMoveAssetToPath = 'moveAssetToPath';
static const String mColumnNames = 'getColumnNames';
static const String mUpdateDateTaken = 'updateDateTaken';

static const String mGetAssetCount = 'getAssetCount';
static const String mGetAssetsByRange = 'getAssetsByRange';
Expand Down
63 changes: 63 additions & 0 deletions lib/src/internal/editor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,37 @@ class DarwinEditor {
);
}

/// Updates the creation date of the given [entity].
///
/// This method modifies the creation date metadata of the asset.
/// On iOS/macOS, this updates the `creationDate` property of the asset.
///
/// Returns the updated [AssetEntity] with the new creation date if the operation
/// was successful; otherwise, throws a [StateError].
///
/// Example:
/// ```dart
/// final newDate = DateTime(2023, 1, 15, 10, 30);
/// final updatedAsset = await PhotoManager().editor.darwin.updateDateTaken(
/// entity: myAsset,
/// dateTime: newDate,
/// );
/// ```
Future<AssetEntity> updateDateTaken({
required AssetEntity entity,
required DateTime dateTime,
}) async {
final bool result = await plugin.updateDateTaken(entity.id, dateTime);
if (result) {
final int timestampInSeconds = dateTime.millisecondsSinceEpoch ~/ 1000;
return entity.copyWith(createDateSecond: timestampInSeconds);
}
throw StateError(
'Failed to update date taken for asset '
'${entity.id}',
);
}

/// Save Live Photo to the gallery from the given [imageFile] and [videoFile].
///
/// {@macro photo_manager.Editor.TitleWhenSaving}
Expand Down Expand Up @@ -359,6 +390,38 @@ class AndroidEditor {
);
}

/// Updates the creation date of the given [entity].
///
/// This method modifies the creation date metadata of the asset.
/// On Android Q (API 29) and above, this updates the `DATE_TAKEN` field in MediaStore.
/// On Android P (API 28) and below, this method is not supported.
///
/// Returns the updated [AssetEntity] with the new creation date if the operation
/// was successful; otherwise, throws a [StateError].
///
/// Example:
/// ```dart
/// final newDate = DateTime(2023, 1, 15, 10, 30);
/// final updatedAsset = await PhotoManager().editor.android.updateDateTaken(
/// entity: myAsset,
/// dateTime: newDate,
/// );
/// ```
Future<AssetEntity> updateDateTaken({
required AssetEntity entity,
required DateTime dateTime,
}) async {
final bool result = await plugin.updateDateTaken(entity.id, dateTime);
if (result) {
final int timestampInSeconds = dateTime.millisecondsSinceEpoch ~/ 1000;
return entity.copyWith(createDateSecond: timestampInSeconds);
}
throw StateError(
'Failed to update date taken for asset '
'${entity.id}',
);
}

/// Moves the given [entity] to the specified [target] path.
///
/// Returns `true` if the move was successful; otherwise, `false`.
Expand Down
11 changes: 11 additions & 0 deletions lib/src/internal/plugin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,17 @@ class PhotoManagerPlugin with BasePlugin, IosPlugin, AndroidPlugin, OhosPlugin {
return result == true;
}

Future<bool> updateDateTaken(String id, DateTime dateTime) async {
final bool? result = await _channel.invokeMethod(
PMConstants.mUpdateDateTaken,
<String, dynamic>{
'id': id,
'timestamp': dateTime.millisecondsSinceEpoch ~/ 1000,
},
);
return result == true;
}

Future<void> ignorePermissionCheck(bool ignore) {
return _channel.invokeMethod(
PMConstants.mIgnorePermissionCheck,
Expand Down
Loading