mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
feat(mobile): trash/restore all (#28116)
* feat(mobile): trash/restore all * chore: remove themeData variable * chore: filter query by user * refactor --------- Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
@@ -164,6 +164,16 @@ class RemoteAssetRepository extends DriftDatabaseRepository {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> emptyTrash(String ownerId) async {
|
||||||
|
await _db.remoteAssetEntity.deleteWhere((t) => t.deletedAt.isNotNull() & t.ownerId.equals(ownerId));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> restoreAllTrash(String ownerId) async {
|
||||||
|
await (_db.remoteAssetEntity.update()..where((t) => t.deletedAt.isNotNull() & t.ownerId.equals(ownerId))).write(
|
||||||
|
const RemoteAssetEntityCompanion(deletedAt: Value(null)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> delete(List<String> ids) {
|
Future<void> delete(List<String> ids) {
|
||||||
return _db.batch((batch) {
|
return _db.batch((batch) {
|
||||||
for (final id in ids) {
|
for (final id in ids) {
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
import 'package:immich_mobile/generated/translations.g.dart';
|
import 'package:immich_mobile/generated/translations.g.dart';
|
||||||
|
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/bottom_sheet/trash_bottom_sheet.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/bottom_sheet/trash_bottom_sheet.widget.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||||
import 'package:immich_mobile/providers/user.provider.dart';
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
|
import 'package:immich_mobile/widgets/common/confirm_dialog.dart';
|
||||||
|
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
class DriftTrashPage extends StatelessWidget {
|
class DriftTrashPage extends StatelessWidget {
|
||||||
@@ -36,6 +41,7 @@ class DriftTrashPage extends StatelessWidget {
|
|||||||
pinned: true,
|
pinned: true,
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
|
actions: [const _TrashKebabMenu()],
|
||||||
),
|
),
|
||||||
topSliverWidgetHeight: 24,
|
topSliverWidgetHeight: 24,
|
||||||
topSliverWidget: Consumer(
|
topSliverWidget: Consumer(
|
||||||
@@ -53,3 +59,89 @@ class DriftTrashPage extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _TrashKebabMenu extends ConsumerWidget {
|
||||||
|
const _TrashKebabMenu();
|
||||||
|
|
||||||
|
Future<void> _confirmAndRun(
|
||||||
|
BuildContext context,
|
||||||
|
WidgetRef ref, {
|
||||||
|
required String title,
|
||||||
|
required String content,
|
||||||
|
required Future<ActionResult> Function(String userId) action,
|
||||||
|
required String Function(int count) successMsg,
|
||||||
|
}) async {
|
||||||
|
await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => ConfirmDialog(
|
||||||
|
title: title,
|
||||||
|
content: content,
|
||||||
|
onOk: () async {
|
||||||
|
final user = ref.read(currentUserProvider);
|
||||||
|
if (user == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final result = await action(user.id);
|
||||||
|
if (!context.mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ImmichToast.show(
|
||||||
|
context: context,
|
||||||
|
msg: result.success ? successMsg(result.count) : context.t.scaffold_body_error_occurred,
|
||||||
|
toastType: result.success ? ToastType.success : ToastType.error,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
return MenuAnchor(
|
||||||
|
consumeOutsideTap: true,
|
||||||
|
style: MenuStyle(
|
||||||
|
backgroundColor: WidgetStatePropertyAll(context.themeData.scaffoldBackgroundColor),
|
||||||
|
surfaceTintColor: const WidgetStatePropertyAll(Colors.grey),
|
||||||
|
elevation: const WidgetStatePropertyAll(4),
|
||||||
|
shape: const WidgetStatePropertyAll(
|
||||||
|
RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12))),
|
||||||
|
),
|
||||||
|
padding: const WidgetStatePropertyAll(EdgeInsets.symmetric(vertical: 6)),
|
||||||
|
),
|
||||||
|
menuChildren: [
|
||||||
|
BaseActionButton(
|
||||||
|
label: context.t.empty_trash,
|
||||||
|
iconData: Icons.delete_forever_outlined,
|
||||||
|
onPressed: () => _confirmAndRun(
|
||||||
|
context,
|
||||||
|
ref,
|
||||||
|
title: context.t.empty_trash,
|
||||||
|
content: context.t.empty_trash_confirmation,
|
||||||
|
action: ref.read(actionProvider.notifier).emptyTrash,
|
||||||
|
successMsg: (count) => context.t.assets_permanently_deleted_count(count: count),
|
||||||
|
),
|
||||||
|
menuItem: true,
|
||||||
|
),
|
||||||
|
BaseActionButton(
|
||||||
|
label: context.t.restore_all,
|
||||||
|
iconData: Icons.restore_outlined,
|
||||||
|
onPressed: () => _confirmAndRun(
|
||||||
|
context,
|
||||||
|
ref,
|
||||||
|
title: context.t.restore_all,
|
||||||
|
content: context.t.assets_restore_confirmation,
|
||||||
|
action: ref.read(actionProvider.notifier).restoreAllTrash,
|
||||||
|
successMsg: (count) => context.t.assets_restored_count(count: count),
|
||||||
|
),
|
||||||
|
menuItem: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
builder: (context, controller, child) {
|
||||||
|
return IconButton(
|
||||||
|
icon: const Icon(Icons.more_vert_rounded),
|
||||||
|
onPressed: () => controller.isOpen ? controller.close() : controller.open(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -239,6 +239,26 @@ class ActionNotifier extends Notifier<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<ActionResult> emptyTrash(String userId) async {
|
||||||
|
try {
|
||||||
|
final count = await _service.emptyTrash(userId);
|
||||||
|
return ActionResult(count: count, success: true);
|
||||||
|
} catch (error, stack) {
|
||||||
|
_logger.severe('Failed to empty trash', error, stack);
|
||||||
|
return ActionResult(count: 0, success: false, error: error.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ActionResult> restoreAllTrash(String userId) async {
|
||||||
|
try {
|
||||||
|
final count = await _service.restoreAllTrash(userId);
|
||||||
|
return ActionResult(count: count, success: true);
|
||||||
|
} catch (error, stack) {
|
||||||
|
_logger.severe('Failed to restore all trash assets', error, stack);
|
||||||
|
return ActionResult(count: 0, success: false, error: error.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<ActionResult> trashRemoteAndDeleteLocal(ActionSource source) async {
|
Future<ActionResult> trashRemoteAndDeleteLocal(ActionSource source) async {
|
||||||
final ids = _getOwnedRemoteIdsForSource(source);
|
final ids = _getOwnedRemoteIdsForSource(source);
|
||||||
final localIds = _getLocalIdsForSource(source);
|
final localIds = _getLocalIdsForSource(source);
|
||||||
|
|||||||
@@ -31,6 +31,16 @@ class AssetApiRepository extends ApiRepository {
|
|||||||
await _trashApi.restoreAssets(BulkIdsDto(ids: ids));
|
await _trashApi.restoreAssets(BulkIdsDto(ids: ids));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<int> emptyTrash() async {
|
||||||
|
final response = await _trashApi.emptyTrash();
|
||||||
|
return response?.count ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> restoreAllTrash() async {
|
||||||
|
final response = await _trashApi.restoreTrash();
|
||||||
|
return response?.count ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> updateVisibility(List<String> ids, AssetVisibilityEnum visibility) async {
|
Future<void> updateVisibility(List<String> ids, AssetVisibilityEnum visibility) async {
|
||||||
return _api.updateAssets(AssetBulkUpdateDto(ids: ids, visibility: _mapVisibility(visibility)));
|
return _api.updateAssets(AssetBulkUpdateDto(ids: ids, visibility: _mapVisibility(visibility)));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,6 +108,18 @@ class ActionService {
|
|||||||
await _remoteAssetRepository.restoreTrash(ids);
|
await _remoteAssetRepository.restoreTrash(ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<int> emptyTrash(String userId) async {
|
||||||
|
final count = await _assetApiRepository.emptyTrash();
|
||||||
|
await _remoteAssetRepository.emptyTrash(userId);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> restoreAllTrash(String userId) async {
|
||||||
|
final count = await _assetApiRepository.restoreAllTrash();
|
||||||
|
await _remoteAssetRepository.restoreAllTrash(userId);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> trashRemoteAndDeleteLocal(List<String> remoteIds, List<String> localIds) async {
|
Future<void> trashRemoteAndDeleteLocal(List<String> remoteIds, List<String> localIds) async {
|
||||||
await _assetApiRepository.delete(remoteIds, false);
|
await _assetApiRepository.delete(remoteIds, false);
|
||||||
await _remoteAssetRepository.trash(remoteIds);
|
await _remoteAssetRepository.trash(remoteIds);
|
||||||
|
|||||||
Reference in New Issue
Block a user