feat(mobile): remove from album action

This commit is contained in:
wuzihao051119 2025-07-03 02:30:03 +08:00
parent ca78bc91b6
commit 83c5b167de
6 changed files with 84 additions and 1 deletions

View File

@ -1498,6 +1498,7 @@
"remove_custom_date_range": "Remove custom date range", "remove_custom_date_range": "Remove custom date range",
"remove_deleted_assets": "Remove Deleted Assets", "remove_deleted_assets": "Remove Deleted Assets",
"remove_from_album": "Remove from album", "remove_from_album": "Remove from album",
"remove_from_album_action_prompt": "{count} removed from the album",
"remove_from_favorites": "Remove from favorites", "remove_from_favorites": "Remove from favorites",
"remove_from_lock_folder_action_prompt": "{count} removed from the locked folder", "remove_from_lock_folder_action_prompt": "{count} removed from the locked folder",
"remove_from_locked_folder": "Remove from locked folder", "remove_from_locked_folder": "Remove from locked folder",

View File

@ -39,6 +39,20 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
query.orderBy(orderings); query.orderBy(orderings);
} }
Future<void> removeFromAlbum(List<String> ids, String albumId) {
return _db.batch((batch) {
for (final assetId in ids) {
batch.delete(
_db.remoteAlbumAssetEntity,
RemoteAlbumAssetEntityCompanion(
albumId: Value(albumId),
assetId: Value(assetId),
),
);
}
});
}
return query return query
.map( .map(
(row) => row.readTable(_db.remoteAlbumEntity).toDto( (row) => row.readTable(_db.remoteAlbumEntity).toDto(

View File

@ -1,16 +1,51 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/extensions/translate_extensions.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/presentation/widgets/action_buttons/base_action_button.widget.dart';
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
class RemoveFromAlbumActionButton extends ConsumerWidget { class RemoveFromAlbumActionButton extends ConsumerWidget {
const RemoveFromAlbumActionButton({super.key}); final ActionSource source;
const RemoveFromAlbumActionButton({super.key, required this.source});
void _onTap(BuildContext context, WidgetRef ref) async {
if (!context.mounted) {
return;
}
final result =
await ref.read(actionProvider.notifier).removeFromAlbum(source);
await ref.read(timelineServiceProvider).reloadBucket();
ref.read(multiSelectProvider.notifier).reset();
final successMessage = 'remove_from_album_action_prompt'.t(
context: context,
args: {'count': result.count.toString()},
);
if (context.mounted) {
ImmichToast.show(
context: context,
msg: result.success
? successMessage
: 'scaffold_body_error_occurred'.t(context: context),
gravity: ToastGravity.BOTTOM,
toastType: result.success ? ToastType.success : ToastType.error,
);
}
}
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
return BaseActionButton( return BaseActionButton(
iconData: Icons.remove_circle_outline, iconData: Icons.remove_circle_outline,
label: "remove_from_album".t(context: context), label: "remove_from_album".t(context: context),
onPressed: () => _onTap(context, ref);
); );
} }
} }

View File

@ -173,6 +173,21 @@ class ActionNotifier extends Notifier<void> {
} }
} }
Future<ActionResult> removeFromAlbum(ActionSource source) async {
final ids = _getOwnedRemoteForSource(source);
try {
await _service.removeFromAlbum(ids);
return ActionResult(count: ids.length, success: true);
} catch (error, stack) {
_logger.severe('Failed to remove assets from album', error, stack);
return ActionResult(
count: ids.length,
success: false,
error: error.toString(),
);
}
}
Future<ActionResult?> editLocation( Future<ActionResult?> editLocation(
ActionSource source, ActionSource source,
BuildContext context, BuildContext context,

View File

@ -66,6 +66,15 @@ class AssetApiRepository extends ApiRepository {
); );
} }
Future<void> removeFromAlbum(
List<String> ids,
String albumId
) async {
return _api.updateAlbum(
);
}
Future<void> updateLocation( Future<void> updateLocation(
List<String> ids, List<String> ids,
LatLng location, LatLng location,

View File

@ -5,6 +5,7 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/infrastructure/repositories/exif.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/exif.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/remote_asset.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/remote_asset.repository.dart';
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
import 'package:immich_mobile/providers/infrastructure/exif.provider.dart'; import 'package:immich_mobile/providers/infrastructure/exif.provider.dart';
import 'package:immich_mobile/repositories/asset_api.repository.dart'; import 'package:immich_mobile/repositories/asset_api.repository.dart';
import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/routing/router.dart';
@ -17,6 +18,7 @@ final actionServiceProvider = Provider<ActionService>(
ref.watch(assetApiRepositoryProvider), ref.watch(assetApiRepositoryProvider),
ref.watch(remoteAssetRepository), ref.watch(remoteAssetRepository),
ref.watch(remoteExifRepository), ref.watch(remoteExifRepository),
ref.watch(remoteAlbumRepository),
), ),
); );
@ -24,11 +26,13 @@ class ActionService {
final AssetApiRepository _assetApiRepository; final AssetApiRepository _assetApiRepository;
final DriftRemoteAssetRepository _remoteAssetRepository; final DriftRemoteAssetRepository _remoteAssetRepository;
final DriftRemoteExifRepository _remoteExifRepository; final DriftRemoteExifRepository _remoteExifRepository;
final DriftRemoteAlbumRepository _remoteAlbumRepository;
const ActionService( const ActionService(
this._assetApiRepository, this._assetApiRepository,
this._remoteAssetRepository, this._remoteAssetRepository,
this._remoteExifRepository, this._remoteExifRepository,
this._remoteAlbumRepository,
); );
Future<void> shareLink(List<String> remoteIds, BuildContext context) async { Future<void> shareLink(List<String> remoteIds, BuildContext context) async {
@ -93,6 +97,11 @@ class ActionService {
); );
} }
Future<void> removeFromAlbum(List<String> remoteIds, String albumId) async {
await _assetApiRepository.removeFromAlbum(remoteIds, albumId);
await _remoteAlbumRepository.removeFromAlbum(remoteIds, albumId);
}
Future<bool> editLocation( Future<bool> editLocation(
List<String> remoteIds, List<String> remoteIds,
BuildContext context, BuildContext context,