diff --git a/mobile/lib/modules/favorite/providers/favorite_provider.dart b/mobile/lib/modules/favorite/providers/favorite_provider.dart index 49176cd067..e702eeec62 100644 --- a/mobile/lib/modules/favorite/providers/favorite_provider.dart +++ b/mobile/lib/modules/favorite/providers/favorite_provider.dart @@ -34,6 +34,18 @@ class FavoriteSelectionNotifier extends StateNotifier> { state.contains(asset.id), ); } + + Future addToFavorites(Iterable assets) { + state = state.union(assets.map((a) => a.id).toSet()); + final futures = assets.map((a) => + ref.watch(assetProvider.notifier).toggleFavorite( + a, + true, + ), + ); + + return Future.wait(futures); + } } final favoriteProvider = diff --git a/mobile/lib/modules/home/ui/control_bottom_app_bar.dart b/mobile/lib/modules/home/ui/control_bottom_app_bar.dart index cf5359b2cc..3901d18e84 100644 --- a/mobile/lib/modules/home/ui/control_bottom_app_bar.dart +++ b/mobile/lib/modules/home/ui/control_bottom_app_bar.dart @@ -8,6 +8,7 @@ import 'package:immich_mobile/shared/models/album.dart'; class ControlBottomAppBar extends ConsumerWidget { final Function onShare; + final Function onFavorite; final Function onDelete; final Function(Album album) onAddToAlbum; final void Function() onCreateNewAlbum; @@ -18,6 +19,7 @@ class ControlBottomAppBar extends ConsumerWidget { const ControlBottomAppBar({ Key? key, required this.onShare, + required this.onFavorite, required this.onDelete, required this.sharedAlbums, required this.albums, @@ -37,6 +39,13 @@ class ControlBottomAppBar extends ConsumerWidget { onShare(); }, ), + ControlBoxButton( + iconData: Icons.star_rounded, + label: "Favorite", + onPressed: () { + onFavorite(); + }, + ), ControlBoxButton( iconData: Icons.delete_outline_rounded, label: "control_bottom_app_bar_delete".tr(), @@ -51,6 +60,7 @@ class ControlBottomAppBar extends ConsumerWidget { ); }, ), + ], ); } diff --git a/mobile/lib/modules/home/views/home_page.dart b/mobile/lib/modules/home/views/home_page.dart index f299d6e87c..a409ce73cf 100644 --- a/mobile/lib/modules/home/views/home_page.dart +++ b/mobile/lib/modules/home/views/home_page.dart @@ -10,6 +10,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/modules/album/providers/album.provider.dart'; import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart'; import 'package:immich_mobile/modules/album/services/album.service.dart'; +import 'package:immich_mobile/modules/favorite/providers/favorite_provider.dart'; import 'package:immich_mobile/modules/home/providers/multiselect.provider.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart'; import 'package:immich_mobile/modules/home/ui/control_bottom_app_bar.dart'; @@ -83,27 +84,39 @@ class HomePage extends HookConsumerWidget { selectionEnabledHook.value = false; } - void onDelete() { - ref.watch(assetProvider.notifier).deleteAssets(selection.value); - selectionEnabledHook.value = false; - } - - Iterable remoteOnlySelection() { + Iterable remoteOnlySelection({String? localErrorMessage}) { final Set assets = selection.value; final bool onlyRemote = assets.every((e) => e.isRemote); if (!onlyRemote) { - ImmichToast.show( - context: context, - msg: "Can not add local assets to albums yet, skipping", - gravity: ToastGravity.BOTTOM, - ); + if (localErrorMessage != null && localErrorMessage.isNotEmpty) { + ImmichToast.show( + context: context, + msg: localErrorMessage, + gravity: ToastGravity.BOTTOM, + ); + } return assets.where((a) => a.isRemote); } return assets; } + void onFavoriteAssets() { + final remoteAssests = remoteOnlySelection( + localErrorMessage: 'Can not favorite local assets yet, skipping', + ); + ref.watch(favoriteProvider.notifier).addToFavorites(remoteAssests); + selectionEnabledHook.value = false; + } + + void onDelete() { + ref.watch(assetProvider.notifier).deleteAssets(selection.value); + selectionEnabledHook.value = false; + } + void onAddToAlbum(Album album) async { - final Iterable assets = remoteOnlySelection(); + final Iterable assets = remoteOnlySelection( + localErrorMessage: "Can not add local assets to albums yet, skipping", + ); if (assets.isEmpty) { return; } @@ -142,7 +155,9 @@ class HomePage extends HookConsumerWidget { } void onCreateNewAlbum() async { - final Iterable assets = remoteOnlySelection(); + final Iterable assets = remoteOnlySelection( + localErrorMessage: "Can not add local assets to albums yet, skipping", + ); if (assets.isEmpty) { return; } @@ -221,6 +236,7 @@ class HomePage extends HookConsumerWidget { if (selectionEnabledHook.value) ControlBottomAppBar( onShare: onShareAssets, + onFavorite: onFavoriteAssets, onDelete: onDelete, onAddToAlbum: onAddToAlbum, albums: albums, diff --git a/mobile/lib/shared/providers/asset.provider.dart b/mobile/lib/shared/providers/asset.provider.dart index 6655955850..09da33f9c4 100644 --- a/mobile/lib/shared/providers/asset.provider.dart +++ b/mobile/lib/shared/providers/asset.provider.dart @@ -277,14 +277,12 @@ class AssetNotifier extends StateNotifier { return asset.isFavorite; } - await _updateAssetsState( - state.allAssets.map((a) { - if (asset.id == a.id) { - return Asset.remote(newAsset); - } - return a; - }).toList(), - ); + final index = state.allAssets.indexWhere((a) => asset.id == a.id); + if (index > 0) { + state.allAssets.removeAt(index); + state.allAssets.insert(index, Asset.remote(newAsset)); + _updateAssetsState(state.allAssets); + } return newAsset.isFavorite; }