mirror of
https://github.com/immich-app/immich
synced 2026-08-15 13:03:57 +00:00
feat: album action (#29770)
refactor: mobile album actions Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
parent
0a97b4b099
commit
1fa985babb
11 changed files with 247 additions and 201 deletions
|
|
@ -0,0 +1,52 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/enums.dart';
|
||||
import 'package:immich_mobile/generated/translations.g.dart';
|
||||
import 'package:immich_mobile/presentation/actions/action.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/toast.provider.dart';
|
||||
import 'package:immich_mobile/utils/error_handler.dart';
|
||||
|
||||
final _stateProvider = Provider.family.autoDispose<List<String>?, ActionSource>((ref, source) {
|
||||
final assets = ref.watch(assetsActionProvider(source));
|
||||
final assetIds = assets.remote().map((asset) => asset.id).toList(growable: false);
|
||||
return assetIds.isEmpty ? null : assetIds;
|
||||
});
|
||||
|
||||
class RemoveFromAlbumAction extends AssetActionBuilder {
|
||||
final String albumId;
|
||||
|
||||
const RemoveFromAlbumAction({required super.source, required this.albumId});
|
||||
|
||||
@override
|
||||
ActionItem? create(BuildContext context, WidgetRef ref) {
|
||||
final assetIds = ref.watch(_stateProvider(source));
|
||||
if (assetIds == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return .new(
|
||||
icon: Icons.remove_circle_outline,
|
||||
label: context.t.remove_from_album,
|
||||
onAction: () => _remove(context, ref, assetIds),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _remove(BuildContext context, WidgetRef ref, List<String> assetIds) async {
|
||||
final albumService = ref.read(remoteAlbumServiceProvider);
|
||||
final toastService = ref.read(toastServiceProvider);
|
||||
final clearSelection = ref.read(clearSelectionProvider(source));
|
||||
|
||||
try {
|
||||
final count = await albumService.removeAssets(albumId: albumId, assetIds: assetIds);
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
toastService.success(context.t.remove_from_album_action_prompt(count: count));
|
||||
clearSelection();
|
||||
} catch (error, stack) {
|
||||
handleError(error, stack: stack, description: "Failed to remove the assets from the album");
|
||||
}
|
||||
}
|
||||
}
|
||||
49
mobile/lib/presentation/actions/set_album_cover.action.dart
Normal file
49
mobile/lib/presentation/actions/set_album_cover.action.dart
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/enums.dart';
|
||||
import 'package:immich_mobile/generated/translations.g.dart';
|
||||
import 'package:immich_mobile/presentation/actions/action.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/toast.provider.dart';
|
||||
import 'package:immich_mobile/utils/error_handler.dart';
|
||||
|
||||
final _stateProvider = Provider.family.autoDispose<String?, ActionSource>((ref, source) {
|
||||
final assets = ref.watch(assetsActionProvider(source));
|
||||
return assets.remote().map((asset) => asset.id).singleOrNull;
|
||||
});
|
||||
|
||||
class SetAlbumCoverAction extends AssetActionBuilder {
|
||||
final String albumId;
|
||||
|
||||
const SetAlbumCoverAction({required super.source, required this.albumId});
|
||||
|
||||
@override
|
||||
ActionItem? create(BuildContext context, WidgetRef ref) {
|
||||
final assetId = ref.watch(_stateProvider(source));
|
||||
if (assetId == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return .new(
|
||||
icon: Icons.image_outlined,
|
||||
label: context.t.set_as_album_cover,
|
||||
onAction: () => _setCover(context, ref, assetId),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _setCover(BuildContext context, WidgetRef ref, String assetId) async {
|
||||
final message = context.t.album_cover_updated;
|
||||
final albumService = ref.read(remoteAlbumServiceProvider);
|
||||
final toastService = ref.read(toastServiceProvider);
|
||||
final clearSelection = ref.read(clearSelectionProvider(source));
|
||||
|
||||
try {
|
||||
await albumService.updateAlbum(albumId, thumbnailAssetId: assetId);
|
||||
toastService.success(message);
|
||||
clearSelection();
|
||||
} catch (error, stack) {
|
||||
handleError(error, stack: stack, description: "Failed to update the album cover");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
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/events.model.dart';
|
||||
import 'package:immich_mobile/domain/utils/event_stream.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/widgets/common/immich_toast.dart';
|
||||
|
||||
class RemoveFromAlbumActionButton extends ConsumerWidget {
|
||||
final String albumId;
|
||||
final ActionSource source;
|
||||
final bool iconOnly;
|
||||
final bool menuItem;
|
||||
|
||||
const RemoveFromAlbumActionButton({
|
||||
super.key,
|
||||
required this.albumId,
|
||||
required this.source,
|
||||
this.iconOnly = false,
|
||||
this.menuItem = false,
|
||||
});
|
||||
|
||||
Future<void> _onTap(BuildContext context, WidgetRef ref) async {
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (source == ActionSource.viewer) {
|
||||
EventStream.shared.emit(const ViewerReloadAssetEvent());
|
||||
}
|
||||
|
||||
final result = await ref.read(actionProvider.notifier).removeFromAlbum(source, albumId);
|
||||
ref.read(multiSelectProvider.notifier).reset();
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
final successMessage = 'remove_from_album_action_prompt'.t(
|
||||
context: context,
|
||||
args: {'count': result.count.toString()},
|
||||
);
|
||||
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
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return BaseActionButton(
|
||||
iconData: Icons.remove_circle_outline,
|
||||
label: "remove_from_album".t(context: context),
|
||||
iconOnly: iconOnly,
|
||||
menuItem: menuItem,
|
||||
onPressed: () => _onTap(context, ref),
|
||||
maxWidth: 100,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
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/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/widgets/common/immich_toast.dart';
|
||||
|
||||
class SetAlbumCoverActionButton extends ConsumerWidget {
|
||||
final String albumId;
|
||||
final ActionSource source;
|
||||
final bool iconOnly;
|
||||
final bool menuItem;
|
||||
|
||||
const SetAlbumCoverActionButton({
|
||||
super.key,
|
||||
required this.albumId,
|
||||
required this.source,
|
||||
this.iconOnly = false,
|
||||
this.menuItem = false,
|
||||
});
|
||||
|
||||
Future<void> _onTap(BuildContext context, WidgetRef ref) async {
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
final result = await ref.read(actionProvider.notifier).setAlbumCover(source, albumId);
|
||||
ref.read(multiSelectProvider.notifier).reset();
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
final successMessage = 'album_cover_updated'.t(context: context);
|
||||
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
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return BaseActionButton(
|
||||
iconData: Icons.image_outlined,
|
||||
label: 'set_as_album_cover'.t(context: context),
|
||||
iconOnly: iconOnly,
|
||||
menuItem: menuItem,
|
||||
onPressed: () => _onTap(context, ref),
|
||||
maxWidth: 100,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -10,12 +10,12 @@ import 'package:immich_mobile/presentation/actions/edit_datetime.action.dart';
|
|||
import 'package:immich_mobile/presentation/actions/edit_location.action.dart';
|
||||
import 'package:immich_mobile/presentation/actions/favorite.action.dart';
|
||||
import 'package:immich_mobile/presentation/actions/lock.action.dart';
|
||||
import 'package:immich_mobile/presentation/actions/remove_from_album.action.dart';
|
||||
import 'package:immich_mobile/presentation/actions/set_album_cover.action.dart';
|
||||
import 'package:immich_mobile/presentation/actions/share.action.dart';
|
||||
import 'package:immich_mobile/presentation/actions/share_link.action.dart';
|
||||
import 'package:immich_mobile/presentation/actions/stack.action.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/download_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/remove_from_album_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/set_album_cover.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/album/album_selector.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
|
||||
|
|
@ -104,9 +104,14 @@ class _RemoteAlbumBottomSheetState extends ConsumerState<RemoteAlbumBottomSheet>
|
|||
],
|
||||
],
|
||||
const ActionColumnButton(action: CleanupLocalAction(source: .timeline)),
|
||||
if (ownsAlbum) RemoveFromAlbumActionButton(source: ActionSource.timeline, albumId: widget.album.id),
|
||||
if (ownsAlbum && multiselect.selectedAssets.length == 1)
|
||||
SetAlbumCoverActionButton(source: ActionSource.timeline, albumId: widget.album.id),
|
||||
if (ownsAlbum) ...[
|
||||
ActionColumnButton(
|
||||
action: RemoveFromAlbumAction(source: .timeline, albumId: widget.album.id),
|
||||
),
|
||||
ActionColumnButton(
|
||||
action: SetAlbumCoverAction(source: .timeline, albumId: widget.album.id),
|
||||
),
|
||||
],
|
||||
],
|
||||
slivers: ownsAlbum
|
||||
? [const AddToAlbumHeader(), AlbumSelector(onAlbumSelected: addToAlbum, onKeyboardExpanded: onKeyboardExpand)]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue