mirror of
https://github.com/immich-app/immich.git
synced 2026-07-28 14:47:30 -07:00
refactor: mobile album actions
This commit is contained in:
@@ -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 AssetsActionState(: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
|
||||
ActionData? build(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 toast = ref.read(toastRepositoryProvider);
|
||||
final selection = ref.read(assetsActionProvider(source).notifier);
|
||||
|
||||
try {
|
||||
final count = await albumService.removeAssets(albumId: albumId, assetIds: assetIds);
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
toast.success(context.t.remove_from_album_action_prompt(count: count));
|
||||
selection.clearSelect();
|
||||
} catch (error, stack) {
|
||||
handleError(error, stack: stack, description: "Failed to remove the assets from the album");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 AssetsActionState(: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
|
||||
ActionData? build(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 toast = ref.read(toastRepositoryProvider);
|
||||
final selection = ref.read(assetsActionProvider(source).notifier);
|
||||
|
||||
try {
|
||||
await albumService.updateAlbum(albumId, thumbnailAssetId: assetId);
|
||||
toast.success(message);
|
||||
selection.clearSelect();
|
||||
} catch (error, stack) {
|
||||
handleError(error, stack: stack, description: "Failed to update the album cover");
|
||||
}
|
||||
}
|
||||
}
|
||||
-65
@@ -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,
|
||||
});
|
||||
|
||||
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();
|
||||
|
||||
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
|
||||
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,
|
||||
});
|
||||
|
||||
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();
|
||||
|
||||
final successMessage = 'album_cover_updated'.t(context: context);
|
||||
|
||||
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
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
+16
-11
@@ -4,18 +4,18 @@ import 'package:immich_mobile/constants/enums.dart';
|
||||
import 'package:immich_mobile/domain/models/album/album.model.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/actions/action.widget.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/archive.action.dart';
|
||||
import 'package:immich_mobile/presentation/actions/delete.action.dart';
|
||||
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/delete.action.dart';
|
||||
import 'package:immich_mobile/presentation/actions/lock.action.dart';
|
||||
import 'package:immich_mobile/presentation/actions/archive.action.dart';
|
||||
import 'package:immich_mobile/presentation/actions/stack.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)]
|
||||
|
||||
@@ -169,33 +169,6 @@ class ActionNotifier extends Notifier<void> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<ActionResult> removeFromAlbum(ActionSource source, String albumId) async {
|
||||
final ids = _getRemoteIdsForSource(source);
|
||||
try {
|
||||
final removedCount = await _service.removeFromAlbum(ids, albumId);
|
||||
return ActionResult(count: removedCount, 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> setAlbumCover(ActionSource source, String albumId) async {
|
||||
final assets = _getAssets(source);
|
||||
final asset = assets.first;
|
||||
if (asset is! RemoteAsset) {
|
||||
return const ActionResult(count: 1, success: false, error: 'Asset must be remote');
|
||||
}
|
||||
|
||||
try {
|
||||
await _service.setAlbumCover(albumId, asset.id);
|
||||
return const ActionResult(count: 1, success: true);
|
||||
} catch (error, stack) {
|
||||
_logger.severe('Failed to set album cover', error, stack);
|
||||
return ActionResult(count: 1, success: false, error: error.toString());
|
||||
}
|
||||
}
|
||||
|
||||
Future<ActionResult> updateDescription(ActionSource source, String description) async {
|
||||
final ids = _getRemoteIdsForSource(source);
|
||||
if (ids.length != 1) {
|
||||
|
||||
@@ -4,21 +4,16 @@ import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/domain/services/tag.service.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/remote_asset.repository.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||
import 'package:immich_mobile/repositories/asset_api.repository.dart';
|
||||
import 'package:immich_mobile/repositories/download.repository.dart';
|
||||
import 'package:immich_mobile/repositories/drift_album_api_repository.dart';
|
||||
import 'package:immich_mobile/widgets/common/tag_picker.dart';
|
||||
|
||||
final actionServiceProvider = Provider<ActionService>(
|
||||
(ref) => ActionService(
|
||||
ref.watch(assetApiRepositoryProvider),
|
||||
ref.watch(remoteAssetRepositoryProvider),
|
||||
ref.watch(driftAlbumApiRepositoryProvider),
|
||||
ref.watch(remoteAlbumRepository),
|
||||
ref.watch(downloadRepositoryProvider),
|
||||
ref.watch(tagServiceProvider),
|
||||
),
|
||||
@@ -27,16 +22,12 @@ final actionServiceProvider = Provider<ActionService>(
|
||||
class ActionService {
|
||||
final AssetApiRepository _assetApiRepository;
|
||||
final RemoteAssetRepository _remoteAssetRepository;
|
||||
final DriftAlbumApiRepository _albumApiRepository;
|
||||
final DriftRemoteAlbumRepository _remoteAlbumRepository;
|
||||
final DownloadRepository _downloadRepository;
|
||||
final TagService _tagService;
|
||||
|
||||
const ActionService(
|
||||
this._assetApiRepository,
|
||||
this._remoteAssetRepository,
|
||||
this._albumApiRepository,
|
||||
this._remoteAlbumRepository,
|
||||
this._downloadRepository,
|
||||
this._tagService,
|
||||
);
|
||||
@@ -53,14 +44,6 @@ class ActionService {
|
||||
return count;
|
||||
}
|
||||
|
||||
Future<int> removeFromAlbum(List<String> remoteIds, String albumId) async {
|
||||
final result = await _albumApiRepository.removeAssets(albumId, remoteIds);
|
||||
if (result.removed.isNotEmpty) {
|
||||
await _remoteAlbumRepository.removeAssets(albumId, result.removed);
|
||||
}
|
||||
return result.removed.length;
|
||||
}
|
||||
|
||||
Future<bool> updateDescription(String assetId, String description) async {
|
||||
// update remote first, then local to ensure consistency
|
||||
await _assetApiRepository.updateDescription(assetId, description);
|
||||
@@ -100,12 +83,4 @@ class ActionService {
|
||||
Future<List<bool>> downloadAll(List<RemoteAsset> assets) {
|
||||
return _downloadRepository.downloadAllAssets(assets);
|
||||
}
|
||||
|
||||
Future<bool> setAlbumCover(String albumId, String assetId) async {
|
||||
final owner = await _remoteAlbumRepository.getOwner(albumId);
|
||||
final updatedAlbum = await _albumApiRepository.updateAlbum(albumId, owner, thumbnailAssetId: assetId);
|
||||
await _remoteAlbumRepository.update(updatedAlbum);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -14,7 +14,9 @@ import 'package:immich_mobile/presentation/actions/cast.action.dart';
|
||||
import 'package:immich_mobile/presentation/actions/delete.action.dart';
|
||||
import 'package:immich_mobile/presentation/actions/lock.action.dart';
|
||||
import 'package:immich_mobile/presentation/actions/open_in_browser.action.dart';
|
||||
import 'package:immich_mobile/presentation/actions/remove_from_album.action.dart';
|
||||
import 'package:immich_mobile/presentation/actions/restore.action.dart';
|
||||
import 'package:immich_mobile/presentation/actions/set_album_cover.action.dart';
|
||||
import 'package:immich_mobile/presentation/actions/set_profile_picture.action.dart';
|
||||
import 'package:immich_mobile/presentation/actions/share.action.dart';
|
||||
import 'package:immich_mobile/presentation/actions/share_link.action.dart';
|
||||
@@ -24,8 +26,6 @@ import 'package:immich_mobile/presentation/actions/stack.action.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/download_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/like_activity_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/action_buttons/upload_action_button.widget.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
|
||||
@@ -185,17 +185,11 @@ enum ActionButtonType {
|
||||
ActionButtonType.removeFromLockFolder => ActionMenuItem(action: LockAction(source: context.source)),
|
||||
ActionButtonType.deleteLocal => ActionMenuItem(action: CleanupLocalAction(source: context.source)),
|
||||
ActionButtonType.upload => UploadActionButton(source: context.source, iconOnly: iconOnly, menuItem: menuItem),
|
||||
ActionButtonType.removeFromAlbum => RemoveFromAlbumActionButton(
|
||||
albumId: context.currentAlbum!.id,
|
||||
source: context.source,
|
||||
iconOnly: iconOnly,
|
||||
menuItem: menuItem,
|
||||
ActionButtonType.removeFromAlbum => ActionMenuItem(
|
||||
action: RemoveFromAlbumAction(source: context.source, albumId: context.currentAlbum!.id),
|
||||
),
|
||||
ActionButtonType.setAlbumCover => SetAlbumCoverActionButton(
|
||||
albumId: context.currentAlbum!.id,
|
||||
source: context.source,
|
||||
iconOnly: iconOnly,
|
||||
menuItem: menuItem,
|
||||
ActionButtonType.setAlbumCover => ActionMenuItem(
|
||||
action: SetAlbumCoverAction(source: context.source, albumId: context.currentAlbum!.id),
|
||||
),
|
||||
ActionButtonType.likeActivity => LikeActivityActionButton(iconOnly: iconOnly, menuItem: menuItem),
|
||||
ActionButtonType.unstack => ActionMenuItem(action: StackAction(source: context.source)),
|
||||
|
||||
@@ -17,8 +17,6 @@ void main() {
|
||||
|
||||
late MockAssetApiRepository assetApiRepository;
|
||||
late MockRemoteAssetRepository remoteAssetRepository;
|
||||
late MockDriftAlbumApiRepository albumApiRepository;
|
||||
late MockRemoteAlbumRepository remoteAlbumRepository;
|
||||
late MockDownloadRepository downloadRepository;
|
||||
late MockTagService tagService;
|
||||
|
||||
@@ -41,19 +39,10 @@ void main() {
|
||||
setUp(() {
|
||||
assetApiRepository = MockAssetApiRepository();
|
||||
remoteAssetRepository = MockRemoteAssetRepository();
|
||||
albumApiRepository = MockDriftAlbumApiRepository();
|
||||
remoteAlbumRepository = MockRemoteAlbumRepository();
|
||||
downloadRepository = MockDownloadRepository();
|
||||
tagService = MockTagService();
|
||||
|
||||
sut = ActionService(
|
||||
assetApiRepository,
|
||||
remoteAssetRepository,
|
||||
albumApiRepository,
|
||||
remoteAlbumRepository,
|
||||
downloadRepository,
|
||||
tagService,
|
||||
);
|
||||
sut = ActionService(assetApiRepository, remoteAssetRepository, downloadRepository, tagService);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/generated/translations.g.dart';
|
||||
import 'package:immich_mobile/presentation/actions/action.widget.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/providers/infrastructure/toast.provider.dart';
|
||||
import 'package:immich_ui/immich_ui.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
import '../../../service.mocks.dart';
|
||||
import '../../factories/local_asset_factory.dart';
|
||||
import '../../factories/remote_album_factory.dart';
|
||||
import '../../factories/remote_asset_factory.dart';
|
||||
import '../presentation_context.dart';
|
||||
|
||||
void main() {
|
||||
const albumId = 'album-1';
|
||||
|
||||
late PresentationContext context;
|
||||
late MockRemoteAlbumService albumService;
|
||||
|
||||
setUp(() async {
|
||||
context = await PresentationContext.create();
|
||||
albumService = context.service.album.service;
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
context.dispose();
|
||||
});
|
||||
|
||||
List<Override> withMockToast() => [toastRepositoryProvider.overrideWithValue(context.repository.toast)];
|
||||
|
||||
group('RemoveFromAlbumAction', () {
|
||||
Future<void> pumpRemove(WidgetTester tester, Set<BaseAsset> selection) => tester.pumpTestAction(
|
||||
context,
|
||||
const RemoveFromAlbumAction(source: .timeline, albumId: albumId),
|
||||
overrides: [...context.selected(selection), ...withMockToast()],
|
||||
);
|
||||
|
||||
testWidgets('removes every selected remote asset from the album', (tester) async {
|
||||
final first = RemoteAssetFactory.create();
|
||||
final second = RemoteAssetFactory.create();
|
||||
|
||||
await pumpRemove(tester, {first, second});
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
verify(() => albumService.removeAssets(albumId: albumId, assetIds: [first.id, second.id])).called(1);
|
||||
});
|
||||
|
||||
testWidgets('reports the count the server actually removed', (tester) async {
|
||||
when(
|
||||
() => albumService.removeAssets(
|
||||
albumId: any(named: 'albumId'),
|
||||
assetIds: any(named: 'assetIds'),
|
||||
),
|
||||
).thenAnswer((_) async => 1);
|
||||
|
||||
await pumpRemove(tester, {RemoteAssetFactory.create(), RemoteAssetFactory.create()});
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final message = verify(() => context.repository.toast.success(captureAny())).captured.single as String;
|
||||
expect(message, StaticTranslations.instance.remove_from_album_action_prompt(count: 1));
|
||||
});
|
||||
|
||||
testWidgets('is hidden for a local-only asset, which is not in the album', (tester) async {
|
||||
await tester.pumpTestWidget(
|
||||
context,
|
||||
const ActionIconButton(
|
||||
action: RemoveFromAlbumAction(source: .timeline, albumId: albumId),
|
||||
),
|
||||
overrides: context.selected({LocalAssetFactory.create()}),
|
||||
);
|
||||
|
||||
expect(find.byType(ImmichIconButton), findsNothing);
|
||||
});
|
||||
});
|
||||
|
||||
group('SetAlbumCoverAction', () {
|
||||
testWidgets('sets the single selected asset as the cover', (tester) async {
|
||||
final asset = RemoteAssetFactory.create();
|
||||
when(
|
||||
() => albumService.updateAlbum(any(), thumbnailAssetId: any(named: 'thumbnailAssetId')),
|
||||
).thenAnswer((_) async => RemoteAlbumFactory.create(id: albumId));
|
||||
|
||||
await tester.pumpTestAction(
|
||||
context,
|
||||
const SetAlbumCoverAction(source: .timeline, albumId: albumId),
|
||||
overrides: [
|
||||
...context.selected({asset}),
|
||||
...withMockToast(),
|
||||
],
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
verify(() => albumService.updateAlbum(albumId, thumbnailAssetId: asset.id)).called(1);
|
||||
|
||||
final message = verify(() => context.repository.toast.success(captureAny())).captured.single as String;
|
||||
expect(message, StaticTranslations.instance.album_cover_updated);
|
||||
});
|
||||
|
||||
testWidgets('is hidden for more than one asset, since a cover is a single image', (tester) async {
|
||||
await tester.pumpTestWidget(
|
||||
context,
|
||||
const ActionIconButton(
|
||||
action: SetAlbumCoverAction(source: .timeline, albumId: albumId),
|
||||
),
|
||||
overrides: context.selected({RemoteAssetFactory.create(), RemoteAssetFactory.create()}),
|
||||
);
|
||||
|
||||
expect(find.byType(ImmichIconButton), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('is hidden for a local-only asset, which has no remote id to point at', (tester) async {
|
||||
await tester.pumpTestWidget(
|
||||
context,
|
||||
const ActionIconButton(
|
||||
action: SetAlbumCoverAction(source: .timeline, albumId: albumId),
|
||||
),
|
||||
overrides: context.selected({LocalAssetFactory.create()}),
|
||||
);
|
||||
|
||||
expect(find.byType(ImmichIconButton), findsNothing);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
||||
import 'package:immich_mobile/presentation/actions/action.dart';
|
||||
import 'package:immich_mobile/presentation/actions/action.widget.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
|
||||
import 'package:immich_mobile/providers/routes.provider.dart';
|
||||
@@ -51,6 +52,7 @@ class PresentationContext {
|
||||
currentUserProvider.overrideWith((ref) => CurrentUserProvider(service.user.service)),
|
||||
assetServiceProvider.overrideWithValue(service.asset.service),
|
||||
cleanupServiceProvider.overrideWithValue(service.cleanup.service),
|
||||
remoteAlbumServiceProvider.overrideWithValue(service.album.service),
|
||||
partnerServiceProvider.overrideWithValue(service.partner.service),
|
||||
gCastServiceProvider.overrideWithValue(service.cast),
|
||||
serverInfoServiceProvider.overrideWithValue(service.serverInfo),
|
||||
|
||||
Reference in New Issue
Block a user