Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,14 @@ class RemoteAssetRepository extends DriftDatabaseRepository {
});
}

Future<void> emptyTrash() async {
await _db.remoteAssetEntity.deleteWhere((t) => t.deletedAt.isNotNull());
}

Future<void> restoreAllTrash() async {
await _db.remoteAssetEntity.update().write(const RemoteAssetEntityCompanion(deletedAt: Value(null)));
}

Future<void> delete(List<String> ids) {
return _db.batch((batch) {
for (final id in ids) {
Expand Down
87 changes: 87 additions & 0 deletions mobile/lib/presentation/pages/drift_trash.page.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.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/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/providers/infrastructure/action.provider.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/server_info.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()
class DriftTrashPage extends StatelessWidget {
Expand Down Expand Up @@ -36,6 +41,7 @@ class DriftTrashPage extends StatelessWidget {
pinned: true,
centerTitle: true,
elevation: 0,
actions: [const _TrashKebabMenu()],
),
topSliverWidgetHeight: 24,
topSliverWidget: Consumer(
Expand All @@ -53,3 +59,84 @@ class DriftTrashPage extends StatelessWidget {
);
}
}

class _TrashKebabMenu extends ConsumerWidget {
const _TrashKebabMenu();

Future<void> _onEmptyTrash(BuildContext context, WidgetRef ref) async {
final confirmed = await showDialog<bool>(
context: context,
builder: (context) =>
ConfirmDialog(title: context.t.empty_trash, content: context.t.empty_trash_confirmation, onOk: () {}),
);
if (confirmed == true && context.mounted) {
final result = await ref.read(actionProvider.notifier).emptyTrash();
if (context.mounted) {
ImmichToast.show(
context: context,
msg: result.success
? context.t.assets_permanently_deleted_count(count: result.count)
: context.t.scaffold_body_error_occurred,
toastType: result.success ? ToastType.success : ToastType.error,
);
}
}
}

Future<void> _onRestoreAll(BuildContext context, WidgetRef ref) async {
final confirmed = await showDialog<bool>(
context: context,
builder: (context) =>
ConfirmDialog(title: context.t.restore_all, content: context.t.assets_restore_confirmation, onOk: () {}),
);
if (confirmed == true && context.mounted) {
final result = await ref.read(actionProvider.notifier).restoreAllTrash();
if (context.mounted) {
ImmichToast.show(
context: context,
msg: result.success
? context.t.assets_restored_count(count: result.count)
: context.t.scaffold_body_error_occurred,
toastType: result.success ? ToastType.success : ToastType.error,
);
}
}
}

@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = context.themeData;
Comment thread
YarosMallorca marked this conversation as resolved.
Outdated
return MenuAnchor(
consumeOutsideTap: true,
style: MenuStyle(
backgroundColor: WidgetStatePropertyAll(theme.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: () => _onEmptyTrash(context, ref),
menuItem: true,
),
BaseActionButton(
label: context.t.restore_all,
iconData: Icons.restore_outlined,
onPressed: () => _onRestoreAll(context, ref),
menuItem: true,
),
],
builder: (context, controller, child) {
return IconButton(
icon: const Icon(Icons.more_vert_rounded),
onPressed: () => controller.isOpen ? controller.close() : controller.open(),
);
},
);
}
}
20 changes: 20 additions & 0 deletions mobile/lib/providers/infrastructure/action.provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,26 @@ class ActionNotifier extends Notifier<void> {
}
}

Future<ActionResult> emptyTrash() async {
try {
final count = await _service.emptyTrash();
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() async {
try {
final count = await _service.restoreAllTrash();
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 {
final ids = _getOwnedRemoteIdsForSource(source);
final localIds = _getLocalIdsForSource(source);
Expand Down
10 changes: 10 additions & 0 deletions mobile/lib/repositories/asset_api.repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ class AssetApiRepository extends ApiRepository {
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 {
return _api.updateAssets(AssetBulkUpdateDto(ids: ids, visibility: _mapVisibility(visibility)));
}
Expand Down
12 changes: 12 additions & 0 deletions mobile/lib/services/action.service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,18 @@ class ActionService {
await _remoteAssetRepository.restoreTrash(ids);
}

Future<int> emptyTrash() async {
final count = await _assetApiRepository.emptyTrash();
await _remoteAssetRepository.emptyTrash();
return count;
}

Future<int> restoreAllTrash() async {
final count = await _assetApiRepository.restoreAllTrash();
await _remoteAssetRepository.restoreAllTrash();
return count;
}

Future<void> trashRemoteAndDeleteLocal(List<String> remoteIds, List<String> localIds) async {
await _assetApiRepository.delete(remoteIds, false);
await _remoteAssetRepository.trash(remoteIds);
Expand Down
Loading