Skip to content

Commit e7d051d

Browse files
feat: drift edit time and date action (#20377)
* feat: drift edit time and date action * feat: add edit button on asset viewer bottom sheet * update localDateTime column in addition to createdAt to keep consistency * fix: dont update local dateTime Server calcs this anyway and it will be synced when the change is applied. We don't use localDateTime on mobile so there is no reason to update this value * fix: padding around edit icon in ListTile Co-authored-by: shenlong <[email protected]> * chore: format * fix: hide date edit control when asset does not have a remote * fix: pull timezones correctly from image --------- Co-authored-by: shenlong <[email protected]>
1 parent 86d31d7 commit e7d051d

File tree

12 files changed

+137
-8
lines changed

12 files changed

+137
-8
lines changed

i18n/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -835,6 +835,7 @@
835835
"edit_birthday": "Edit Birthday",
836836
"edit_date": "Edit date",
837837
"edit_date_and_time": "Edit date and time",
838+
"edit_date_and_time_action_prompt": "{count} date and time edited",
838839
"edit_description": "Edit description",
839840
"edit_description_prompt": "Please select a new description:",
840841
"edit_exclusion_pattern": "Edit exclusion pattern",

mobile/lib/infrastructure/repositories/remote_asset.repository.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,23 @@ class RemoteAssetRepository extends DriftDatabaseRepository {
186186
});
187187
}
188188

