From 32a7087883ca3537fd8ba946b73fe8f4ecc0ef76 Mon Sep 17 00:00:00 2001 From: Daimolean <92239625+wuzihao051119@users.noreply.github.com> Date: Tue, 1 Jul 2025 03:38:15 +0800 Subject: [PATCH] feat(mobile): archive action (#19630) * feat(mobile): archive action * fix: lint * Update i18n/en.json Co-authored-by: Alex * fix: lint * fix: lint --------- Co-authored-by: Alex --- i18n/en.json | 1 + .../repositories/remote_asset.repository.dart | 13 ++++ .../archive_action_button.widget.dart | 59 ++++++++++++++++++- .../home_bottom_app_bar.widget.dart | 2 +- .../infrastructure/action.provider.dart | 8 +++ mobile/lib/services/action.service.dart | 32 ++++++++++ mobile/lib/services/api.service.dart | 2 +- 7 files changed, 114 insertions(+), 3 deletions(-) diff --git a/i18n/en.json b/i18n/en.json index 92f2d3c35f..1eff23eca9 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -427,6 +427,7 @@ "app_settings": "App Settings", "appears_in": "Appears in", "archive": "Archive", + "archive_action_prompt": "{count} added to Archive", "archive_or_unarchive_photo": "Archive or unarchive photo", "archive_page_no_archived_assets": "No archived assets found", "archive_page_title": "Archive ({count})", diff --git a/mobile/lib/infrastructure/repositories/remote_asset.repository.dart b/mobile/lib/infrastructure/repositories/remote_asset.repository.dart index ef347cfa75..909c3b3910 100644 --- a/mobile/lib/infrastructure/repositories/remote_asset.repository.dart +++ b/mobile/lib/infrastructure/repositories/remote_asset.repository.dart @@ -1,5 +1,6 @@ import 'package:drift/drift.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; @@ -23,4 +24,16 @@ class RemoteAssetRepository extends DriftDatabaseRepository { } }); } + + Future updateVisibility(List ids, AssetVisibility visibility) { + return _db.batch((batch) async { + for (final id in ids) { + batch.update( + _db.remoteAssetEntity, + RemoteAssetEntityCompanion(visibility: Value(visibility)), + where: (e) => e.id.equals(id), + ); + } + }); + } } diff --git a/mobile/lib/presentation/widgets/action_buttons/archive_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/archive_action_button.widget.dart index 90209b171d..046ee051fc 100644 --- a/mobile/lib/presentation/widgets/action_buttons/archive_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/archive_action_button.widget.dart @@ -1,16 +1,73 @@ import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; +import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; class ArchiveActionButton extends ConsumerWidget { - const ArchiveActionButton({super.key}); + final ActionSource source; + + const ArchiveActionButton({super.key, required this.source}); + + onAction(BuildContext context, WidgetRef ref) { + switch (source) { + case ActionSource.timeline: + timelineAction(context, ref); + case ActionSource.viewer: + viewerAction(ref); + } + } + + void timelineAction(BuildContext context, WidgetRef ref) { + final user = ref.read(currentUserProvider); + if (user == null) { + return; + } + + final ids = ref + .read(multiSelectProvider.select((value) => value.selectedAssets)) + .whereType() + .where((asset) => asset.ownerId == user.id) + .map((asset) => asset.id) + .toList(); + + if (ids.isEmpty) { + return; + } + + ref.read(actionProvider.notifier).archive(ids); + ref.read(multiSelectProvider.notifier).reset(); + + final toastMessage = 'archive_action_prompt'.t( + context: context, + args: {'count': ids.length.toString()}, + ); + + if (context.mounted) { + ImmichToast.show( + context: context, + msg: toastMessage, + gravity: ToastGravity.BOTTOM, + ); + } + } + + void viewerAction(WidgetRef _) { + UnimplementedError("Viewer action for archive is not implemented yet."); + } @override Widget build(BuildContext context, WidgetRef ref) { return BaseActionButton( iconData: Icons.archive_outlined, label: "archive".t(context: context), + onPressed: () => onAction(context, ref), ); } } diff --git a/mobile/lib/presentation/widgets/bottom_app_bar/home_bottom_app_bar.widget.dart b/mobile/lib/presentation/widgets/bottom_app_bar/home_bottom_app_bar.widget.dart index e2c2c2898f..e09d14c4e8 100644 --- a/mobile/lib/presentation/widgets/bottom_app_bar/home_bottom_app_bar.widget.dart +++ b/mobile/lib/presentation/widgets/bottom_app_bar/home_bottom_app_bar.widget.dart @@ -35,7 +35,7 @@ class HomeBottomAppBar extends ConsumerWidget { const ShareActionButton(), if (multiselect.hasRemote) ...[ const ShareLinkActionButton(source: ActionSource.timeline), - const ArchiveActionButton(), + const ArchiveActionButton(source: ActionSource.timeline), const FavoriteActionButton(source: ActionSource.timeline), const DownloadActionButton(), isTrashEnable diff --git a/mobile/lib/providers/infrastructure/action.provider.dart b/mobile/lib/providers/infrastructure/action.provider.dart index 18b7378dd2..f5fb83bd5a 100644 --- a/mobile/lib/providers/infrastructure/action.provider.dart +++ b/mobile/lib/providers/infrastructure/action.provider.dart @@ -25,4 +25,12 @@ class ActionNotifier extends Notifier { Future unFavorite(List ids) async { await _service.unFavorite(ids); } + + Future archive(List ids) async { + await _service.archive(ids); + } + + Future unArchive(List ids) async { + await _service.unArchive(ids); + } } diff --git a/mobile/lib/services/action.service.dart b/mobile/lib/services/action.service.dart index 9affff9804..29afb3a331 100644 --- a/mobile/lib/services/action.service.dart +++ b/mobile/lib/services/action.service.dart @@ -1,4 +1,6 @@ import 'package:flutter/foundation.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/infrastructure/repositories/remote_asset.repository.dart'; import 'package:immich_mobile/repositories/asset_api.repository.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -33,4 +35,34 @@ class ActionService { debugPrint('Error unfavoriting assets: $e'); } } + + Future archive(List remoteIds) async { + try { + await _assetApiRepository.updateVisibility( + remoteIds, + AssetVisibilityEnum.archive, + ); + await _remoteAssetRepository.updateVisibility( + remoteIds, + AssetVisibility.archive, + ); + } catch (e) { + debugPrint('Error archive assets: $e'); + } + } + + Future unArchive(List remoteIds) async { + try { + await _assetApiRepository.updateVisibility( + remoteIds, + AssetVisibilityEnum.timeline, + ); + await _remoteAssetRepository.updateVisibility( + remoteIds, + AssetVisibility.timeline, + ); + } catch (e) { + debugPrint('Error unarchive assets: $e'); + } + } } diff --git a/mobile/lib/services/api.service.dart b/mobile/lib/services/api.service.dart index fe007a2aab..65da247eac 100644 --- a/mobile/lib/services/api.service.dart +++ b/mobile/lib/services/api.service.dart @@ -121,7 +121,7 @@ class ApiService implements Authentication { try { await setEndpoint(serverUrl); - await serverInfoApi.pingServer().timeout(const Duration(seconds: 5)); + await serverInfoApi.pingServer().timeout(const Duration(seconds: 60)); } on TimeoutException catch (_) { return false; } on SocketException catch (_) {