From 034889ca60cb6f88bb75d1d24fdfa7c17da57776 Mon Sep 17 00:00:00 2001 From: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> Date: Mon, 29 Jun 2026 16:28:31 +0530 Subject: [PATCH] feat: restore action --- mobile/lib/domain/services/asset.service.dart | 9 +++ .../presentation/actions/restore.action.dart | 31 ++++++++ .../restore_action_button.widget.dart | 55 -------------- .../restore_trash_action_button.widget.dart | 43 ----------- .../asset_viewer/bottom_bar.widget.dart | 10 +-- .../trash_bottom_sheet.widget.dart | 15 ++-- .../infrastructure/action.provider.dart | 11 --- mobile/lib/services/action.service.dart | 5 -- mobile/lib/utils/action_button.utils.dart | 8 +-- mobile/lib/utils/asset_filter.dart | 3 + .../test/infrastructure/repository.mock.dart | 5 +- .../unit/factories/remote_asset_factory.dart | 2 + mobile/test/unit/mocks.dart | 6 ++ .../actions/restore_action_test.dart | 71 +++++++++++++++++++ .../presentation/presentation_context.dart | 2 + 15 files changed, 146 insertions(+), 130 deletions(-) create mode 100644 mobile/lib/presentation/actions/restore.action.dart delete mode 100644 mobile/lib/presentation/widgets/action_buttons/restore_action_button.widget.dart delete mode 100644 mobile/lib/presentation/widgets/action_buttons/restore_trash_action_button.widget.dart create mode 100644 mobile/test/unit/presentation/actions/restore_action_test.dart diff --git a/mobile/lib/domain/services/asset.service.dart b/mobile/lib/domain/services/asset.service.dart index ca16d1f980..05c227e906 100644 --- a/mobile/lib/domain/services/asset.service.dart +++ b/mobile/lib/domain/services/asset.service.dart @@ -77,4 +77,13 @@ class AssetService { await _apiRepository.updateFavorite(remoteIds, isFavorite); await _remoteRepository.updateFavorite(remoteIds, isFavorite); } + + Future restoreTrash(List remoteIds) async { + if (remoteIds.isEmpty) { + return; + } + + await _apiRepository.restoreTrash(remoteIds); + await _remoteRepository.restoreTrash(remoteIds); + } } diff --git a/mobile/lib/presentation/actions/restore.action.dart b/mobile/lib/presentation/actions/restore.action.dart new file mode 100644 index 0000000000..7c24de4879 --- /dev/null +++ b/mobile/lib/presentation/actions/restore.action.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.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.dart'; +import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/toast.provider.dart'; +import 'package:immich_mobile/utils/asset_filter.dart'; + +class RestoreAction extends AssetAction { + const RestoreAction({required super.assets}); + + @override + IconData get icon => Icons.history_rounded; + + @override + String label(ActionScope scope) => scope.context.t.restore; + + @override + Iterable filter(ActionScope scope) => AssetFilter(assets).owned(scope.authUser.id).trashed(); + + @override + bool isVisible(ActionScope scope) => filter(scope).isNotEmpty; + + @override + Future onAction(ActionScope scope) async { + final ActionScope(:ref, :context) = scope; + final ids = filter(scope).map((asset) => asset.id).toList(growable: false); + await ref.read(assetServiceProvider).restoreTrash(ids); + ref.read(toastRepositoryProvider).success(context.t.assets_restored_count(count: ids.length)); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/restore_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/restore_action_button.widget.dart deleted file mode 100644 index 1713718967..0000000000 --- a/mobile/lib/presentation/widgets/action_buttons/restore_action_button.widget.dart +++ /dev/null @@ -1,55 +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 RestoreActionButton extends ConsumerWidget { - final ActionSource source; - final bool iconOnly; - final bool menuItem; - - const RestoreActionButton({super.key, 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).restoreTrash(source); - ref.read(multiSelectProvider.notifier).reset(); - - if (source == ActionSource.viewer) { - EventStream.shared.emit(const ViewerReloadAssetEvent()); - } - - final successMessage = 'assets_restored_count'.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.history_rounded, - label: 'restore'.t(context: context), - iconOnly: iconOnly, - menuItem: menuItem, - onPressed: () => _onTap(context, ref), - maxWidth: 100.0, - ); - } -} diff --git a/mobile/lib/presentation/widgets/action_buttons/restore_trash_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/restore_trash_action_button.widget.dart deleted file mode 100644 index e7928bd325..0000000000 --- a/mobile/lib/presentation/widgets/action_buttons/restore_trash_action_button.widget.dart +++ /dev/null @@ -1,43 +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/providers/infrastructure/action.provider.dart'; -import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; -import 'package:immich_mobile/widgets/common/immich_toast.dart'; - -class RestoreTrashActionButton extends ConsumerWidget { - final ActionSource source; - - const RestoreTrashActionButton({super.key, required this.source}); - - void _onTap(BuildContext context, WidgetRef ref) async { - if (!context.mounted) { - return; - } - - final result = await ref.read(actionProvider.notifier).restoreTrash(source); - ref.read(multiSelectProvider.notifier).reset(); - - final successMessage = 'assets_restored_count'.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 TextButton.icon( - icon: const Icon(Icons.history_rounded), - label: Text('restore'.t(), style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)), - onPressed: () => _onTap(context, ref), - ); - } -} diff --git a/mobile/lib/presentation/widgets/asset_viewer/bottom_bar.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/bottom_bar.widget.dart index 01a48e7e97..03ddb0de5f 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/bottom_bar.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/bottom_bar.widget.dart @@ -4,12 +4,13 @@ import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/services/timeline.service.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/presentation/actions/action.widget.dart'; +import 'package:immich_mobile/presentation/actions/restore.action.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/add_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/delete_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/edit_image_action_button.widget.dart'; -import 'package:immich_mobile/presentation/widgets/action_buttons/restore_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/ocr_toggle_button.widget.dart'; @@ -42,11 +43,10 @@ class ViewerBottomBar extends ConsumerWidget { final originalTheme = context.themeData; + final assets = [asset]; final actions = [ - if (isInTrash && isOwner && asset.hasRemote) - const RestoreActionButton(source: ActionSource.viewer) - else - const ShareActionButton(source: ActionSource.viewer), + ActionColumnButtonWidget(action: RestoreAction(assets: assets)), + const ShareActionButton(source: ActionSource.viewer), if (!isInLockedView) ...[ if (!isInTrash) ...[ diff --git a/mobile/lib/presentation/widgets/bottom_sheet/trash_bottom_sheet.widget.dart b/mobile/lib/presentation/widgets/bottom_sheet/trash_bottom_sheet.widget.dart index c96e680966..a198cb0b40 100644 --- a/mobile/lib/presentation/widgets/bottom_sheet/trash_bottom_sheet.widget.dart +++ b/mobile/lib/presentation/widgets/bottom_sheet/trash_bottom_sheet.widget.dart @@ -2,26 +2,33 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/presentation/actions/action.widget.dart'; +import 'package:immich_mobile/presentation/actions/restore.action.dart'; +import 'package:immich_mobile/presentation/actions/timeline.action.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/delete_trash_action_button.widget.dart'; -import 'package:immich_mobile/presentation/widgets/action_buttons/restore_trash_action_button.widget.dart'; +import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; class TrashBottomBar extends ConsumerWidget { const TrashBottomBar({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { + final assets = ref.watch(multiSelectProvider.select((s) => s.selectedAssets)).toList(growable: false); + return Align( alignment: Alignment.bottomCenter, child: Container( color: context.themeData.canvasColor, padding: const EdgeInsets.symmetric(vertical: 8), - child: const SafeArea( + child: SafeArea( top: false, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - DeleteTrashActionButton(source: ActionSource.timeline), - RestoreTrashActionButton(source: ActionSource.timeline), + const DeleteTrashActionButton(source: ActionSource.timeline), + ActionColumnButtonWidget( + action: TimelineAction(action: RestoreAction(assets: assets)), + ), ], ), ), diff --git a/mobile/lib/providers/infrastructure/action.provider.dart b/mobile/lib/providers/infrastructure/action.provider.dart index ed62b9a0e8..70068a27e3 100644 --- a/mobile/lib/providers/infrastructure/action.provider.dart +++ b/mobile/lib/providers/infrastructure/action.provider.dart @@ -235,17 +235,6 @@ class ActionNotifier extends Notifier { } } - Future restoreTrash(ActionSource source) async { - final ids = _getOwnedRemoteIdsForSource(source); - try { - await _service.restoreTrash(ids); - return ActionResult(count: ids.length, success: true); - } catch (error, stack) { - _logger.severe('Failed to restore trash assets', error, stack); - return ActionResult(count: ids.length, success: false, error: error.toString()); - } - } - Future emptyTrash(String userId) async { try { final count = await _service.emptyTrash(userId); diff --git a/mobile/lib/services/action.service.dart b/mobile/lib/services/action.service.dart index 19782c8512..0981942055 100644 --- a/mobile/lib/services/action.service.dart +++ b/mobile/lib/services/action.service.dart @@ -108,11 +108,6 @@ class ActionService { await _remoteAssetRepository.trash(remoteIds); } - Future restoreTrash(List ids) async { - await _assetApiRepository.restoreTrash(ids); - await _remoteAssetRepository.restoreTrash(ids); - } - Future emptyTrash(String userId) async { final count = await _assetApiRepository.emptyTrash(); await _remoteAssetRepository.emptyTrash(userId); diff --git a/mobile/lib/utils/action_button.utils.dart b/mobile/lib/utils/action_button.utils.dart index 0e5a3123e7..861fbff32a 100644 --- a/mobile/lib/utils/action_button.utils.dart +++ b/mobile/lib/utils/action_button.utils.dart @@ -9,6 +9,7 @@ import 'package:immich_mobile/domain/services/timeline.service.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/presentation/actions/action.widget.dart'; import 'package:immich_mobile/presentation/actions/asset_debug.action.dart'; +import 'package:immich_mobile/presentation/actions/restore.action.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/archive_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/cast_action_button.widget.dart'; @@ -21,7 +22,6 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/move_to_lock_f import 'package:immich_mobile/presentation/widgets/action_buttons/open_in_browser_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/remove_from_lock_folder_action_button.widget.dart'; -import 'package:immich_mobile/presentation/widgets/action_buttons/restore_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/set_profile_picture_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart'; @@ -208,11 +208,7 @@ enum ActionButtonType { ), ActionButtonType.download => DownloadActionButton(source: context.source, iconOnly: iconOnly, menuItem: menuItem), ActionButtonType.trash => TrashActionButton(source: context.source, iconOnly: iconOnly, menuItem: menuItem), - ActionButtonType.restoreTrash => RestoreActionButton( - source: context.source, - iconOnly: iconOnly, - menuItem: menuItem, - ), + ActionButtonType.restoreTrash => ActionMenuItemWidget(action: RestoreAction(assets: [context.asset])), ActionButtonType.deletePermanent => DeletePermanentActionButton( source: context.source, iconOnly: iconOnly, diff --git a/mobile/lib/utils/asset_filter.dart b/mobile/lib/utils/asset_filter.dart index ea660fdaf1..30483a5295 100644 --- a/mobile/lib/utils/asset_filter.dart +++ b/mobile/lib/utils/asset_filter.dart @@ -16,6 +16,8 @@ extension type const AssetFilter(Iterable assets) implem AssetFilter notArchived() => notVisibility(.archive); AssetFilter stacked() => remote().where(_isStacked); AssetFilter notStacked() => remote().whereNot(_isStacked); + AssetFilter trashed() => remote().where(_isTrashed); + AssetFilter notTrashed() => remote().whereNot(_isTrashed); AssetFilter local() => AssetFilter(assets.whereType()); AssetFilter backedUp() => local().where(_isBackedUp); @@ -23,6 +25,7 @@ extension type const AssetFilter(Iterable assets) implem bool _isFavorite(BaseAsset asset) => asset.isFavorite; bool _isStacked(RemoteAsset asset) => asset.isStacked; +bool _isTrashed(RemoteAsset asset) => asset.isTrashed; bool _isBackedUp(LocalAsset asset) => asset.remoteAssetId != null; bool Function(RemoteAsset asset) _hasVisibility(AssetVisibility visibility) => (asset) => asset.visibility == visibility; diff --git a/mobile/test/infrastructure/repository.mock.dart b/mobile/test/infrastructure/repository.mock.dart index 0688576682..ce3c05e802 100644 --- a/mobile/test/infrastructure/repository.mock.dart +++ b/mobile/test/infrastructure/repository.mock.dart @@ -3,9 +3,9 @@ import 'package:immich_mobile/infrastructure/repositories/local_album.repository import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/log.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/partner.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/settings.repository.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/infrastructure/repositories/settings.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.dart'; @@ -15,6 +15,7 @@ import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.re import 'package:immich_mobile/infrastructure/repositories/user.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/user_api.repository.dart'; import 'package:immich_mobile/repositories/drift_album_api_repository.dart'; +import 'package:immich_mobile/repositories/toast.repository.dart'; import 'package:immich_mobile/repositories/upload.repository.dart'; import 'package:mocktail/mocktail.dart'; @@ -50,6 +51,8 @@ class MockUserRepository extends Mock implements UserRepository {} class MockPartnerRepository extends Mock implements PartnerRepository {} +class MockToastRepository extends Mock implements ToastRepository {} + // API Repos class MockUserApiRepository extends Mock implements UserApiRepository {} diff --git a/mobile/test/unit/factories/remote_asset_factory.dart b/mobile/test/unit/factories/remote_asset_factory.dart index 3ab76ae15e..955884831d 100644 --- a/mobile/test/unit/factories/remote_asset_factory.dart +++ b/mobile/test/unit/factories/remote_asset_factory.dart @@ -12,6 +12,7 @@ class RemoteAssetFactory { bool isFavorite = false, AssetVisibility visibility = AssetVisibility.timeline, String? stackId, + DateTime? deletedAt, }) { id = TestUtils.uuid(id); @@ -27,6 +28,7 @@ class RemoteAssetFactory { visibility: visibility, stackId: stackId, isEdited: false, + deletedAt: deletedAt, ); } } diff --git a/mobile/test/unit/mocks.dart b/mobile/test/unit/mocks.dart index 06993f854d..5c57268421 100644 --- a/mobile/test/unit/mocks.dart +++ b/mobile/test/unit/mocks.dart @@ -18,6 +18,7 @@ class RepositoryMocks { final localAlbum = LocalAlbumRepositoryStub(MockLocalAlbumRepository()); final localAsset = LocalAssetRepositoryStub(MockDriftLocalAssetRepository()); final trashedAsset = MockTrashedLocalAssetRepository(); + final toast = MockToastRepository(); final nativeApi = NativeSyncApiStub(MockNativeSyncApi()); @@ -31,6 +32,7 @@ class RepositoryMocks { localAsset.reset(); reset(trashedAsset); nativeApi.reset(); + reset(toast); _stubLocalAlbumRepository(); _stubLocalAssetRepository(); _stubNativeSyncApi(); @@ -89,6 +91,7 @@ class ServiceMocks { void _stubAssetService() { when(asset.updateFavorite).thenAnswer((_) async {}); + when(asset.restoreTrash).thenAnswer((_) async {}); } } @@ -167,6 +170,9 @@ extension type const UserServiceStub(MockUserService service) implements Stub { Future Function() get updateFavorite => () => service.updateFavorite(any(), any()); + + Future Function() get restoreTrash => + () => service.restoreTrash(any()); } extension type const NativeSyncApiStub(MockNativeSyncApi api) implements Stub { diff --git a/mobile/test/unit/presentation/actions/restore_action_test.dart b/mobile/test/unit/presentation/actions/restore_action_test.dart new file mode 100644 index 0000000000..fe0a2dc4a8 --- /dev/null +++ b/mobile/test/unit/presentation/actions/restore_action_test.dart @@ -0,0 +1,71 @@ +import 'package:flutter_test/flutter_test.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/restore.action.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../../../domain/service.mock.dart'; +import '../../factories/remote_asset_factory.dart'; +import '../presentation_context.dart'; + +void main() { + late PresentationContext context; + late MockAssetService assetService; + + setUp(() async { + context = await PresentationContext.create(); + assetService = context.service.asset.service; + }); + + tearDown(() { + context.dispose(); + }); + + RemoteAsset owned({bool trashed = true}) => + RemoteAssetFactory.create(ownerId: context.currentUser.id, deletedAt: trashed ? DateTime(2020) : null); + + group('RestoreAction', () { + testWidgets('restores the eligible owned trashed assets', (tester) async { + final asset = owned(); + + await tester.pumpTestAction(context, RestoreAction(assets: [asset])); + + verify(() => assetService.restoreTrash([asset.id])).called(1); + }); + + testWidgets('ignores assets owned by someone else', (tester) async { + final mine = owned(); + final theirs = RemoteAssetFactory.create(deletedAt: DateTime(2020)); + + await tester.pumpTestAction(context, RestoreAction(assets: [mine, theirs])); + + verify(() => assetService.restoreTrash([mine.id])).called(1); + }); + + testWidgets('skips owned assets that are not trashed', (tester) async { + final trashed = owned(); + final live = owned(trashed: false); + + await tester.pumpTestAction(context, RestoreAction(assets: [trashed, live])); + + verify(() => assetService.restoreTrash([trashed.id])).called(1); + }); + + testWidgets('batches every eligible owned asset into a single call', (tester) async { + final first = owned(); + final second = owned(); + + await tester.pumpTestAction(context, RestoreAction(assets: [first, second])); + + verify(() => assetService.restoreTrash([first.id, second.id])).called(1); + }); + + testWidgets('reports success through the toast repository with the restored count', (tester) async { + final toast = context.repository.toast; + await tester.pumpTestAction(context, RestoreAction(assets: [owned(), owned()])); + + final message = verify(() => toast.success(captureAny())).captured.single as String; + expect(message, StaticTranslations.instance.assets_restored_count(count: 2)); + }); + }); +} diff --git a/mobile/test/unit/presentation/presentation_context.dart b/mobile/test/unit/presentation/presentation_context.dart index 585cb3707e..973f2bbc20 100644 --- a/mobile/test/unit/presentation/presentation_context.dart +++ b/mobile/test/unit/presentation/presentation_context.dart @@ -14,6 +14,7 @@ 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/asset.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/toast.provider.dart'; import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_ui/immich_ui.dart'; @@ -43,6 +44,7 @@ class PresentationContext { currentUserProvider.overrideWith((ref) => CurrentUserProvider(service.user.service)), assetServiceProvider.overrideWithValue(service.asset.service), partnerServiceProvider.overrideWithValue(service.partner.service), + toastRepositoryProvider.overrideWithValue(repository.toast), ]; static Future create() async {