189+
Future<void> updateDateTime(List<String> ids, DateTime dateTime) {
190+
return _db.batch((batch) async {
191+
for (final id in ids) {
192+
batch.update(
193+
_db.remoteExifEntity,
194+
RemoteExifEntityCompanion(dateTimeOriginal: Value(dateTime)),
195+
where: (e) => e.assetId.equals(id),
196+
);
197+
batch.update(
198+
_db.remoteAssetEntity,
199+
RemoteAssetEntityCompanion(createdAt: Value(dateTime)),
200+
where: (e) => e.id.equals(id),
201+
);
202+
}
203+
});
204+
}
205+
189206
Future<void> stack(String userId, StackResponse stack) {
190207
return _db.transaction(() async {
191208
final stackIds = await _db.managers.stackEntity
Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,52 @@
11
import 'package:flutter/material.dart';
2+
import 'package:fluttertoast/fluttertoast.dart';
23
import 'package:hooks_riverpod/hooks_riverpod.dart';
4+
import 'package:immich_mobile/constants/enums.dart';
35
import 'package:immich_mobile/extensions/translate_extensions.dart';
46
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
7+
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
8+
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
9+
import 'package:immich_mobile/widgets/common/immich_toast.dart';
510

611
class EditDateTimeActionButton extends ConsumerWidget {
7-
const EditDateTimeActionButton({super.key});
12+
final ActionSource source;
13+
14+
const EditDateTimeActionButton({super.key, required this.source});
15+
16+
_onTap(BuildContext context, WidgetRef ref) async {
17+
if (!context.mounted) {
18+
return;
19+
}
20+
21+
final result = await ref.read(actionProvider.notifier).editDateTime(source, context);
22+
if (result == null) {
23+
return;
24+
}
25+
26+
ref.read(multiSelectProvider.notifier).reset();
27+
28+
final successMessage = 'edit_date_and_time_action_prompt'.t(
29+
context: context,
30+
args: {'count': result.count.toString()},
31+
);
32+
33+
if (context.mounted) {
34+
ImmichToast.show(
35+
context: context,
36+
msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context),
37+
gravity: ToastGravity.BOTTOM,
38+
toastType: result.success ? ToastType.success : ToastType.error,
39+
);
40+
}
41+
}
842

943
@override
1044
Widget build(BuildContext context, WidgetRef ref) {
1145
return BaseActionButton(
1246
maxWidth: 95.0,
1347
iconData: Icons.edit_calendar_outlined,
1448
label: "control_bottom_app_bar_edit_time".t(context: context),
49+
onPressed: () => _onTap(context, ref),
1550
);
1651
}
1752
}

mobile/lib/presentation/widgets/asset_viewer/bottom_sheet.widget.dart

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,12 +143,18 @@ class _AssetDetailBottomSheet extends ConsumerWidget {
143143
final exifInfo = ref.watch(currentAssetExifProvider).valueOrNull;
144144
final cameraTitle = _getCameraInfoTitle(exifInfo);
145145

146+
Future<void> editDateTime() async {
147+
await ref.read(actionProvider.notifier).editDateTime(ActionSource.viewer, context);
148+
}
149+
146150
return SliverList.list(
147151
children: [
148152
// Asset Date and Time
149153
_SheetTile(
150154
title: _getDateTime(context, asset),
151155
titleStyle: context.textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w600),
156+
trailing: asset.hasRemote ? const Icon(Icons.edit, size: 18) : null,
157+
onTap: asset.hasRemote ? () async => await editDateTime() : null,
152158
),
153159
if (exifInfo != null) _SheetAssetDescription(exif: exifInfo),
154160
const SheetPeopleDetails(),
@@ -194,11 +200,21 @@ class _AssetDetailBottomSheet extends ConsumerWidget {
194200
class _SheetTile extends StatelessWidget {
195201
final String title;
196202
final Widget? leading;
203+
final Widget? trailing;
197204
final String? subtitle;
198205
final TextStyle? titleStyle;
199206
final TextStyle? subtitleStyle;
200-
201-
const _SheetTile({required this.title, this.titleStyle, this.leading, this.subtitle, this.subtitleStyle});
207+
final VoidCallback? onTap;
208+
209+
const _SheetTile({
210+
required this.title,
211+
this.titleStyle,
212+
this.leading,
213+
this.subtitle,
214+
this.subtitleStyle,
215+
this.trailing,
216+
this.onTap,
217+
});
202218

203219
@override
204220
Widget build(BuildContext context) {
@@ -234,8 +250,10 @@ class _SheetTile extends StatelessWidget {
234250
title: titleWidget,
235251
titleAlignment: ListTileTitleAlignment.center,
236252
leading: leading,
253+
trailing: trailing,
237254
contentPadding: leading == null ? null : const EdgeInsets.only(left: 25),
238255
subtitle: subtitleWidget,
256+
onTap: onTap,
239257
);
240258
}
241259
}

mobile/lib/presentation/widgets/bottom_sheet/archive_bottom_sheet.widget.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class ArchiveBottomSheet extends ConsumerWidget {
4040
isTrashEnable
4141
? const TrashActionButton(source: ActionSource.timeline)
4242
: const DeletePermanentActionButton(source: ActionSource.timeline),
43-
const EditDateTimeActionButton(),
43+
const EditDateTimeActionButton(source: ActionSource.timeline),
4444
const EditLocationActionButton(source: ActionSource.timeline),
4545
const MoveToLockFolderActionButton(source: ActionSource.timeline),
4646
const StackActionButton(source: ActionSource.timeline),

mobile/lib/presentation/widgets/bottom_sheet/favorite_bottom_sheet.widget.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class FavoriteBottomSheet extends ConsumerWidget {
4040
isTrashEnable
4141
? const TrashActionButton(source: ActionSource.timeline)
4242
: const DeletePermanentActionButton(source: ActionSource.timeline),
43-
const EditDateTimeActionButton(),
43+
const EditDateTimeActionButton(source: ActionSource.timeline),
4444
const EditLocationActionButton(source: ActionSource.timeline),
4545
const MoveToLockFolderActionButton(source: ActionSource.timeline),
4646
const StackActionButton(source: ActionSource.timeline),

mobile/lib/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ class GeneralBottomSheet extends ConsumerWidget {
7676
if (multiselect.hasLocal || multiselect.hasMerged) ...[
7777
const DeleteLocalActionButton(source: ActionSource.timeline),
7878
],
79-
const EditDateTimeActionButton(),
79+
const EditDateTimeActionButton(source: ActionSource.timeline),
8080
const EditLocationActionButton(source: ActionSource.timeline),
8181
const MoveToLockFolderActionButton(source: ActionSource.timeline),
8282
const StackActionButton(source: ActionSource.timeline),

mobile/lib/presentation/widgets/bottom_sheet/remote_album_bottom_sheet.widget.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class RemoteAlbumBottomSheet extends ConsumerWidget {
4343
isTrashEnable
4444
? const TrashActionButton(source: ActionSource.timeline)
4545
: const DeletePermanentActionButton(source: ActionSource.timeline),
46-
const EditDateTimeActionButton(),
46+
const EditDateTimeActionButton(source: ActionSource.timeline),
4747
const EditLocationActionButton(source: ActionSource.timeline),
4848
const MoveToLockFolderActionButton(source: ActionSource.timeline),
4949
const StackActionButton(source: ActionSource.timeline),

mobile/lib/providers/infrastructure/action.provider.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,21 @@ class ActionNotifier extends Notifier<void> {
266266
}
267267
}
268268

269+
Future<ActionResult?> editDateTime(ActionSource source, BuildContext context) async {
270+
final ids = _getOwnedRemoteIdsForSource(source);
271+
try {
272+
final isEdited = await _service.editDateTime(ids, context);
273+
if (!isEdited) {
274+
return null;
275+
}
276+
277+
return ActionResult(count: ids.length, success: true);
278+
} catch (error, stack) {
279+
_logger.severe('Failed to edit date and time for assets', error, stack);
280+
return ActionResult(count: ids.length, success: false, error: error.toString());
281+
}
282+
}
283+
269284
Future<ActionResult> removeFromAlbum(ActionSource source, String albumId) async {
270285
final ids = _getRemoteIdsForSource(source);
271286
try {

mobile/lib/repositories/asset_api.repository.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ class AssetApiRepository extends ApiRepository {
6666
return _api.updateAssets(AssetBulkUpdateDto(ids: ids, latitude: location.latitude, longitude: location.longitude));
6767
}
6868

69+
Future<void> updateDateTime(List<String> ids, DateTime dateTime) async {
70+
return _api.updateAssets(AssetBulkUpdateDto(ids: ids, dateTimeOriginal: dateTime.toIso8601String()));
71+
}
72+
6973
Future<StackResponse> stack(List<String> ids) async {
7074
final responseDto = await checkNull(_stacksApi.createStack(StackCreateDto(assetIds: ids)));
7175

0 commit comments

Comments
 (0)