forked from Cutlery/immich
		
	feat(mobile): Archive feature on mobile (#2258)
* update asset to include isArchive property * Not display archived assets on timeline * replace share button to archive button * Added archive page * Add bottom nav bar * clean up homepage * remove deadcode * improve on sync is archive * show archive asset correctly * better merge condition * Added back renderList to re-rendering don't jump around * Better way to handle showing archive assets * complete ArchiveSelectionNotifier * toggle archive * remove deadcode * fix unit tests * update assets in DB when changing assets * update asset state to reflect archived status * allow to archive assets via multi-select from timeline * fixed logic * Add options to bulk unarchive * regenerate api * Change position of toast message --------- Co-authored-by: Fynn Petersen-Frey <zoodyy@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									635eee9e5e
								
							
						
					
					
						commit
						2e5cd986dd
					
				@ -111,6 +111,7 @@
 | 
				
			|||||||
  "control_bottom_app_bar_create_new_album": "Create new album",
 | 
					  "control_bottom_app_bar_create_new_album": "Create new album",
 | 
				
			||||||
  "control_bottom_app_bar_delete": "Delete",
 | 
					  "control_bottom_app_bar_delete": "Delete",
 | 
				
			||||||
  "control_bottom_app_bar_favorite": "Favorite",
 | 
					  "control_bottom_app_bar_favorite": "Favorite",
 | 
				
			||||||
 | 
					  "control_bottom_app_bar_archive": "Archive",
 | 
				
			||||||
  "control_bottom_app_bar_share": "Share",
 | 
					  "control_bottom_app_bar_share": "Share",
 | 
				
			||||||
  "create_album_page_untitled": "Untitled",
 | 
					  "create_album_page_untitled": "Untitled",
 | 
				
			||||||
  "create_shared_album_page_create": "Create",
 | 
					  "create_shared_album_page_create": "Create",
 | 
				
			||||||
@ -139,6 +140,7 @@
 | 
				
			|||||||
  "home_page_add_to_album_success": "Added {added} assets to album {album}.",
 | 
					  "home_page_add_to_album_success": "Added {added} assets to album {album}.",
 | 
				
			||||||
  "home_page_building_timeline": "Building the timeline",
 | 
					  "home_page_building_timeline": "Building the timeline",
 | 
				
			||||||
  "home_page_favorite_err_local": "Can not favorite local assets yet, skipping",
 | 
					  "home_page_favorite_err_local": "Can not favorite local assets yet, skipping",
 | 
				
			||||||
 | 
					  "home_page_archive_err_local": "Can not archive local assets yet, skipping",
 | 
				
			||||||
  "home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).",
 | 
					  "home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).",
 | 
				
			||||||
  "image_viewer_page_state_provider_download_error": "Download Error",
 | 
					  "image_viewer_page_state_provider_download_error": "Download Error",
 | 
				
			||||||
  "image_viewer_page_state_provider_download_success": "Download Success",
 | 
					  "image_viewer_page_state_provider_download_success": "Download Success",
 | 
				
			||||||
@ -147,6 +149,7 @@
 | 
				
			|||||||
  "library_page_favorites": "Favorites",
 | 
					  "library_page_favorites": "Favorites",
 | 
				
			||||||
  "library_page_new_album": "New album",
 | 
					  "library_page_new_album": "New album",
 | 
				
			||||||
  "library_page_sharing": "Sharing",
 | 
					  "library_page_sharing": "Sharing",
 | 
				
			||||||
 | 
					  "library_page_archive": "Archive",
 | 
				
			||||||
  "library_page_sort_created": "Most recently created",
 | 
					  "library_page_sort_created": "Most recently created",
 | 
				
			||||||
  "library_page_sort_title": "Album title",
 | 
					  "library_page_sort_title": "Album title",
 | 
				
			||||||
  "login_form_api_exception": "API exception. Please check the server URL and try again.",
 | 
					  "login_form_api_exception": "API exception. Please check the server URL and try again.",
 | 
				
			||||||
@ -268,5 +271,6 @@
 | 
				
			|||||||
  "advanced_settings_troubleshooting_title": "Troubleshooting",
 | 
					  "advanced_settings_troubleshooting_title": "Troubleshooting",
 | 
				
			||||||
  "advanced_settings_troubleshooting_subtitle": "Enable additional features for troubleshooting",
 | 
					  "advanced_settings_troubleshooting_subtitle": "Enable additional features for troubleshooting",
 | 
				
			||||||
  "description_input_submit_error": "Error updating description, check the log for more details",
 | 
					  "description_input_submit_error": "Error updating description, check the log for more details",
 | 
				
			||||||
  "description_input_hint_text": "Add description..."
 | 
					  "description_input_hint_text": "Add description...",
 | 
				
			||||||
 | 
					  "archive_page_title": "Archive ({})"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -192,12 +192,15 @@ class ImmichAppState extends ConsumerState<ImmichApp>
 | 
				
			|||||||
    SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
 | 
					    SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Sets the navigation bar color
 | 
					    // Sets the navigation bar color
 | 
				
			||||||
    SystemUiOverlayStyle overlayStyle = const SystemUiOverlayStyle(systemNavigationBarColor: Colors.transparent);
 | 
					    SystemUiOverlayStyle overlayStyle = const SystemUiOverlayStyle(
 | 
				
			||||||
 | 
					      systemNavigationBarColor: Colors.transparent,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
    if (Platform.isAndroid) {
 | 
					    if (Platform.isAndroid) {
 | 
				
			||||||
      // Android 8 does not support transparent app bars
 | 
					      // Android 8 does not support transparent app bars
 | 
				
			||||||
      final info = await DeviceInfoPlugin().androidInfo;
 | 
					      final info = await DeviceInfoPlugin().androidInfo;
 | 
				
			||||||
      if (info.version.sdkInt <= 26) {
 | 
					      if (info.version.sdkInt <= 26) {
 | 
				
			||||||
        overlayStyle = MediaQuery.of(context).platformBrightness == Brightness.light
 | 
					        overlayStyle =
 | 
				
			||||||
 | 
					            MediaQuery.of(context).platformBrightness == Brightness.light
 | 
				
			||||||
                ? SystemUiOverlayStyle.light
 | 
					                ? SystemUiOverlayStyle.light
 | 
				
			||||||
                : SystemUiOverlayStyle.dark;
 | 
					                : SystemUiOverlayStyle.dark;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@ -213,9 +216,6 @@ class ImmichAppState extends ConsumerState<ImmichApp>
 | 
				
			|||||||
      // needs to be delayed so that EasyLocalization is working
 | 
					      // needs to be delayed so that EasyLocalization is working
 | 
				
			||||||
      ref.read(backgroundServiceProvider).resumeServiceIfEnabled();
 | 
					      ref.read(backgroundServiceProvider).resumeServiceIfEnabled();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
 | 
				
			|||||||
@ -51,14 +51,14 @@ class AddToAlbumBottomSheet extends HookConsumerWidget {
 | 
				
			|||||||
          ImmichToast.show(
 | 
					          ImmichToast.show(
 | 
				
			||||||
            context: context,
 | 
					            context: context,
 | 
				
			||||||
            msg: 'add_to_album_bottom_sheet_already_exists'.tr(
 | 
					            msg: 'add_to_album_bottom_sheet_already_exists'.tr(
 | 
				
			||||||
              namedArgs: { "album": album.name },
 | 
					              namedArgs: {"album": album.name},
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          ImmichToast.show(
 | 
					          ImmichToast.show(
 | 
				
			||||||
            context: context,
 | 
					            context: context,
 | 
				
			||||||
            msg: 'add_to_album_bottom_sheet_added'.tr(
 | 
					            msg: 'add_to_album_bottom_sheet_added'.tr(
 | 
				
			||||||
              namedArgs: { "album": album.name },
 | 
					              namedArgs: {"album": album.name},
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -71,6 +71,7 @@ class AddToAlbumBottomSheet extends HookConsumerWidget {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return Card(
 | 
					    return Card(
 | 
				
			||||||
 | 
					      elevation: 0,
 | 
				
			||||||
      shape: const RoundedRectangleBorder(
 | 
					      shape: const RoundedRectangleBorder(
 | 
				
			||||||
        borderRadius: BorderRadius.only(
 | 
					        borderRadius: BorderRadius.only(
 | 
				
			||||||
          topLeft: Radius.circular(15),
 | 
					          topLeft: Radius.circular(15),
 | 
				
			||||||
@ -99,8 +100,15 @@ class AddToAlbumBottomSheet extends HookConsumerWidget {
 | 
				
			|||||||
                        style: Theme.of(context).textTheme.displayMedium,
 | 
					                        style: Theme.of(context).textTheme.displayMedium,
 | 
				
			||||||
                      ),
 | 
					                      ),
 | 
				
			||||||
                      TextButton.icon(
 | 
					                      TextButton.icon(
 | 
				
			||||||
                        icon: const Icon(Icons.add),
 | 
					                        icon: Icon(
 | 
				
			||||||
                        label: Text('common_create_new_album'.tr()),
 | 
					                          Icons.add,
 | 
				
			||||||
 | 
					                          color: Theme.of(context).primaryColor,
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                        label: Text(
 | 
				
			||||||
 | 
					                          'common_create_new_album'.tr(),
 | 
				
			||||||
 | 
					                          style:
 | 
				
			||||||
 | 
					                              TextStyle(color: Theme.of(context).primaryColor),
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
                        onPressed: () {
 | 
					                        onPressed: () {
 | 
				
			||||||
                          ref
 | 
					                          ref
 | 
				
			||||||
                              .watch(assetSelectionProvider.notifier)
 | 
					                              .watch(assetSelectionProvider.notifier)
 | 
				
			||||||
 | 
				
			|||||||
@ -43,7 +43,8 @@ class LibraryPage extends HookConsumerWidget {
 | 
				
			|||||||
      );
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    final selectedAlbumSortOrder = useState(settings.getSetting(AppSettingsEnum.selectedAlbumSortOrder));
 | 
					    final selectedAlbumSortOrder =
 | 
				
			||||||
 | 
					        useState(settings.getSetting(AppSettingsEnum.selectedAlbumSortOrder));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    List<Album> sortedAlbums() {
 | 
					    List<Album> sortedAlbums() {
 | 
				
			||||||
      if (selectedAlbumSortOrder.value == 0) {
 | 
					      if (selectedAlbumSortOrder.value == 0) {
 | 
				
			||||||
@ -179,13 +180,13 @@ class LibraryPage extends HookConsumerWidget {
 | 
				
			|||||||
              label,
 | 
					              label,
 | 
				
			||||||
              style: TextStyle(
 | 
					              style: TextStyle(
 | 
				
			||||||
                fontWeight: FontWeight.bold,
 | 
					                fontWeight: FontWeight.bold,
 | 
				
			||||||
                fontSize: 12.0,
 | 
					                fontSize: 13.0,
 | 
				
			||||||
                color: isDarkMode ? Colors.white : Colors.black,
 | 
					                color: isDarkMode ? Colors.white : Colors.grey[800],
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
          style: OutlinedButton.styleFrom(
 | 
					          style: OutlinedButton.styleFrom(
 | 
				
			||||||
            padding: const EdgeInsets.all(12),
 | 
					            padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
 | 
				
			||||||
            backgroundColor: isDarkMode ? Colors.grey[900] : Colors.grey[50],
 | 
					            backgroundColor: isDarkMode ? Colors.grey[900] : Colors.grey[50],
 | 
				
			||||||
            side: BorderSide(
 | 
					            side: BorderSide(
 | 
				
			||||||
              color: isDarkMode ? Colors.grey[800]! : Colors.grey[300]!,
 | 
					              color: isDarkMode ? Colors.grey[800]! : Colors.grey[300]!,
 | 
				
			||||||
@ -225,8 +226,8 @@ class LibraryPage extends HookConsumerWidget {
 | 
				
			|||||||
                  }),
 | 
					                  }),
 | 
				
			||||||
                  const SizedBox(width: 12.0),
 | 
					                  const SizedBox(width: 12.0),
 | 
				
			||||||
                  buildLibraryNavButton(
 | 
					                  buildLibraryNavButton(
 | 
				
			||||||
                      "library_page_sharing".tr(), Icons.group_outlined, () {
 | 
					                      "library_page_archive".tr(), Icons.archive_outlined, () {
 | 
				
			||||||
                    AutoRouter.of(context).navigate(const SharingRoute());
 | 
					                    AutoRouter.of(context).navigate(const ArchiveRoute());
 | 
				
			||||||
                  }),
 | 
					                  }),
 | 
				
			||||||
                ],
 | 
					                ],
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,55 @@
 | 
				
			|||||||
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/shared/models/asset.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/shared/providers/asset.provider.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/shared/providers/db.provider.dart';
 | 
				
			||||||
 | 
					import 'package:isar/isar.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ArchiveSelectionNotifier extends StateNotifier<Set<int>> {
 | 
				
			||||||
 | 
					  ArchiveSelectionNotifier(this.db, this.assetNotifier) : super({}) {
 | 
				
			||||||
 | 
					    state = db.assets
 | 
				
			||||||
 | 
					        .filter()
 | 
				
			||||||
 | 
					        .isArchivedEqualTo(true)
 | 
				
			||||||
 | 
					        .findAllSync()
 | 
				
			||||||
 | 
					        .map((e) => e.id)
 | 
				
			||||||
 | 
					        .toSet();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final Isar db;
 | 
				
			||||||
 | 
					  final AssetNotifier assetNotifier;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void _setArchiveForAssetId(int id, bool archive) {
 | 
				
			||||||
 | 
					    if (!archive) {
 | 
				
			||||||
 | 
					      state = state.difference({id});
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      state = state.union({id});
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bool _isArchive(int id) {
 | 
				
			||||||
 | 
					    return state.contains(id);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> toggleArchive(Asset asset) async {
 | 
				
			||||||
 | 
					    if (!asset.isRemote) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _setArchiveForAssetId(asset.id, !_isArchive(asset.id));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await assetNotifier.toggleArchive(
 | 
				
			||||||
 | 
					      [asset],
 | 
				
			||||||
 | 
					      state.contains(asset.id),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> addToArchives(Iterable<Asset> assets) {
 | 
				
			||||||
 | 
					    state = state.union(assets.map((a) => a.id).toSet());
 | 
				
			||||||
 | 
					    return assetNotifier.toggleArchive(assets, true);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final archiveProvider =
 | 
				
			||||||
 | 
					    StateNotifierProvider<ArchiveSelectionNotifier, Set<int>>((ref) {
 | 
				
			||||||
 | 
					  return ArchiveSelectionNotifier(
 | 
				
			||||||
 | 
					    ref.watch(dbProvider),
 | 
				
			||||||
 | 
					    ref.watch(assetProvider.notifier),
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										0
									
								
								mobile/lib/modules/archive/ui/store_ui_here.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								mobile/lib/modules/archive/ui/store_ui_here.txt
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										124
									
								
								mobile/lib/modules/archive/views/archive_page.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								mobile/lib/modules/archive/views/archive_page.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,124 @@
 | 
				
			|||||||
 | 
					import 'package:auto_route/auto_route.dart';
 | 
				
			||||||
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
 | 
				
			||||||
 | 
					import 'package:fluttertoast/fluttertoast.dart';
 | 
				
			||||||
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/shared/models/asset.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/shared/models/store.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/shared/models/user.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/shared/providers/asset.provider.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/shared/providers/db.provider.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/shared/ui/immich_toast.dart';
 | 
				
			||||||
 | 
					import 'package:isar/isar.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ArchivePage extends HookConsumerWidget {
 | 
				
			||||||
 | 
					  const ArchivePage({super.key});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context, WidgetRef ref) {
 | 
				
			||||||
 | 
					    final User me = Store.get(StoreKey.currentUser);
 | 
				
			||||||
 | 
					    final query = ref
 | 
				
			||||||
 | 
					        .watch(dbProvider)
 | 
				
			||||||
 | 
					        .assets
 | 
				
			||||||
 | 
					        .filter()
 | 
				
			||||||
 | 
					        .ownerIdEqualTo(me.isarId)
 | 
				
			||||||
 | 
					        .isArchivedEqualTo(true);
 | 
				
			||||||
 | 
					    final stream = query.watch();
 | 
				
			||||||
 | 
					    final archivedAssets = useState<List<Asset>>([]);
 | 
				
			||||||
 | 
					    final selectionEnabledHook = useState(false);
 | 
				
			||||||
 | 
					    final selection = useState(<Asset>{});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    useEffect(
 | 
				
			||||||
 | 
					      () {
 | 
				
			||||||
 | 
					        query.findAll().then((value) => archivedAssets.value = value);
 | 
				
			||||||
 | 
					        final subscription = stream.listen((e) {
 | 
				
			||||||
 | 
					          archivedAssets.value = e;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        // Cancel the subscription when the widget is disposed
 | 
				
			||||||
 | 
					        return subscription.cancel;
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      [],
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void selectionListener(
 | 
				
			||||||
 | 
					      bool multiselect,
 | 
				
			||||||
 | 
					      Set<Asset> selectedAssets,
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					      selectionEnabledHook.value = multiselect;
 | 
				
			||||||
 | 
					      selection.value = selectedAssets;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    AppBar buildAppBar() {
 | 
				
			||||||
 | 
					      return AppBar(
 | 
				
			||||||
 | 
					        leading: IconButton(
 | 
				
			||||||
 | 
					          onPressed: () => AutoRouter.of(context).pop(),
 | 
				
			||||||
 | 
					          icon: const Icon(Icons.arrow_back_ios_rounded),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        centerTitle: true,
 | 
				
			||||||
 | 
					        automaticallyImplyLeading: false,
 | 
				
			||||||
 | 
					        title: const Text(
 | 
				
			||||||
 | 
					          'archive_page_title',
 | 
				
			||||||
 | 
					        ).tr(args: [archivedAssets.value.length.toString()]),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Widget buildBottomBar() {
 | 
				
			||||||
 | 
					      return Align(
 | 
				
			||||||
 | 
					        alignment: Alignment.bottomCenter,
 | 
				
			||||||
 | 
					        child: SizedBox(
 | 
				
			||||||
 | 
					          height: 64,
 | 
				
			||||||
 | 
					          child: Card(
 | 
				
			||||||
 | 
					            child: Column(
 | 
				
			||||||
 | 
					              children: [
 | 
				
			||||||
 | 
					                ListTile(
 | 
				
			||||||
 | 
					                  shape: RoundedRectangleBorder(
 | 
				
			||||||
 | 
					                    borderRadius: BorderRadius.circular(10),
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
 | 
					                  leading: const Icon(
 | 
				
			||||||
 | 
					                    Icons.unarchive_rounded,
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
 | 
					                  title:
 | 
				
			||||||
 | 
					                      const Text("Unarchive", style: TextStyle(fontSize: 14)),
 | 
				
			||||||
 | 
					                  onTap: () {
 | 
				
			||||||
 | 
					                    if (selection.value.isNotEmpty) {
 | 
				
			||||||
 | 
					                      ref
 | 
				
			||||||
 | 
					                          .watch(assetProvider.notifier)
 | 
				
			||||||
 | 
					                          .toggleArchive(selection.value, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                      final assetOrAssets =
 | 
				
			||||||
 | 
					                          selection.value.length > 1 ? 'assets' : 'asset';
 | 
				
			||||||
 | 
					                      ImmichToast.show(
 | 
				
			||||||
 | 
					                        context: context,
 | 
				
			||||||
 | 
					                        msg:
 | 
				
			||||||
 | 
					                            'Moved ${selection.value.length} $assetOrAssets to library',
 | 
				
			||||||
 | 
					                        gravity: ToastGravity.CENTER,
 | 
				
			||||||
 | 
					                      );
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    selectionEnabledHook.value = false;
 | 
				
			||||||
 | 
					                  },
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					              ],
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Scaffold(
 | 
				
			||||||
 | 
					      appBar: buildAppBar(),
 | 
				
			||||||
 | 
					      body: Stack(
 | 
				
			||||||
 | 
					        children: [
 | 
				
			||||||
 | 
					          ImmichAssetGrid(
 | 
				
			||||||
 | 
					            assets: archivedAssets.value,
 | 
				
			||||||
 | 
					            listener: selectionListener,
 | 
				
			||||||
 | 
					            selectionActive: selectionEnabledHook.value,
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					          if (selectionEnabledHook.value) buildBottomBar()
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -9,8 +9,6 @@ class TopControlAppBar extends HookConsumerWidget {
 | 
				
			|||||||
    required this.asset,
 | 
					    required this.asset,
 | 
				
			||||||
    required this.onMoreInfoPressed,
 | 
					    required this.onMoreInfoPressed,
 | 
				
			||||||
    required this.onDownloadPressed,
 | 
					    required this.onDownloadPressed,
 | 
				
			||||||
    required this.onSharePressed,
 | 
					 | 
				
			||||||
    required this.onDeletePressed,
 | 
					 | 
				
			||||||
    required this.onAddToAlbumPressed,
 | 
					    required this.onAddToAlbumPressed,
 | 
				
			||||||
    required this.onToggleMotionVideo,
 | 
					    required this.onToggleMotionVideo,
 | 
				
			||||||
    required this.isPlayingMotionVideo,
 | 
					    required this.isPlayingMotionVideo,
 | 
				
			||||||
@ -22,10 +20,8 @@ class TopControlAppBar extends HookConsumerWidget {
 | 
				
			|||||||
  final Function onMoreInfoPressed;
 | 
					  final Function onMoreInfoPressed;
 | 
				
			||||||
  final VoidCallback? onDownloadPressed;
 | 
					  final VoidCallback? onDownloadPressed;
 | 
				
			||||||
  final VoidCallback onToggleMotionVideo;
 | 
					  final VoidCallback onToggleMotionVideo;
 | 
				
			||||||
  final VoidCallback onDeletePressed;
 | 
					 | 
				
			||||||
  final VoidCallback onAddToAlbumPressed;
 | 
					  final VoidCallback onAddToAlbumPressed;
 | 
				
			||||||
  final VoidCallback onFavorite;
 | 
					  final VoidCallback onFavorite;
 | 
				
			||||||
  final Function onSharePressed;
 | 
					 | 
				
			||||||
  final bool isPlayingMotionVideo;
 | 
					  final bool isPlayingMotionVideo;
 | 
				
			||||||
  final bool isFavorite;
 | 
					  final bool isFavorite;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -86,15 +82,6 @@ class TopControlAppBar extends HookConsumerWidget {
 | 
				
			|||||||
              color: Colors.grey[200],
 | 
					              color: Colors.grey[200],
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
        IconButton(
 | 
					 | 
				
			||||||
          onPressed: () {
 | 
					 | 
				
			||||||
            onSharePressed();
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
          icon: Icon(
 | 
					 | 
				
			||||||
            Icons.ios_share_rounded,
 | 
					 | 
				
			||||||
            color: Colors.grey[200],
 | 
					 | 
				
			||||||
          ),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        if (asset.isRemote)
 | 
					        if (asset.isRemote)
 | 
				
			||||||
          IconButton(
 | 
					          IconButton(
 | 
				
			||||||
            onPressed: () {
 | 
					            onPressed: () {
 | 
				
			||||||
@ -105,15 +92,6 @@ class TopControlAppBar extends HookConsumerWidget {
 | 
				
			|||||||
              color: Colors.grey[200],
 | 
					              color: Colors.grey[200],
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
        IconButton(
 | 
					 | 
				
			||||||
          onPressed: () {
 | 
					 | 
				
			||||||
            onDeletePressed();
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
          icon: Icon(
 | 
					 | 
				
			||||||
            Icons.delete_outline_rounded,
 | 
					 | 
				
			||||||
            color: Colors.grey[200],
 | 
					 | 
				
			||||||
          ),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        IconButton(
 | 
					        IconButton(
 | 
				
			||||||
          onPressed: () {
 | 
					          onPressed: () {
 | 
				
			||||||
            onMoreInfoPressed();
 | 
					            onMoreInfoPressed();
 | 
				
			||||||
 | 
				
			|||||||
@ -231,11 +231,10 @@ class GalleryViewerPage extends HookConsumerWidget {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    void addToAlbum(Asset addToAlbumAsset) {
 | 
					    void addToAlbum(Asset addToAlbumAsset) {
 | 
				
			||||||
      showModalBottomSheet(
 | 
					      showModalBottomSheet(
 | 
				
			||||||
 | 
					        elevation: 0,
 | 
				
			||||||
        shape: RoundedRectangleBorder(
 | 
					        shape: RoundedRectangleBorder(
 | 
				
			||||||
          borderRadius: BorderRadius.circular(15.0),
 | 
					          borderRadius: BorderRadius.circular(15.0),
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        barrierColor: Colors.transparent,
 | 
					 | 
				
			||||||
        backgroundColor: Colors.transparent,
 | 
					 | 
				
			||||||
        context: context,
 | 
					        context: context,
 | 
				
			||||||
        builder: (BuildContext _) {
 | 
					        builder: (BuildContext _) {
 | 
				
			||||||
          return AddToAlbumBottomSheet(
 | 
					          return AddToAlbumBottomSheet(
 | 
				
			||||||
@ -267,6 +266,19 @@ class GalleryViewerPage extends HookConsumerWidget {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    shareAsset() {
 | 
				
			||||||
 | 
					      ref
 | 
				
			||||||
 | 
					          .watch(imageViewerStateProvider.notifier)
 | 
				
			||||||
 | 
					          .shareAsset(assetList[indexOfAsset.value], context);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    handleArchive(Asset asset) {
 | 
				
			||||||
 | 
					      ref
 | 
				
			||||||
 | 
					          .watch(assetProvider.notifier)
 | 
				
			||||||
 | 
					          .toggleArchive([asset], !asset.isArchived);
 | 
				
			||||||
 | 
					      AutoRouter.of(context).pop();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    buildAppBar() {
 | 
					    buildAppBar() {
 | 
				
			||||||
      final show = (showAppBar.value || // onTap has the final say
 | 
					      final show = (showAppBar.value || // onTap has the final say
 | 
				
			||||||
              (showAppBar.value && !isZoomed.value)) &&
 | 
					              (showAppBar.value && !isZoomed.value)) &&
 | 
				
			||||||
@ -297,16 +309,9 @@ class GalleryViewerPage extends HookConsumerWidget {
 | 
				
			|||||||
                          context,
 | 
					                          context,
 | 
				
			||||||
                        );
 | 
					                        );
 | 
				
			||||||
                  },
 | 
					                  },
 | 
				
			||||||
            onSharePressed: () {
 | 
					 | 
				
			||||||
              ref
 | 
					 | 
				
			||||||
                  .watch(imageViewerStateProvider.notifier)
 | 
					 | 
				
			||||||
                  .shareAsset(assetList[indexOfAsset.value], context);
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            onToggleMotionVideo: (() {
 | 
					            onToggleMotionVideo: (() {
 | 
				
			||||||
              isPlayingMotionVideo.value = !isPlayingMotionVideo.value;
 | 
					              isPlayingMotionVideo.value = !isPlayingMotionVideo.value;
 | 
				
			||||||
            }),
 | 
					            }),
 | 
				
			||||||
            onDeletePressed: () =>
 | 
					 | 
				
			||||||
                handleDelete((assetList[indexOfAsset.value])),
 | 
					 | 
				
			||||||
            onAddToAlbumPressed: () =>
 | 
					            onAddToAlbumPressed: () =>
 | 
				
			||||||
                addToAlbum(assetList[indexOfAsset.value]),
 | 
					                addToAlbum(assetList[indexOfAsset.value]),
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
@ -314,6 +319,59 @@ class GalleryViewerPage extends HookConsumerWidget {
 | 
				
			|||||||
      );
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    buildBottomBar() {
 | 
				
			||||||
 | 
					      final show = (showAppBar.value || // onTap has the final say
 | 
				
			||||||
 | 
					              (showAppBar.value && !isZoomed.value)) &&
 | 
				
			||||||
 | 
					          !isPlayingVideo.value;
 | 
				
			||||||
 | 
					      final currentAsset = assetList[indexOfAsset.value];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return AnimatedOpacity(
 | 
				
			||||||
 | 
					        duration: const Duration(milliseconds: 100),
 | 
				
			||||||
 | 
					        opacity: show ? 1.0 : 0.0,
 | 
				
			||||||
 | 
					        child: BottomNavigationBar(
 | 
				
			||||||
 | 
					          backgroundColor: Colors.black.withOpacity(0.4),
 | 
				
			||||||
 | 
					          unselectedIconTheme: const IconThemeData(color: Colors.white),
 | 
				
			||||||
 | 
					          selectedIconTheme: const IconThemeData(color: Colors.white),
 | 
				
			||||||
 | 
					          unselectedLabelStyle: const TextStyle(color: Colors.black),
 | 
				
			||||||
 | 
					          selectedLabelStyle: const TextStyle(color: Colors.black),
 | 
				
			||||||
 | 
					          showSelectedLabels: false,
 | 
				
			||||||
 | 
					          showUnselectedLabels: false,
 | 
				
			||||||
 | 
					          items: [
 | 
				
			||||||
 | 
					            const BottomNavigationBarItem(
 | 
				
			||||||
 | 
					              icon: Icon(Icons.ios_share_rounded),
 | 
				
			||||||
 | 
					              label: 'Share',
 | 
				
			||||||
 | 
					              tooltip: 'Share',
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            BottomNavigationBarItem(
 | 
				
			||||||
 | 
					              icon: currentAsset.isArchived
 | 
				
			||||||
 | 
					                  ? const Icon(Icons.unarchive_rounded)
 | 
				
			||||||
 | 
					                  : const Icon(Icons.archive_outlined),
 | 
				
			||||||
 | 
					              label: 'Archive',
 | 
				
			||||||
 | 
					              tooltip: 'Archive',
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            const BottomNavigationBarItem(
 | 
				
			||||||
 | 
					              icon: Icon(Icons.delete_outline),
 | 
				
			||||||
 | 
					              label: 'Delete',
 | 
				
			||||||
 | 
					              tooltip: 'Delete',
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          ],
 | 
				
			||||||
 | 
					          onTap: (index) {
 | 
				
			||||||
 | 
					            switch (index) {
 | 
				
			||||||
 | 
					              case 0:
 | 
				
			||||||
 | 
					                shareAsset();
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					              case 1:
 | 
				
			||||||
 | 
					                handleArchive(assetList[indexOfAsset.value]);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					              case 2:
 | 
				
			||||||
 | 
					                handleDelete(assetList[indexOfAsset.value]);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return Scaffold(
 | 
					    return Scaffold(
 | 
				
			||||||
      backgroundColor: Colors.black,
 | 
					      backgroundColor: Colors.black,
 | 
				
			||||||
      body: WillPopScope(
 | 
					      body: WillPopScope(
 | 
				
			||||||
@ -481,6 +539,12 @@ class GalleryViewerPage extends HookConsumerWidget {
 | 
				
			|||||||
              right: 0,
 | 
					              right: 0,
 | 
				
			||||||
              child: buildAppBar(),
 | 
					              child: buildAppBar(),
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
 | 
					            Positioned(
 | 
				
			||||||
 | 
					              bottom: 0,
 | 
				
			||||||
 | 
					              left: 0,
 | 
				
			||||||
 | 
					              right: 0,
 | 
				
			||||||
 | 
					              child: buildBottomBar(),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
          ],
 | 
					          ],
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
 | 
				
			|||||||
@ -109,7 +109,7 @@ class RenderList {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    final groups = _groupAssets(allAssets, groupBy);
 | 
					    final groups = _groupAssets(allAssets, groupBy);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    groups.entries.sortedBy((e) =>e.key).reversed.forEach((entry) {
 | 
					    groups.entries.sortedBy((e) => e.key).reversed.forEach((entry) {
 | 
				
			||||||
      final date = entry.key;
 | 
					      final date = entry.key;
 | 
				
			||||||
      final assets = entry.value;
 | 
					      final assets = entry.value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -50,8 +50,7 @@ class ImmichAssetGrid extends HookConsumerWidget {
 | 
				
			|||||||
          // Unfortunately, using the transition animation itself didn't
 | 
					          // Unfortunately, using the transition animation itself didn't
 | 
				
			||||||
          // seem to work reliably. So instead, wait until the duration of the
 | 
					          // seem to work reliably. So instead, wait until the duration of the
 | 
				
			||||||
          // animation has elapsed to re-enable the hero animations
 | 
					          // animation has elapsed to re-enable the hero animations
 | 
				
			||||||
          Future.delayed(transitionDuration)
 | 
					          Future.delayed(transitionDuration).then((_) {
 | 
				
			||||||
              .then((_) {
 | 
					 | 
				
			||||||
            enableHeroAnimations.value = true;
 | 
					            enableHeroAnimations.value = true;
 | 
				
			||||||
          });
 | 
					          });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -9,6 +9,7 @@ import 'package:immich_mobile/shared/models/album.dart';
 | 
				
			|||||||
class ControlBottomAppBar extends ConsumerWidget {
 | 
					class ControlBottomAppBar extends ConsumerWidget {
 | 
				
			||||||
  final Function onShare;
 | 
					  final Function onShare;
 | 
				
			||||||
  final Function onFavorite;
 | 
					  final Function onFavorite;
 | 
				
			||||||
 | 
					  final Function onArchive;
 | 
				
			||||||
  final Function onDelete;
 | 
					  final Function onDelete;
 | 
				
			||||||
  final Function(Album album) onAddToAlbum;
 | 
					  final Function(Album album) onAddToAlbum;
 | 
				
			||||||
  final void Function() onCreateNewAlbum;
 | 
					  final void Function() onCreateNewAlbum;
 | 
				
			||||||
@ -20,6 +21,7 @@ class ControlBottomAppBar extends ConsumerWidget {
 | 
				
			|||||||
    Key? key,
 | 
					    Key? key,
 | 
				
			||||||
    required this.onShare,
 | 
					    required this.onShare,
 | 
				
			||||||
    required this.onFavorite,
 | 
					    required this.onFavorite,
 | 
				
			||||||
 | 
					    required this.onArchive,
 | 
				
			||||||
    required this.onDelete,
 | 
					    required this.onDelete,
 | 
				
			||||||
    required this.sharedAlbums,
 | 
					    required this.sharedAlbums,
 | 
				
			||||||
    required this.albums,
 | 
					    required this.albums,
 | 
				
			||||||
@ -62,6 +64,11 @@ class ControlBottomAppBar extends ConsumerWidget {
 | 
				
			|||||||
              );
 | 
					              );
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
 | 
					          ControlBoxButton(
 | 
				
			||||||
 | 
					            iconData: Icons.archive,
 | 
				
			||||||
 | 
					            label: "control_bottom_app_bar_archive".tr(),
 | 
				
			||||||
 | 
					            onPressed: () => onArchive(),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -94,7 +94,6 @@ class HomePage extends HookConsumerWidget {
 | 
				
			|||||||
          barrierDismissible: false,
 | 
					          barrierDismissible: false,
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // ref.watch(shareServiceProvider).shareAssets(selection.value.toList());
 | 
					 | 
				
			||||||
        selectionEnabledHook.value = false;
 | 
					        selectionEnabledHook.value = false;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -132,6 +131,24 @@ class HomePage extends HookConsumerWidget {
 | 
				
			|||||||
        selectionEnabledHook.value = false;
 | 
					        selectionEnabledHook.value = false;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      void onArchiveAsset() {
 | 
				
			||||||
 | 
					        final remoteAssets = remoteOnlySelection(
 | 
				
			||||||
 | 
					          localErrorMessage: 'home_page_archive_err_local'.tr(),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        if (remoteAssets.isNotEmpty) {
 | 
				
			||||||
 | 
					          ref.watch(assetProvider.notifier).toggleArchive(remoteAssets, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          final assetOrAssets = remoteAssets.length > 1 ? 'assets' : 'asset';
 | 
				
			||||||
 | 
					          ImmichToast.show(
 | 
				
			||||||
 | 
					            context: context,
 | 
				
			||||||
 | 
					            msg: 'Moved ${remoteAssets.length} $assetOrAssets to archive',
 | 
				
			||||||
 | 
					            gravity: ToastGravity.CENTER,
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        selectionEnabledHook.value = false;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      void onDelete() {
 | 
					      void onDelete() {
 | 
				
			||||||
        ref.watch(assetProvider.notifier).deleteAssets(selection.value);
 | 
					        ref.watch(assetProvider.notifier).deleteAssets(selection.value);
 | 
				
			||||||
        selectionEnabledHook.value = false;
 | 
					        selectionEnabledHook.value = false;
 | 
				
			||||||
@ -265,7 +282,7 @@ class HomePage extends HookConsumerWidget {
 | 
				
			|||||||
                ? buildLoadingIndicator()
 | 
					                ? buildLoadingIndicator()
 | 
				
			||||||
                : ImmichAssetGrid(
 | 
					                : ImmichAssetGrid(
 | 
				
			||||||
                    renderList: ref.watch(assetProvider).renderList!,
 | 
					                    renderList: ref.watch(assetProvider).renderList!,
 | 
				
			||||||
                    assets: ref.watch(assetProvider).allAssets,
 | 
					                    assets: ref.read(assetProvider).allAssets,
 | 
				
			||||||
                    assetsPerRow: appSettingService
 | 
					                    assetsPerRow: appSettingService
 | 
				
			||||||
                        .getSetting(AppSettingsEnum.tilesPerRow),
 | 
					                        .getSetting(AppSettingsEnum.tilesPerRow),
 | 
				
			||||||
                    showStorageIndicator: appSettingService
 | 
					                    showStorageIndicator: appSettingService
 | 
				
			||||||
@ -278,6 +295,7 @@ class HomePage extends HookConsumerWidget {
 | 
				
			|||||||
              ControlBottomAppBar(
 | 
					              ControlBottomAppBar(
 | 
				
			||||||
                onShare: onShareAssets,
 | 
					                onShare: onShareAssets,
 | 
				
			||||||
                onFavorite: onFavoriteAssets,
 | 
					                onFavorite: onFavoriteAssets,
 | 
				
			||||||
 | 
					                onArchive: onArchiveAsset,
 | 
				
			||||||
                onDelete: onDelete,
 | 
					                onDelete: onDelete,
 | 
				
			||||||
                onAddToAlbum: onAddToAlbum,
 | 
					                onAddToAlbum: onAddToAlbum,
 | 
				
			||||||
                albums: albums,
 | 
					                albums: albums,
 | 
				
			||||||
@ -291,9 +309,7 @@ class HomePage extends HookConsumerWidget {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return Scaffold(
 | 
					    return Scaffold(
 | 
				
			||||||
      appBar: !selectionEnabledHook.value
 | 
					      appBar: !selectionEnabledHook.value
 | 
				
			||||||
          ? HomePageAppBar(
 | 
					          ? HomePageAppBar(onPopBack: reloadAllAsset)
 | 
				
			||||||
              onPopBack: reloadAllAsset,
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
          : null,
 | 
					          : null,
 | 
				
			||||||
      drawer: const ProfileDrawer(),
 | 
					      drawer: const ProfileDrawer(),
 | 
				
			||||||
      body: buildBody(),
 | 
					      body: buildBody(),
 | 
				
			||||||
 | 
				
			|||||||
@ -9,6 +9,7 @@ import 'package:immich_mobile/modules/album/views/library_page.dart';
 | 
				
			|||||||
import 'package:immich_mobile/modules/album/views/select_additional_user_for_sharing_page.dart';
 | 
					import 'package:immich_mobile/modules/album/views/select_additional_user_for_sharing_page.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/album/views/select_user_for_sharing_page.dart';
 | 
					import 'package:immich_mobile/modules/album/views/select_user_for_sharing_page.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/album/views/sharing_page.dart';
 | 
					import 'package:immich_mobile/modules/album/views/sharing_page.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/modules/archive/views/archive_page.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/asset_viewer/views/gallery_viewer.dart';
 | 
					import 'package:immich_mobile/modules/asset_viewer/views/gallery_viewer.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/asset_viewer/views/video_viewer_page.dart';
 | 
					import 'package:immich_mobile/modules/asset_viewer/views/video_viewer_page.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/backup/views/album_preview_page.dart';
 | 
					import 'package:immich_mobile/modules/backup/views/album_preview_page.dart';
 | 
				
			||||||
@ -128,6 +129,13 @@ part 'router.gr.dart';
 | 
				
			|||||||
    AutoRoute(
 | 
					    AutoRoute(
 | 
				
			||||||
      page: AppLogDetailPage,
 | 
					      page: AppLogDetailPage,
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
 | 
					    AutoRoute(
 | 
				
			||||||
 | 
					      page: ArchivePage,
 | 
				
			||||||
 | 
					      guards: [
 | 
				
			||||||
 | 
					        AuthGuard,
 | 
				
			||||||
 | 
					        DuplicateGuard,
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
class AppRouter extends _$AppRouter {
 | 
					class AppRouter extends _$AppRouter {
 | 
				
			||||||
 | 
				
			|||||||
@ -240,6 +240,12 @@ class _$AppRouter extends RootStackRouter {
 | 
				
			|||||||
        ),
 | 
					        ),
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    ArchiveRoute.name: (routeData) {
 | 
				
			||||||
 | 
					      return MaterialPageX<dynamic>(
 | 
				
			||||||
 | 
					        routeData: routeData,
 | 
				
			||||||
 | 
					        child: const ArchivePage(),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    HomeRoute.name: (routeData) {
 | 
					    HomeRoute.name: (routeData) {
 | 
				
			||||||
      return MaterialPageX<dynamic>(
 | 
					      return MaterialPageX<dynamic>(
 | 
				
			||||||
        routeData: routeData,
 | 
					        routeData: routeData,
 | 
				
			||||||
@ -499,6 +505,14 @@ class _$AppRouter extends RootStackRouter {
 | 
				
			|||||||
          AppLogDetailRoute.name,
 | 
					          AppLogDetailRoute.name,
 | 
				
			||||||
          path: '/app-log-detail-page',
 | 
					          path: '/app-log-detail-page',
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
 | 
					        RouteConfig(
 | 
				
			||||||
 | 
					          ArchiveRoute.name,
 | 
				
			||||||
 | 
					          path: '/archive-page',
 | 
				
			||||||
 | 
					          guards: [
 | 
				
			||||||
 | 
					            authGuard,
 | 
				
			||||||
 | 
					            duplicateGuard,
 | 
				
			||||||
 | 
					          ],
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
      ];
 | 
					      ];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1022,6 +1036,18 @@ class AppLogDetailRouteArgs {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// generated route for
 | 
				
			||||||
 | 
					/// [ArchivePage]
 | 
				
			||||||
 | 
					class ArchiveRoute extends PageRouteInfo<void> {
 | 
				
			||||||
 | 
					  const ArchiveRoute()
 | 
				
			||||||
 | 
					      : super(
 | 
				
			||||||
 | 
					          ArchiveRoute.name,
 | 
				
			||||||
 | 
					          path: '/archive-page',
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static const String name = 'ArchiveRoute';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// generated route for
 | 
					/// generated route for
 | 
				
			||||||
/// [HomePage]
 | 
					/// [HomePage]
 | 
				
			||||||
class HomeRoute extends PageRouteInfo<void> {
 | 
					class HomeRoute extends PageRouteInfo<void> {
 | 
				
			||||||
 | 
				
			|||||||
@ -29,7 +29,8 @@ class Asset {
 | 
				
			|||||||
        ownerId = fastHash(remote.ownerId),
 | 
					        ownerId = fastHash(remote.ownerId),
 | 
				
			||||||
        exifInfo =
 | 
					        exifInfo =
 | 
				
			||||||
            remote.exifInfo != null ? ExifInfo.fromDto(remote.exifInfo!) : null,
 | 
					            remote.exifInfo != null ? ExifInfo.fromDto(remote.exifInfo!) : null,
 | 
				
			||||||
        isFavorite = remote.isFavorite;
 | 
					        isFavorite = remote.isFavorite,
 | 
				
			||||||
 | 
					        isArchived = remote.isArchived;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Asset.local(AssetEntity local)
 | 
					  Asset.local(AssetEntity local)
 | 
				
			||||||
      : localId = local.id,
 | 
					      : localId = local.id,
 | 
				
			||||||
@ -44,6 +45,7 @@ class Asset {
 | 
				
			|||||||
        fileModifiedAt = local.modifiedDateTime,
 | 
					        fileModifiedAt = local.modifiedDateTime,
 | 
				
			||||||
        updatedAt = local.modifiedDateTime,
 | 
					        updatedAt = local.modifiedDateTime,
 | 
				
			||||||
        isFavorite = local.isFavorite,
 | 
					        isFavorite = local.isFavorite,
 | 
				
			||||||
 | 
					        isArchived = false,
 | 
				
			||||||
        fileCreatedAt = local.createDateTime {
 | 
					        fileCreatedAt = local.createDateTime {
 | 
				
			||||||
    if (fileCreatedAt.year == 1970) {
 | 
					    if (fileCreatedAt.year == 1970) {
 | 
				
			||||||
      fileCreatedAt = fileModifiedAt;
 | 
					      fileCreatedAt = fileModifiedAt;
 | 
				
			||||||
@ -70,6 +72,7 @@ class Asset {
 | 
				
			|||||||
    this.exifInfo,
 | 
					    this.exifInfo,
 | 
				
			||||||
    required this.isFavorite,
 | 
					    required this.isFavorite,
 | 
				
			||||||
    required this.isLocal,
 | 
					    required this.isLocal,
 | 
				
			||||||
 | 
					    required this.isArchived,
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @ignore
 | 
					  @ignore
 | 
				
			||||||
@ -132,6 +135,8 @@ class Asset {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  bool isLocal;
 | 
					  bool isLocal;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bool isArchived;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @ignore
 | 
					  @ignore
 | 
				
			||||||
  ExifInfo? exifInfo;
 | 
					  ExifInfo? exifInfo;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -168,7 +173,8 @@ class Asset {
 | 
				
			|||||||
        fileName == other.fileName &&
 | 
					        fileName == other.fileName &&
 | 
				
			||||||
        livePhotoVideoId == other.livePhotoVideoId &&
 | 
					        livePhotoVideoId == other.livePhotoVideoId &&
 | 
				
			||||||
        isFavorite == other.isFavorite &&
 | 
					        isFavorite == other.isFavorite &&
 | 
				
			||||||
        isLocal == other.isLocal;
 | 
					        isLocal == other.isLocal &&
 | 
				
			||||||
 | 
					        isArchived == other.isArchived;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
@ -189,7 +195,8 @@ class Asset {
 | 
				
			|||||||
      fileName.hashCode ^
 | 
					      fileName.hashCode ^
 | 
				
			||||||
      livePhotoVideoId.hashCode ^
 | 
					      livePhotoVideoId.hashCode ^
 | 
				
			||||||
      isFavorite.hashCode ^
 | 
					      isFavorite.hashCode ^
 | 
				
			||||||
      isLocal.hashCode;
 | 
					      isLocal.hashCode ^
 | 
				
			||||||
 | 
					      isArchived.hashCode;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  bool updateFromAssetEntity(AssetEntity ae) {
 | 
					  bool updateFromAssetEntity(AssetEntity ae) {
 | 
				
			||||||
    // TODO check more fields;
 | 
					    // TODO check more fields;
 | 
				
			||||||
@ -217,6 +224,9 @@ class Asset {
 | 
				
			|||||||
    height ??= a.height;
 | 
					    height ??= a.height;
 | 
				
			||||||
    exifInfo ??= a.exifInfo;
 | 
					    exifInfo ??= a.exifInfo;
 | 
				
			||||||
    exifInfo?.id = id;
 | 
					    exifInfo?.id = id;
 | 
				
			||||||
 | 
					    if (!isRemote) {
 | 
				
			||||||
 | 
					      isArchived = a.isArchived;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    return this;
 | 
					    return this;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -271,7 +281,8 @@ class Asset {
 | 
				
			|||||||
  "isFavorite": $isFavorite, 
 | 
					  "isFavorite": $isFavorite, 
 | 
				
			||||||
  "isLocal": $isLocal,
 | 
					  "isLocal": $isLocal,
 | 
				
			||||||
  "width": ${width ?? "N/A"},
 | 
					  "width": ${width ?? "N/A"},
 | 
				
			||||||
  "height": ${height ?? "N/A"}
 | 
					  "height": ${height ?? "N/A"},
 | 
				
			||||||
 | 
					  "isArchived": $isArchived
 | 
				
			||||||
}""";
 | 
					}""";
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -47,49 +47,54 @@ const AssetSchema = CollectionSchema(
 | 
				
			|||||||
      name: r'height',
 | 
					      name: r'height',
 | 
				
			||||||
      type: IsarType.int,
 | 
					      type: IsarType.int,
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
    r'isFavorite': PropertySchema(
 | 
					    r'isArchived': PropertySchema(
 | 
				
			||||||
      id: 6,
 | 
					      id: 6,
 | 
				
			||||||
 | 
					      name: r'isArchived',
 | 
				
			||||||
 | 
					      type: IsarType.bool,
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    r'isFavorite': PropertySchema(
 | 
				
			||||||
 | 
					      id: 7,
 | 
				
			||||||
      name: r'isFavorite',
 | 
					      name: r'isFavorite',
 | 
				
			||||||
      type: IsarType.bool,
 | 
					      type: IsarType.bool,
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
    r'isLocal': PropertySchema(
 | 
					    r'isLocal': PropertySchema(
 | 
				
			||||||
      id: 7,
 | 
					      id: 8,
 | 
				
			||||||
      name: r'isLocal',
 | 
					      name: r'isLocal',
 | 
				
			||||||
      type: IsarType.bool,
 | 
					      type: IsarType.bool,
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
    r'livePhotoVideoId': PropertySchema(
 | 
					    r'livePhotoVideoId': PropertySchema(
 | 
				
			||||||
      id: 8,
 | 
					      id: 9,
 | 
				
			||||||
      name: r'livePhotoVideoId',
 | 
					      name: r'livePhotoVideoId',
 | 
				
			||||||
      type: IsarType.string,
 | 
					      type: IsarType.string,
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
    r'localId': PropertySchema(
 | 
					    r'localId': PropertySchema(
 | 
				
			||||||
      id: 9,
 | 
					      id: 10,
 | 
				
			||||||
      name: r'localId',
 | 
					      name: r'localId',
 | 
				
			||||||
      type: IsarType.string,
 | 
					      type: IsarType.string,
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
    r'ownerId': PropertySchema(
 | 
					    r'ownerId': PropertySchema(
 | 
				
			||||||
      id: 10,
 | 
					      id: 11,
 | 
				
			||||||
      name: r'ownerId',
 | 
					      name: r'ownerId',
 | 
				
			||||||
      type: IsarType.long,
 | 
					      type: IsarType.long,
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
    r'remoteId': PropertySchema(
 | 
					    r'remoteId': PropertySchema(
 | 
				
			||||||
      id: 11,
 | 
					      id: 12,
 | 
				
			||||||
      name: r'remoteId',
 | 
					      name: r'remoteId',
 | 
				
			||||||
      type: IsarType.string,
 | 
					      type: IsarType.string,
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
    r'type': PropertySchema(
 | 
					    r'type': PropertySchema(
 | 
				
			||||||
      id: 12,
 | 
					      id: 13,
 | 
				
			||||||
      name: r'type',
 | 
					      name: r'type',
 | 
				
			||||||
      type: IsarType.byte,
 | 
					      type: IsarType.byte,
 | 
				
			||||||
      enumMap: _AssettypeEnumValueMap,
 | 
					      enumMap: _AssettypeEnumValueMap,
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
    r'updatedAt': PropertySchema(
 | 
					    r'updatedAt': PropertySchema(
 | 
				
			||||||
      id: 13,
 | 
					      id: 14,
 | 
				
			||||||
      name: r'updatedAt',
 | 
					      name: r'updatedAt',
 | 
				
			||||||
      type: IsarType.dateTime,
 | 
					      type: IsarType.dateTime,
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
    r'width': PropertySchema(
 | 
					    r'width': PropertySchema(
 | 
				
			||||||
      id: 14,
 | 
					      id: 15,
 | 
				
			||||||
      name: r'width',
 | 
					      name: r'width',
 | 
				
			||||||
      type: IsarType.int,
 | 
					      type: IsarType.int,
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
@ -175,15 +180,16 @@ void _assetSerialize(
 | 
				
			|||||||
  writer.writeDateTime(offsets[3], object.fileModifiedAt);
 | 
					  writer.writeDateTime(offsets[3], object.fileModifiedAt);
 | 
				
			||||||
  writer.writeString(offsets[4], object.fileName);
 | 
					  writer.writeString(offsets[4], object.fileName);
 | 
				
			||||||
  writer.writeInt(offsets[5], object.height);
 | 
					  writer.writeInt(offsets[5], object.height);
 | 
				
			||||||
  writer.writeBool(offsets[6], object.isFavorite);
 | 
					  writer.writeBool(offsets[6], object.isArchived);
 | 
				
			||||||
  writer.writeBool(offsets[7], object.isLocal);
 | 
					  writer.writeBool(offsets[7], object.isFavorite);
 | 
				
			||||||
  writer.writeString(offsets[8], object.livePhotoVideoId);
 | 
					  writer.writeBool(offsets[8], object.isLocal);
 | 
				
			||||||
  writer.writeString(offsets[9], object.localId);
 | 
					  writer.writeString(offsets[9], object.livePhotoVideoId);
 | 
				
			||||||
  writer.writeLong(offsets[10], object.ownerId);
 | 
					  writer.writeString(offsets[10], object.localId);
 | 
				
			||||||
  writer.writeString(offsets[11], object.remoteId);
 | 
					  writer.writeLong(offsets[11], object.ownerId);
 | 
				
			||||||
  writer.writeByte(offsets[12], object.type.index);
 | 
					  writer.writeString(offsets[12], object.remoteId);
 | 
				
			||||||
  writer.writeDateTime(offsets[13], object.updatedAt);
 | 
					  writer.writeByte(offsets[13], object.type.index);
 | 
				
			||||||
  writer.writeInt(offsets[14], object.width);
 | 
					  writer.writeDateTime(offsets[14], object.updatedAt);
 | 
				
			||||||
 | 
					  writer.writeInt(offsets[15], object.width);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Asset _assetDeserialize(
 | 
					Asset _assetDeserialize(
 | 
				
			||||||
@ -199,16 +205,17 @@ Asset _assetDeserialize(
 | 
				
			|||||||
    fileModifiedAt: reader.readDateTime(offsets[3]),
 | 
					    fileModifiedAt: reader.readDateTime(offsets[3]),
 | 
				
			||||||
    fileName: reader.readString(offsets[4]),
 | 
					    fileName: reader.readString(offsets[4]),
 | 
				
			||||||
    height: reader.readIntOrNull(offsets[5]),
 | 
					    height: reader.readIntOrNull(offsets[5]),
 | 
				
			||||||
    isFavorite: reader.readBool(offsets[6]),
 | 
					    isArchived: reader.readBool(offsets[6]),
 | 
				
			||||||
    isLocal: reader.readBool(offsets[7]),
 | 
					    isFavorite: reader.readBool(offsets[7]),
 | 
				
			||||||
    livePhotoVideoId: reader.readStringOrNull(offsets[8]),
 | 
					    isLocal: reader.readBool(offsets[8]),
 | 
				
			||||||
    localId: reader.readString(offsets[9]),
 | 
					    livePhotoVideoId: reader.readStringOrNull(offsets[9]),
 | 
				
			||||||
    ownerId: reader.readLong(offsets[10]),
 | 
					    localId: reader.readString(offsets[10]),
 | 
				
			||||||
    remoteId: reader.readStringOrNull(offsets[11]),
 | 
					    ownerId: reader.readLong(offsets[11]),
 | 
				
			||||||
    type: _AssettypeValueEnumMap[reader.readByteOrNull(offsets[12])] ??
 | 
					    remoteId: reader.readStringOrNull(offsets[12]),
 | 
				
			||||||
 | 
					    type: _AssettypeValueEnumMap[reader.readByteOrNull(offsets[13])] ??
 | 
				
			||||||
        AssetType.other,
 | 
					        AssetType.other,
 | 
				
			||||||
    updatedAt: reader.readDateTime(offsets[13]),
 | 
					    updatedAt: reader.readDateTime(offsets[14]),
 | 
				
			||||||
    width: reader.readIntOrNull(offsets[14]),
 | 
					    width: reader.readIntOrNull(offsets[15]),
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
  object.id = id;
 | 
					  object.id = id;
 | 
				
			||||||
  return object;
 | 
					  return object;
 | 
				
			||||||
@ -238,19 +245,21 @@ P _assetDeserializeProp<P>(
 | 
				
			|||||||
    case 7:
 | 
					    case 7:
 | 
				
			||||||
      return (reader.readBool(offset)) as P;
 | 
					      return (reader.readBool(offset)) as P;
 | 
				
			||||||
    case 8:
 | 
					    case 8:
 | 
				
			||||||
      return (reader.readStringOrNull(offset)) as P;
 | 
					      return (reader.readBool(offset)) as P;
 | 
				
			||||||
    case 9:
 | 
					    case 9:
 | 
				
			||||||
      return (reader.readString(offset)) as P;
 | 
					 | 
				
			||||||
    case 10:
 | 
					 | 
				
			||||||
      return (reader.readLong(offset)) as P;
 | 
					 | 
				
			||||||
    case 11:
 | 
					 | 
				
			||||||
      return (reader.readStringOrNull(offset)) as P;
 | 
					      return (reader.readStringOrNull(offset)) as P;
 | 
				
			||||||
 | 
					    case 10:
 | 
				
			||||||
 | 
					      return (reader.readString(offset)) as P;
 | 
				
			||||||
 | 
					    case 11:
 | 
				
			||||||
 | 
					      return (reader.readLong(offset)) as P;
 | 
				
			||||||
    case 12:
 | 
					    case 12:
 | 
				
			||||||
 | 
					      return (reader.readStringOrNull(offset)) as P;
 | 
				
			||||||
 | 
					    case 13:
 | 
				
			||||||
      return (_AssettypeValueEnumMap[reader.readByteOrNull(offset)] ??
 | 
					      return (_AssettypeValueEnumMap[reader.readByteOrNull(offset)] ??
 | 
				
			||||||
          AssetType.other) as P;
 | 
					          AssetType.other) as P;
 | 
				
			||||||
    case 13:
 | 
					 | 
				
			||||||
      return (reader.readDateTime(offset)) as P;
 | 
					 | 
				
			||||||
    case 14:
 | 
					    case 14:
 | 
				
			||||||
 | 
					      return (reader.readDateTime(offset)) as P;
 | 
				
			||||||
 | 
					    case 15:
 | 
				
			||||||
      return (reader.readIntOrNull(offset)) as P;
 | 
					      return (reader.readIntOrNull(offset)) as P;
 | 
				
			||||||
    default:
 | 
					    default:
 | 
				
			||||||
      throw IsarError('Unknown property with id $propertyId');
 | 
					      throw IsarError('Unknown property with id $propertyId');
 | 
				
			||||||
@ -1024,6 +1033,16 @@ extension AssetQueryFilter on QueryBuilder<Asset, Asset, QFilterCondition> {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  QueryBuilder<Asset, Asset, QAfterFilterCondition> isArchivedEqualTo(
 | 
				
			||||||
 | 
					      bool value) {
 | 
				
			||||||
 | 
					    return QueryBuilder.apply(this, (query) {
 | 
				
			||||||
 | 
					      return query.addFilterCondition(FilterCondition.equalTo(
 | 
				
			||||||
 | 
					        property: r'isArchived',
 | 
				
			||||||
 | 
					        value: value,
 | 
				
			||||||
 | 
					      ));
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  QueryBuilder<Asset, Asset, QAfterFilterCondition> isFavoriteEqualTo(
 | 
					  QueryBuilder<Asset, Asset, QAfterFilterCondition> isFavoriteEqualTo(
 | 
				
			||||||
      bool value) {
 | 
					      bool value) {
 | 
				
			||||||
    return QueryBuilder.apply(this, (query) {
 | 
					    return QueryBuilder.apply(this, (query) {
 | 
				
			||||||
@ -1771,6 +1790,18 @@ extension AssetQuerySortBy on QueryBuilder<Asset, Asset, QSortBy> {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  QueryBuilder<Asset, Asset, QAfterSortBy> sortByIsArchived() {
 | 
				
			||||||
 | 
					    return QueryBuilder.apply(this, (query) {
 | 
				
			||||||
 | 
					      return query.addSortBy(r'isArchived', Sort.asc);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  QueryBuilder<Asset, Asset, QAfterSortBy> sortByIsArchivedDesc() {
 | 
				
			||||||
 | 
					    return QueryBuilder.apply(this, (query) {
 | 
				
			||||||
 | 
					      return query.addSortBy(r'isArchived', Sort.desc);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  QueryBuilder<Asset, Asset, QAfterSortBy> sortByIsFavorite() {
 | 
					  QueryBuilder<Asset, Asset, QAfterSortBy> sortByIsFavorite() {
 | 
				
			||||||
    return QueryBuilder.apply(this, (query) {
 | 
					    return QueryBuilder.apply(this, (query) {
 | 
				
			||||||
      return query.addSortBy(r'isFavorite', Sort.asc);
 | 
					      return query.addSortBy(r'isFavorite', Sort.asc);
 | 
				
			||||||
@ -1965,6 +1996,18 @@ extension AssetQuerySortThenBy on QueryBuilder<Asset, Asset, QSortThenBy> {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  QueryBuilder<Asset, Asset, QAfterSortBy> thenByIsArchived() {
 | 
				
			||||||
 | 
					    return QueryBuilder.apply(this, (query) {
 | 
				
			||||||
 | 
					      return query.addSortBy(r'isArchived', Sort.asc);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  QueryBuilder<Asset, Asset, QAfterSortBy> thenByIsArchivedDesc() {
 | 
				
			||||||
 | 
					    return QueryBuilder.apply(this, (query) {
 | 
				
			||||||
 | 
					      return query.addSortBy(r'isArchived', Sort.desc);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  QueryBuilder<Asset, Asset, QAfterSortBy> thenByIsFavorite() {
 | 
					  QueryBuilder<Asset, Asset, QAfterSortBy> thenByIsFavorite() {
 | 
				
			||||||
    return QueryBuilder.apply(this, (query) {
 | 
					    return QueryBuilder.apply(this, (query) {
 | 
				
			||||||
      return query.addSortBy(r'isFavorite', Sort.asc);
 | 
					      return query.addSortBy(r'isFavorite', Sort.asc);
 | 
				
			||||||
@ -2112,6 +2155,12 @@ extension AssetQueryWhereDistinct on QueryBuilder<Asset, Asset, QDistinct> {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  QueryBuilder<Asset, Asset, QDistinct> distinctByIsArchived() {
 | 
				
			||||||
 | 
					    return QueryBuilder.apply(this, (query) {
 | 
				
			||||||
 | 
					      return query.addDistinctBy(r'isArchived');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  QueryBuilder<Asset, Asset, QDistinct> distinctByIsFavorite() {
 | 
					  QueryBuilder<Asset, Asset, QDistinct> distinctByIsFavorite() {
 | 
				
			||||||
    return QueryBuilder.apply(this, (query) {
 | 
					    return QueryBuilder.apply(this, (query) {
 | 
				
			||||||
      return query.addDistinctBy(r'isFavorite');
 | 
					      return query.addDistinctBy(r'isFavorite');
 | 
				
			||||||
@ -2214,6 +2263,12 @@ extension AssetQueryProperty on QueryBuilder<Asset, Asset, QQueryProperty> {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  QueryBuilder<Asset, bool, QQueryOperations> isArchivedProperty() {
 | 
				
			||||||
 | 
					    return QueryBuilder.apply(this, (query) {
 | 
				
			||||||
 | 
					      return query.addPropertyName(r'isArchived');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  QueryBuilder<Asset, bool, QQueryOperations> isFavoriteProperty() {
 | 
					  QueryBuilder<Asset, bool, QQueryOperations> isFavoriteProperty() {
 | 
				
			||||||
    return QueryBuilder.apply(this, (query) {
 | 
					    return QueryBuilder.apply(this, (query) {
 | 
				
			||||||
      return query.addPropertyName(r'isFavorite');
 | 
					      return query.addPropertyName(r'isFavorite');
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/album/services/album.service.dart';
 | 
					import 'package:immich_mobile/modules/album/services/album.service.dart';
 | 
				
			||||||
import 'package:immich_mobile/shared/models/exif_info.dart';
 | 
					import 'package:immich_mobile/shared/models/exif_info.dart';
 | 
				
			||||||
@ -19,6 +20,8 @@ import 'package:logging/logging.dart';
 | 
				
			|||||||
import 'package:openapi/api.dart';
 | 
					import 'package:openapi/api.dart';
 | 
				
			||||||
import 'package:photo_manager/photo_manager.dart';
 | 
					import 'package:photo_manager/photo_manager.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// State does not contain archived assets.
 | 
				
			||||||
 | 
					/// Use database provider if you want to access the isArchived assets
 | 
				
			||||||
class AssetsState {
 | 
					class AssetsState {
 | 
				
			||||||
  final List<Asset> allAssets;
 | 
					  final List<Asset> allAssets;
 | 
				
			||||||
  final RenderList? renderList;
 | 
					  final RenderList? renderList;
 | 
				
			||||||
@ -76,6 +79,7 @@ class AssetNotifier extends StateNotifier<AssetsState> {
 | 
				
			|||||||
      GroupAssetsBy
 | 
					      GroupAssetsBy
 | 
				
			||||||
          .values[_settingsService.getSetting(AppSettingsEnum.groupAssetsBy)],
 | 
					          .values[_settingsService.getSetting(AppSettingsEnum.groupAssetsBy)],
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    state = await AssetsState.fromAssetList(newAssetList)
 | 
					    state = await AssetsState.fromAssetList(newAssetList)
 | 
				
			||||||
        .withRenderDataStructure(layout);
 | 
					        .withRenderDataStructure(layout);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -112,6 +116,7 @@ class AssetNotifier extends StateNotifier<AssetsState> {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
      final bool newRemote = await _assetService.refreshRemoteAssets();
 | 
					      final bool newRemote = await _assetService.refreshRemoteAssets();
 | 
				
			||||||
      final bool newLocal = await _albumService.refreshDeviceAlbums();
 | 
					      final bool newLocal = await _albumService.refreshDeviceAlbums();
 | 
				
			||||||
 | 
					      debugPrint("newRemote: $newRemote, newLocal: $newLocal");
 | 
				
			||||||
      log.info("Load assets: ${stopwatch.elapsedMilliseconds}ms");
 | 
					      log.info("Load assets: ${stopwatch.elapsedMilliseconds}ms");
 | 
				
			||||||
      stopwatch.reset();
 | 
					      stopwatch.reset();
 | 
				
			||||||
      if (!newRemote &&
 | 
					      if (!newRemote &&
 | 
				
			||||||
@ -139,6 +144,7 @@ class AssetNotifier extends StateNotifier<AssetsState> {
 | 
				
			|||||||
  Future<List<Asset>> _getUserAssets(int userId) => _db.assets
 | 
					  Future<List<Asset>> _getUserAssets(int userId) => _db.assets
 | 
				
			||||||
      .filter()
 | 
					      .filter()
 | 
				
			||||||
      .ownerIdEqualTo(userId)
 | 
					      .ownerIdEqualTo(userId)
 | 
				
			||||||
 | 
					      .isArchivedEqualTo(false)
 | 
				
			||||||
      .sortByFileCreatedAtDesc()
 | 
					      .sortByFileCreatedAtDesc()
 | 
				
			||||||
      .findAll();
 | 
					      .findAll();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -224,13 +230,46 @@ class AssetNotifier extends StateNotifier<AssetsState> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    final index = state.allAssets.indexWhere((a) => asset.id == a.id);
 | 
					    final index = state.allAssets.indexWhere((a) => asset.id == a.id);
 | 
				
			||||||
    if (index > 0) {
 | 
					    if (index != -1) {
 | 
				
			||||||
      state.allAssets[index] = newAsset;
 | 
					      state.allAssets[index] = newAsset;
 | 
				
			||||||
      _updateAssetsState(state.allAssets);
 | 
					      _updateAssetsState(state.allAssets);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return newAsset.isFavorite;
 | 
					    return newAsset.isFavorite;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> toggleArchive(Iterable<Asset> assets, bool status) async {
 | 
				
			||||||
 | 
					    final newAssets = await Future.wait(
 | 
				
			||||||
 | 
					      assets.map((a) => _assetService.changeArchiveStatus(a, status)),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    int i = 0;
 | 
				
			||||||
 | 
					    bool unArchived = false;
 | 
				
			||||||
 | 
					    for (Asset oldAsset in assets) {
 | 
				
			||||||
 | 
					      final newAsset = newAssets[i++];
 | 
				
			||||||
 | 
					      if (newAsset == null) {
 | 
				
			||||||
 | 
					        log.severe("Change archive status failed for asset ${oldAsset.id}");
 | 
				
			||||||
 | 
					        continue;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      final index = state.allAssets.indexWhere((a) => oldAsset.id == a.id);
 | 
				
			||||||
 | 
					      if (newAsset.isArchived) {
 | 
				
			||||||
 | 
					        // remove from state
 | 
				
			||||||
 | 
					        if (index != -1) {
 | 
				
			||||||
 | 
					          state.allAssets.removeAt(index);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        // add to state is difficult because the list is sorted
 | 
				
			||||||
 | 
					        unArchived = true;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (unArchived) {
 | 
				
			||||||
 | 
					      final User me = Store.get(StoreKey.currentUser);
 | 
				
			||||||
 | 
					      await _stateUpdateLock.run(
 | 
				
			||||||
 | 
					        () async => _updateAssetsState(await _getUserAssets(me.isarId)),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      _updateAssetsState(state.allAssets);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
final assetProvider = StateNotifierProvider<AssetNotifier, AssetsState>((ref) {
 | 
					final assetProvider = StateNotifierProvider<AssetNotifier, AssetsState>((ref) {
 | 
				
			||||||
 | 
				
			|||||||
@ -121,10 +121,21 @@ class AssetService {
 | 
				
			|||||||
  ) async {
 | 
					  ) async {
 | 
				
			||||||
    final dto =
 | 
					    final dto =
 | 
				
			||||||
        await _apiService.assetApi.updateAsset(asset.remoteId!, updateAssetDto);
 | 
					        await _apiService.assetApi.updateAsset(asset.remoteId!, updateAssetDto);
 | 
				
			||||||
    return dto == null ? null : Asset.remote(dto);
 | 
					    if (dto != null) {
 | 
				
			||||||
 | 
					      final updated = Asset.remote(dto).updateFromDb(asset);
 | 
				
			||||||
 | 
					      if (updated.isInDb) {
 | 
				
			||||||
 | 
					        await _db.writeTxn(() => updated.put(_db));
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return updated;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return null;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Future<Asset?> changeFavoriteStatus(Asset asset, bool isFavorite) {
 | 
					  Future<Asset?> changeFavoriteStatus(Asset asset, bool isFavorite) {
 | 
				
			||||||
    return updateAsset(asset, UpdateAssetDto(isFavorite: isFavorite));
 | 
					    return updateAsset(asset, UpdateAssetDto(isFavorite: isFavorite));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<Asset?> changeArchiveStatus(Asset asset, bool isArchive) {
 | 
				
			||||||
 | 
					    return updateAsset(asset, UpdateAssetDto(isArchived: isArchive));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
build:
 | 
					build:
 | 
				
			||||||
	flutter packages pub run build_runner build
 | 
						flutter packages pub run build_runner build --delete-conflicting-outputs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
watch:
 | 
					watch:
 | 
				
			||||||
	flutter packages pub run build_runner watch --delete-conflicting-outputs
 | 
						flutter packages pub run build_runner watch --delete-conflicting-outputs
 | 
				
			||||||
 | 
				
			|||||||
@ -24,6 +24,7 @@ void main() {
 | 
				
			|||||||
        fileName: '',
 | 
					        fileName: '',
 | 
				
			||||||
        isFavorite: false,
 | 
					        isFavorite: false,
 | 
				
			||||||
        isLocal: false,
 | 
					        isLocal: false,
 | 
				
			||||||
 | 
					        isArchived: false,
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -26,6 +26,7 @@ Asset _getTestAsset(int id, bool favorite) {
 | 
				
			|||||||
    type: AssetType.image,
 | 
					    type: AssetType.image,
 | 
				
			||||||
    fileName: '',
 | 
					    fileName: '',
 | 
				
			||||||
    isFavorite: favorite,
 | 
					    isFavorite: favorite,
 | 
				
			||||||
 | 
					    isArchived: false,
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
  a.id = id;
 | 
					  a.id = id;
 | 
				
			||||||
  return a;
 | 
					  return a;
 | 
				
			||||||
 | 
				
			|||||||
@ -32,6 +32,7 @@ void main() {
 | 
				
			|||||||
      fileName: localId,
 | 
					      fileName: localId,
 | 
				
			||||||
      isFavorite: false,
 | 
					      isFavorite: false,
 | 
				
			||||||
      isLocal: isLocal,
 | 
					      isLocal: isLocal,
 | 
				
			||||||
 | 
					      isArchived: false,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -141,7 +141,7 @@ class {{{classname}}} {
 | 
				
			|||||||
        {{{name}}}: json[r'{{{baseName}}}'] is List
 | 
					        {{{name}}}: json[r'{{{baseName}}}'] is List
 | 
				
			||||||
          ? (json[r'{{{baseName}}}'] as List).map((e) =>
 | 
					          ? (json[r'{{{baseName}}}'] as List).map((e) =>
 | 
				
			||||||
              {{#items.complexType}}
 | 
					              {{#items.complexType}}
 | 
				
			||||||
              {{items.complexType}}.listFromJson(json[r'{{{baseName}}}']){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}
 | 
					              {{items.complexType}}.listFromJson(json[r'{{{baseName}}}']){{#required}}{{^isNullable}}!{{/isNullable}}{{#uniqueItems}}.toSet(){{/uniqueItems}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}
 | 
				
			||||||
              {{/items.complexType}}
 | 
					              {{/items.complexType}}
 | 
				
			||||||
              {{^items.complexType}}
 | 
					              {{^items.complexType}}
 | 
				
			||||||
              e == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const  <{{items.items.dataType}}>[]{{/items.isNullable}} : (e as List).cast<{{items.items.dataType}}>()
 | 
					              e == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const  <{{items.items.dataType}}>[]{{/items.isNullable}} : (e as List).cast<{{items.items.dataType}}>()
 | 
				
			||||||
@ -150,7 +150,7 @@ class {{{classname}}} {
 | 
				
			|||||||
          :  {{#isNullable}}null{{/isNullable}}{{^isNullable}}const []{{/isNullable}},
 | 
					          :  {{#isNullable}}null{{/isNullable}}{{^isNullable}}const []{{/isNullable}},
 | 
				
			||||||
            {{/items.isArray}}
 | 
					            {{/items.isArray}}
 | 
				
			||||||
            {{^items.isArray}}
 | 
					            {{^items.isArray}}
 | 
				
			||||||
        {{{name}}}: {{{complexType}}}.listFromJson(json[r'{{{baseName}}}']){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}},
 | 
					        {{{name}}}: {{{complexType}}}.listFromJson(json[r'{{{baseName}}}']){{#required}}{{^isNullable}}!{{/isNullable}}{{#uniqueItems}}.toSet(){{/uniqueItems}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}},
 | 
				
			||||||
            {{/items.isArray}}
 | 
					            {{/items.isArray}}
 | 
				
			||||||
          {{/isArray}}
 | 
					          {{/isArray}}
 | 
				
			||||||
          {{^isArray}}
 | 
					          {{^isArray}}
 | 
				
			||||||
@ -197,7 +197,7 @@ class {{{classname}}} {
 | 
				
			|||||||
        {{^complexType}}
 | 
					        {{^complexType}}
 | 
				
			||||||
          {{#isArray}}
 | 
					          {{#isArray}}
 | 
				
			||||||
            {{#isEnum}}
 | 
					            {{#isEnum}}
 | 
				
			||||||
        {{{name}}}: {{{items.datatypeWithEnum}}}.listFromJson(json[r'{{{baseName}}}']){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}},
 | 
					        {{{name}}}: {{{items.datatypeWithEnum}}}.listFromJson(json[r'{{{baseName}}}']){{#required}}{{^isNullable}}!{{/isNullable}}{{#uniqueItems}}.toSet(){{/uniqueItems}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}},
 | 
				
			||||||
            {{/isEnum}}
 | 
					            {{/isEnum}}
 | 
				
			||||||
            {{^isEnum}}
 | 
					            {{^isEnum}}
 | 
				
			||||||
        {{{name}}}: json[r'{{{baseName}}}'] is {{#uniqueItems}}Set{{/uniqueItems}}{{^uniqueItems}}List{{/uniqueItems}}
 | 
					        {{{name}}}: json[r'{{{baseName}}}'] is {{#uniqueItems}}Set{{/uniqueItems}}{{^uniqueItems}}List{{/uniqueItems}}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user