From 5145c33ed4047727472d0a0e034632971dd4b034 Mon Sep 17 00:00:00 2001 From: Fynn Petersen-Frey <10599762+fyfrey@users.noreply.github.com> Date: Tue, 14 Nov 2023 21:30:27 +0100 Subject: [PATCH] feat(mobile): use app without storage permission (#5014) * feat(mobile): use app without storage permission * address review feedback --- mobile/assets/i18n/en-US.json | 6 +- .../album/providers/album.provider.dart | 2 + .../modules/album/services/album.service.dart | 4 + .../backup/providers/backup.provider.dart | 45 +++----- .../modules/backup/ui/album_info_card.dart | 31 +----- .../backup/ui/album_info_list_tile.dart | 32 +----- .../views/backup_album_selection_page.dart | 58 +--------- .../backup/views/backup_controller_page.dart | 105 +++++++++--------- .../views/permission_onboarding_page.dart | 23 +--- ...uard.dart => backup_permission_guard.dart} | 6 +- mobile/lib/routing/router.dart | 15 ++- mobile/lib/routing/router.gr.dart | 7 +- .../shared/providers/app_state.provider.dart | 12 +- mobile/lib/shared/services/sync.service.dart | 20 ++++ 14 files changed, 134 insertions(+), 232 deletions(-) rename mobile/lib/routing/{gallery_permission_guard.dart => backup_permission_guard.dart} (73%) diff --git a/mobile/assets/i18n/en-US.json b/mobile/assets/i18n/en-US.json index ff43095476..65a75bf327 100644 --- a/mobile/assets/i18n/en-US.json +++ b/mobile/assets/i18n/en-US.json @@ -80,7 +80,6 @@ "backup_controller_page_failed": "Failed ({})", "backup_controller_page_filename": "File name: {} [{}]", "backup_controller_page_id": "ID: {}", - "backup_controller_page_info": "Backup Information", "backup_controller_page_none_selected": "None selected", "backup_controller_page_remainder": "Remainder", "backup_controller_page_remainder_sub": "Remaining photos and videos to back up from selection", @@ -96,7 +95,6 @@ "backup_controller_page_turn_off": "Turn off foreground backup", "backup_controller_page_turn_on": "Turn on foreground backup", "backup_controller_page_uploading_file_info": "Uploading file info", - "backup_err_only_album": "Cannot remove the only album", "backup_info_card_assets": "assets", "backup_manual_cancelled": "Cancelled", "backup_manual_failed": "Failed", @@ -253,11 +251,11 @@ "permission_onboarding_get_started": "Get started", "permission_onboarding_go_to_settings": "Go to settings", "permission_onboarding_grant_permission": "Grant permission", - "permission_onboarding_log_out": "Log out", + "permission_onboarding_back": "Back", "permission_onboarding_permission_denied": "Permission denied. To use Immich, grant photo and video permissions in Settings.", "permission_onboarding_permission_granted": "Permission granted! You are all set.", "permission_onboarding_permission_limited": "Permission limited. To let Immich backup and manage your entire gallery collection, grant photo and video permissions in Settings.", - "permission_onboarding_request": "Immich requires permission to view your photos and videos.", + "permission_onboarding_request": "Immich requires permission to access your photos and videos.", "profile_drawer_app_logs": "Logs", "profile_drawer_client_server_up_to_date": "Client and Server are up-to-date", "profile_drawer_documentation": "Documentation", diff --git a/mobile/lib/modules/album/providers/album.provider.dart b/mobile/lib/modules/album/providers/album.provider.dart index 24679c5178..48a2e8f1f7 100644 --- a/mobile/lib/modules/album/providers/album.provider.dart +++ b/mobile/lib/modules/album/providers/album.provider.dart @@ -25,6 +25,8 @@ class AlbumNotifier extends StateNotifier> { _albumService.refreshRemoteAlbums(isShared: false), ]); + Future getDeviceAlbums() => _albumService.refreshDeviceAlbums(); + Future deleteAlbum(Album album) => _albumService.deleteAlbum(album); Future createAlbum( diff --git a/mobile/lib/modules/album/services/album.service.dart b/mobile/lib/modules/album/services/album.service.dart index fdb07f563d..1f50a3667e 100644 --- a/mobile/lib/modules/album/services/album.service.dart +++ b/mobile/lib/modules/album/services/album.service.dart @@ -67,6 +67,10 @@ class AlbumService { final List selectedIds = await _backupService.selectedAlbumsQuery().idProperty().findAll(); if (selectedIds.isEmpty) { + final numLocal = await _db.albums.where().localIdIsNotNull().count(); + if (numLocal > 0) { + _syncService.removeAllLocalAlbumsAndAssets(); + } return false; } final List onDevice = diff --git a/mobile/lib/modules/backup/providers/backup.provider.dart b/mobile/lib/modules/backup/providers/backup.provider.dart index 0df8bc90d4..3d8d6414c8 100644 --- a/mobile/lib/modules/backup/providers/backup.provider.dart +++ b/mobile/lib/modules/backup/providers/backup.provider.dart @@ -89,7 +89,6 @@ class BackupNotifier extends StateNotifier { state = state .copyWith(selectedBackupAlbums: {...state.selectedBackupAlbums, album}); - _updateBackupAssetCount(); } void addExcludedAlbumForBackup(AvailableAlbum album) { @@ -98,7 +97,6 @@ class BackupNotifier extends StateNotifier { } state = state .copyWith(excludedBackupAlbums: {...state.excludedBackupAlbums, album}); - _updateBackupAssetCount(); } void removeAlbumForBackup(AvailableAlbum album) { @@ -107,7 +105,6 @@ class BackupNotifier extends StateNotifier { currentSelectedAlbums.removeWhere((a) => a == album); state = state.copyWith(selectedBackupAlbums: currentSelectedAlbums); - _updateBackupAssetCount(); } void removeExcludedAlbumForBackup(AvailableAlbum album) { @@ -116,7 +113,20 @@ class BackupNotifier extends StateNotifier { currentExcludedAlbums.removeWhere((a) => a == album); state = state.copyWith(excludedBackupAlbums: currentExcludedAlbums); - _updateBackupAssetCount(); + } + + Future backupAlbumSelectionDone() { + if (state.selectedBackupAlbums.isEmpty) { + // disable any backup + cancelBackup(); + setAutoBackup(false); + configureBackgroundBackup( + enabled: false, + onError: (msg) {}, + onBatteryInfo: () {}, + ); + } + return _updateBackupAssetCount(); } void setAutoBackup(bool enabled) { @@ -249,30 +259,6 @@ class BackupNotifier extends StateNotifier { final List selectedBackupAlbums = await _backupService.selectedAlbumsQuery().findAll(); - // First time backup - set isAll album is the default one for backup. - if (selectedBackupAlbums.isEmpty) { - log.info("First time backup; setup 'Recent(s)' album as default"); - - // Get album that contains all assets - final list = await PhotoManager.getAssetPathList( - hasAll: true, - onlyAll: true, - type: RequestType.common, - ); - - if (list.isEmpty) { - return; - } - AssetPathEntity albumHasAllAssets = list.first; - - final ba = BackupAlbum( - albumHasAllAssets.id, - DateTime.fromMillisecondsSinceEpoch(0), - BackupSelection.select, - ); - await _db.writeTxn(() => _db.backupAlbums.put(ba)); - } - // Generate AssetPathEntity from id to add to local state final Set selectedAlbums = {}; for (final BackupAlbum ba in selectedBackupAlbums) { @@ -362,7 +348,6 @@ class BackupNotifier extends StateNotifier { allUniqueAssets: {}, selectedAlbumsBackupAssetsIds: selectedAlbumsBackupAssets, ); - return; } else { state = state.copyWith( allAssetsInDatabase: allAssetsInDatabase, @@ -373,8 +358,6 @@ class BackupNotifier extends StateNotifier { // Save to persistent storage await _updatePersistentAlbumsSelection(); - - return; } /// Get all necessary information for calculating the available albums, diff --git a/mobile/lib/modules/backup/ui/album_info_card.dart b/mobile/lib/modules/backup/ui/album_info_card.dart index 4df62090bf..3e579a84c7 100644 --- a/mobile/lib/modules/backup/ui/album_info_card.dart +++ b/mobile/lib/modules/backup/ui/album_info_card.dart @@ -82,19 +82,9 @@ class AlbumInfoCard extends HookConsumerWidget { HapticFeedback.selectionClick(); if (isSelected) { - if (ref.watch(backupProvider).selectedBackupAlbums.length == 1) { - ImmichToast.show( - context: context, - msg: "backup_err_only_album".tr(), - toastType: ToastType.error, - gravity: ToastGravity.BOTTOM, - ); - return; - } - - ref.watch(backupProvider.notifier).removeAlbumForBackup(albumInfo); + ref.read(backupProvider.notifier).removeAlbumForBackup(albumInfo); } else { - ref.watch(backupProvider.notifier).addAlbumForBackup(albumInfo); + ref.read(backupProvider.notifier).addAlbumForBackup(albumInfo); } }, onDoubleTap: () { @@ -103,23 +93,10 @@ class AlbumInfoCard extends HookConsumerWidget { if (isExcluded) { // Remove from exclude album list ref - .watch(backupProvider.notifier) + .read(backupProvider.notifier) .removeExcludedAlbumForBackup(albumInfo); } else { // Add to exclude album list - if (ref.watch(backupProvider).selectedBackupAlbums.length == 1 && - ref - .watch(backupProvider) - .selectedBackupAlbums - .contains(albumInfo)) { - ImmichToast.show( - context: context, - msg: "backup_err_only_album".tr(), - toastType: ToastType.error, - gravity: ToastGravity.BOTTOM, - ); - return; - } if (albumInfo.id == 'isAll' || albumInfo.name == 'Recents') { ImmichToast.show( @@ -132,7 +109,7 @@ class AlbumInfoCard extends HookConsumerWidget { } ref - .watch(backupProvider.notifier) + .read(backupProvider.notifier) .addExcludedAlbumForBackup(albumInfo); } }, diff --git a/mobile/lib/modules/backup/ui/album_info_list_tile.dart b/mobile/lib/modules/backup/ui/album_info_list_tile.dart index e19827bf90..0c27ca1bad 100644 --- a/mobile/lib/modules/backup/ui/album_info_list_tile.dart +++ b/mobile/lib/modules/backup/ui/album_info_list_tile.dart @@ -1,4 +1,3 @@ -import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; @@ -74,23 +73,10 @@ class AlbumInfoListTile extends HookConsumerWidget { if (isExcluded) { // Remove from exclude album list ref - .watch(backupProvider.notifier) + .read(backupProvider.notifier) .removeExcludedAlbumForBackup(albumInfo); } else { // Add to exclude album list - if (ref.watch(backupProvider).selectedBackupAlbums.length == 1 && - ref - .watch(backupProvider) - .selectedBackupAlbums - .contains(albumInfo)) { - ImmichToast.show( - context: context, - msg: "backup_err_only_album".tr(), - toastType: ToastType.error, - gravity: ToastGravity.BOTTOM, - ); - return; - } if (albumInfo.id == 'isAll' || albumInfo.name == 'Recents') { ImmichToast.show( @@ -103,7 +89,7 @@ class AlbumInfoListTile extends HookConsumerWidget { } ref - .watch(backupProvider.notifier) + .read(backupProvider.notifier) .addExcludedAlbumForBackup(albumInfo); } }, @@ -113,19 +99,9 @@ class AlbumInfoListTile extends HookConsumerWidget { onTap: () { HapticFeedback.selectionClick(); if (isSelected) { - if (ref.watch(backupProvider).selectedBackupAlbums.length == 1) { - ImmichToast.show( - context: context, - msg: "backup_err_only_album".tr(), - toastType: ToastType.error, - gravity: ToastGravity.BOTTOM, - ); - return; - } - - ref.watch(backupProvider.notifier).removeAlbumForBackup(albumInfo); + ref.read(backupProvider.notifier).removeAlbumForBackup(albumInfo); } else { - ref.watch(backupProvider.notifier).addAlbumForBackup(albumInfo); + ref.read(backupProvider.notifier).addAlbumForBackup(albumInfo); } }, leading: ClipRRect( diff --git a/mobile/lib/modules/backup/views/backup_album_selection_page.dart b/mobile/lib/modules/backup/views/backup_album_selection_page.dart index e259bf1376..7e0a0b513d 100644 --- a/mobile/lib/modules/backup/views/backup_album_selection_page.dart +++ b/mobile/lib/modules/backup/views/backup_album_selection_page.dart @@ -1,7 +1,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/immich_colors.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; @@ -9,7 +8,6 @@ import 'package:immich_mobile/modules/backup/providers/backup.provider.dart'; import 'package:immich_mobile/modules/backup/ui/album_info_card.dart'; import 'package:immich_mobile/modules/backup/ui/album_info_list_tile.dart'; import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; -import 'package:immich_mobile/shared/ui/immich_toast.dart'; class BackupAlbumSelectionPage extends HookConsumerWidget { const BackupAlbumSelectionPage({Key? key}) : super(key: key); @@ -91,19 +89,8 @@ class BackupAlbumSelectionPage extends HookConsumerWidget { buildSelectedAlbumNameChip() { return selectedBackupAlbums.map((album) { - void removeSelection() { - if (ref.watch(backupProvider).selectedBackupAlbums.length == 1) { - ImmichToast.show( - context: context, - msg: "backup_err_only_album".tr(), - toastType: ToastType.error, - gravity: ToastGravity.BOTTOM, - ); - return; - } - - ref.watch(backupProvider.notifier).removeAlbumForBackup(album); - } + void removeSelection() => + ref.read(backupProvider.notifier).removeAlbumForBackup(album); return Padding( padding: const EdgeInsets.only(right: 8.0), @@ -252,47 +239,6 @@ class BackupAlbumSelectionPage extends HookConsumerWidget { ), ), - Padding( - padding: - const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8), - child: Card( - margin: const EdgeInsets.all(0), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - side: BorderSide( - color: isDarkTheme - ? const Color.fromARGB(255, 0, 0, 0) - : const Color.fromARGB(255, 235, 235, 235), - width: 1, - ), - ), - elevation: 0, - borderOnForeground: false, - child: Column( - children: [ - ListTile( - visualDensity: VisualDensity.compact, - title: const Text( - "backup_album_selection_page_total_assets", - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 14, - ), - ).tr(), - trailing: Text( - ref - .watch(backupProvider) - .allUniqueAssets - .length - .toString(), - style: const TextStyle(fontWeight: FontWeight.bold), - ), - ), - ], - ), - ), - ), - ListTile( title: Text( "backup_album_selection_page_albums_device".tr( diff --git a/mobile/lib/modules/backup/views/backup_controller_page.dart b/mobile/lib/modules/backup/views/backup_controller_page.dart index fa8fac1d6b..6a949213a2 100644 --- a/mobile/lib/modules/backup/views/backup_controller_page.dart +++ b/mobile/lib/modules/backup/views/backup_controller_page.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/modules/album/providers/album.provider.dart'; import 'package:immich_mobile/modules/backup/background_service/background.service.dart'; import 'package:immich_mobile/modules/backup/providers/error_backup_list.provider.dart'; import 'package:immich_mobile/modules/backup/providers/ios_background_settings.provider.dart'; @@ -38,6 +39,7 @@ class BackupControllerPage extends HookConsumerWidget { final settingsService = ref.watch(appSettingsServiceProvider); final showBackupFix = Platform.isAndroid && settingsService.getSetting(AppSettingsEnum.advancedTroubleshooting); + final hasAnyAlbum = backupState.selectedBackupAlbums.isNotEmpty; final appRefreshDisabled = Platform.isIOS && settings?.appRefreshEnabled != true; @@ -590,8 +592,14 @@ class BackupControllerPage extends HookConsumerWidget { ), ), trailing: ElevatedButton( - onPressed: () { - context.autoPush(const BackupAlbumSelectionRoute()); + onPressed: () async { + await context.autoPush(const BackupAlbumSelectionRoute()); + // waited until returning from selection + await ref + .read(backupProvider.notifier) + .backupAlbumSelectionDone(); + // waited until backup albums are stored in DB + ref.read(albumProvider.notifier).getDeviceAlbums(); }, child: const Text( "backup_controller_page_select", @@ -689,55 +697,50 @@ class BackupControllerPage extends HookConsumerWidget { padding: const EdgeInsets.only(left: 16.0, right: 16, bottom: 32), child: ListView( // crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: const Text( - "backup_controller_page_info", - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16), - ).tr(), - ), - buildFolderSelectionTile(), - BackupInfoCard( - title: "backup_controller_page_total".tr(), - subtitle: "backup_controller_page_total_sub".tr(), - info: ref.watch(backupProvider).availableAlbums.isEmpty - ? "..." - : "${backupState.allUniqueAssets.length}", - ), - BackupInfoCard( - title: "backup_controller_page_backup".tr(), - subtitle: "backup_controller_page_backup_sub".tr(), - info: ref.watch(backupProvider).availableAlbums.isEmpty - ? "..." - : "${backupState.selectedAlbumsBackupAssetsIds.length}", - ), - BackupInfoCard( - title: "backup_controller_page_remainder".tr(), - subtitle: "backup_controller_page_remainder_sub".tr(), - info: ref.watch(backupProvider).availableAlbums.isEmpty - ? "..." - : "${backupState.allUniqueAssets.length - backupState.selectedAlbumsBackupAssetsIds.length}", - ), - const Divider(), - buildAutoBackupController(), - const Divider(), - AnimatedSwitcher( - duration: const Duration(milliseconds: 500), - child: Platform.isIOS - ? (appRefreshDisabled - ? buildBackgroundAppRefreshWarning() - : buildBackgroundBackupController()) - : buildBackgroundBackupController(), - ), - if (showBackupFix) const Divider(), - if (showBackupFix) buildCheckCorruptBackups(), - const Divider(), - const Divider(), - const CurrentUploadingAssetInfoBox(), - if (!hasExclusiveAccess) buildBackgroundBackupInfo(), - buildBackupButton(), - ], + children: hasAnyAlbum + ? [ + buildFolderSelectionTile(), + BackupInfoCard( + title: "backup_controller_page_total".tr(), + subtitle: "backup_controller_page_total_sub".tr(), + info: ref.watch(backupProvider).availableAlbums.isEmpty + ? "..." + : "${backupState.allUniqueAssets.length}", + ), + BackupInfoCard( + title: "backup_controller_page_backup".tr(), + subtitle: "backup_controller_page_backup_sub".tr(), + info: ref.watch(backupProvider).availableAlbums.isEmpty + ? "..." + : "${backupState.selectedAlbumsBackupAssetsIds.length}", + ), + BackupInfoCard( + title: "backup_controller_page_remainder".tr(), + subtitle: "backup_controller_page_remainder_sub".tr(), + info: ref.watch(backupProvider).availableAlbums.isEmpty + ? "..." + : "${backupState.allUniqueAssets.length - backupState.selectedAlbumsBackupAssetsIds.length}", + ), + const Divider(), + buildAutoBackupController(), + const Divider(), + AnimatedSwitcher( + duration: const Duration(milliseconds: 500), + child: Platform.isIOS + ? (appRefreshDisabled + ? buildBackgroundAppRefreshWarning() + : buildBackgroundBackupController()) + : buildBackgroundBackupController(), + ), + if (showBackupFix) const Divider(), + if (showBackupFix) buildCheckCorruptBackups(), + const Divider(), + const Divider(), + const CurrentUploadingAssetInfoBox(), + if (!hasExclusiveAccess) buildBackgroundBackupInfo(), + buildBackupButton(), + ] + : [buildFolderSelectionTile()], ), ), ); diff --git a/mobile/lib/modules/onboarding/views/permission_onboarding_page.dart b/mobile/lib/modules/onboarding/views/permission_onboarding_page.dart index e1fd05dabb..771deefa32 100644 --- a/mobile/lib/modules/onboarding/views/permission_onboarding_page.dart +++ b/mobile/lib/modules/onboarding/views/permission_onboarding_page.dart @@ -2,8 +2,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/modules/backup/providers/backup.provider.dart'; -import 'package:immich_mobile/modules/login/providers/authentication.provider.dart'; import 'package:immich_mobile/modules/onboarding/providers/gallery_permission.provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/shared/ui/immich_logo.dart'; @@ -18,13 +16,7 @@ class PermissionOnboardingPage extends HookConsumerWidget { final PermissionStatus permission = ref.watch(galleryPermissionNotifier); // Navigate to the main Tab Controller when permission is granted - void goToHome() { - // Resume backup (if enable) then navigate - ref.watch(backupProvider.notifier).resumeBackup().catchError((error) { - debugPrint('PermissionOnboardingPage error: $error'); - }); - context.autoReplace(const TabControllerRoute()); - } + void goToBackup() => context.autoReplace(const BackupControllerRoute()); // When the permission is denied, we show a request permission page buildRequestPermission() { @@ -46,7 +38,7 @@ class PermissionOnboardingPage extends HookConsumerWidget { if (permission.isGranted) { // If permission is limited, we will show the limited // permission page - goToHome(); + goToBackup(); } }), child: const Text( @@ -71,7 +63,7 @@ class PermissionOnboardingPage extends HookConsumerWidget { ).tr(), const SizedBox(height: 18), ElevatedButton( - onPressed: () => goToHome(), + onPressed: () => goToBackup(), child: const Text('permission_onboarding_get_started').tr(), ), ], @@ -106,7 +98,7 @@ class PermissionOnboardingPage extends HookConsumerWidget { ), const SizedBox(height: 8.0), TextButton( - onPressed: () => goToHome(), + onPressed: () => goToBackup(), child: const Text( 'permission_onboarding_continue_anyway', ).tr(), @@ -181,11 +173,8 @@ class PermissionOnboardingPage extends HookConsumerWidget { ), ), TextButton( - child: const Text('permission_onboarding_log_out').tr(), - onPressed: () { - ref.read(authenticationProvider.notifier).logout(); - context.autoReplace(const LoginRoute()); - }, + child: const Text('permission_onboarding_back').tr(), + onPressed: () => context.autoPop(), ), ], ), diff --git a/mobile/lib/routing/gallery_permission_guard.dart b/mobile/lib/routing/backup_permission_guard.dart similarity index 73% rename from mobile/lib/routing/gallery_permission_guard.dart rename to mobile/lib/routing/backup_permission_guard.dart index 65455584bc..81d40513b3 100644 --- a/mobile/lib/routing/gallery_permission_guard.dart +++ b/mobile/lib/routing/backup_permission_guard.dart @@ -2,10 +2,10 @@ import 'package:auto_route/auto_route.dart'; import 'package:immich_mobile/modules/onboarding/providers/gallery_permission.provider.dart'; import 'package:immich_mobile/routing/router.dart'; -class GalleryPermissionGuard extends AutoRouteGuard { +class BackupPermissionGuard extends AutoRouteGuard { final GalleryPermissionNotifier _permission; - GalleryPermissionGuard(this._permission); + BackupPermissionGuard(this._permission); @override void onNavigation(NavigationResolver resolver, StackRouter router) async { @@ -13,7 +13,7 @@ class GalleryPermissionGuard extends AutoRouteGuard { if (p) { resolver.next(true); } else { - router.replaceAll([const PermissionOnboardingRoute()]); + router.push(const PermissionOnboardingRoute()); } } } diff --git a/mobile/lib/routing/router.dart b/mobile/lib/routing/router.dart index 01d54082ec..dfb87a4ae5 100644 --- a/mobile/lib/routing/router.dart +++ b/mobile/lib/routing/router.dart @@ -44,7 +44,7 @@ import 'package:immich_mobile/modules/search/views/search_result_page.dart'; import 'package:immich_mobile/modules/settings/views/settings_page.dart'; import 'package:immich_mobile/routing/auth_guard.dart'; import 'package:immich_mobile/routing/duplicate_guard.dart'; -import 'package:immich_mobile/routing/gallery_permission_guard.dart'; +import 'package:immich_mobile/routing/backup_permission_guard.dart'; import 'package:immich_mobile/shared/models/asset.dart'; import 'package:immich_mobile/shared/models/album.dart'; import 'package:immich_mobile/shared/models/logger_message.model.dart'; @@ -77,7 +77,7 @@ part 'router.gr.dart'; AutoRoute(page: ChangePasswordPage), CustomRoute( page: TabControllerPage, - guards: [AuthGuard, DuplicateGuard, GalleryPermissionGuard], + guards: [AuthGuard, DuplicateGuard], children: [ AutoRoute(page: HomePage, guards: [AuthGuard, DuplicateGuard]), AutoRoute(page: SearchPage, guards: [AuthGuard, DuplicateGuard]), @@ -88,10 +88,13 @@ part 'router.gr.dart'; ), AutoRoute( page: GalleryViewerPage, - guards: [AuthGuard, DuplicateGuard, GalleryPermissionGuard], + guards: [AuthGuard, DuplicateGuard], ), AutoRoute(page: VideoViewerPage, guards: [AuthGuard, DuplicateGuard]), - AutoRoute(page: BackupControllerPage, guards: [AuthGuard, DuplicateGuard]), + AutoRoute( + page: BackupControllerPage, + guards: [AuthGuard, DuplicateGuard, BackupPermissionGuard], + ), AutoRoute(page: SearchResultPage, guards: [AuthGuard, DuplicateGuard]), AutoRoute(page: CuratedLocationPage, guards: [AuthGuard, DuplicateGuard]), AutoRoute(page: CreateAlbumPage, guards: [AuthGuard, DuplicateGuard]), @@ -179,8 +182,8 @@ class AppRouter extends _$AppRouter { ) : super( authGuard: AuthGuard(_apiService), duplicateGuard: DuplicateGuard(), - galleryPermissionGuard: - GalleryPermissionGuard(galleryPermissionNotifier), + backupPermissionGuard: + BackupPermissionGuard(galleryPermissionNotifier), ); } diff --git a/mobile/lib/routing/router.gr.dart b/mobile/lib/routing/router.gr.dart index c6a54ffcd8..583aa11251 100644 --- a/mobile/lib/routing/router.gr.dart +++ b/mobile/lib/routing/router.gr.dart @@ -17,14 +17,14 @@ class _$AppRouter extends RootStackRouter { GlobalKey? navigatorKey, required this.authGuard, required this.duplicateGuard, - required this.galleryPermissionGuard, + required this.backupPermissionGuard, }) : super(navigatorKey); final AuthGuard authGuard; final DuplicateGuard duplicateGuard; - final GalleryPermissionGuard galleryPermissionGuard; + final BackupPermissionGuard backupPermissionGuard; @override final Map pagesMap = { @@ -414,7 +414,6 @@ class _$AppRouter extends RootStackRouter { guards: [ authGuard, duplicateGuard, - galleryPermissionGuard, ], children: [ RouteConfig( @@ -461,7 +460,6 @@ class _$AppRouter extends RootStackRouter { guards: [ authGuard, duplicateGuard, - galleryPermissionGuard, ], ), RouteConfig( @@ -478,6 +476,7 @@ class _$AppRouter extends RootStackRouter { guards: [ authGuard, duplicateGuard, + backupPermissionGuard, ], ), RouteConfig( diff --git a/mobile/lib/shared/providers/app_state.provider.dart b/mobile/lib/shared/providers/app_state.provider.dart index 44fff2c07f..dce2c054c3 100644 --- a/mobile/lib/shared/providers/app_state.provider.dart +++ b/mobile/lib/shared/providers/app_state.provider.dart @@ -45,12 +45,14 @@ class AppStateNotiifer extends StateNotifier { _wasPaused = false; final isAuthenticated = _ref.read(authenticationProvider).isAuthenticated; - final permission = _ref.read(galleryPermissionNotifier); - // Needs to be logged in and have gallery permissions - if (isAuthenticated && (permission.isGranted || permission.isLimited)) { - _ref.read(backupProvider.notifier).resumeBackup(); - _ref.read(backgroundServiceProvider).resumeServiceIfEnabled(); + // Needs to be logged in + if (isAuthenticated) { + final permission = _ref.watch(galleryPermissionNotifier); + if (permission.isGranted || permission.isLimited) { + _ref.read(backupProvider.notifier).resumeBackup(); + _ref.read(backgroundServiceProvider).resumeServiceIfEnabled(); + } _ref.read(serverInfoProvider.notifier).getServerVersion(); switch (_ref.read(tabProvider)) { case TabEnum.home: diff --git a/mobile/lib/shared/services/sync.service.dart b/mobile/lib/shared/services/sync.service.dart index f195f5dbb6..50b96319db 100644 --- a/mobile/lib/shared/services/sync.service.dart +++ b/mobile/lib/shared/services/sync.service.dart @@ -90,6 +90,9 @@ class SyncService { Future syncNewAssetToDb(Asset newAsset) => _lock.run(() => _syncNewAssetToDb(newAsset)); + Future removeAllLocalAlbumsAndAssets() => + _lock.run(_removeAllLocalAlbumsAndAssets); + // private methods: /// Syncs users from the server to the local database @@ -756,6 +759,23 @@ class SyncService { await a.assetCountAsync != (await _db.eTags.getById(a.eTagKeyAssetCount))?.assetCount; } + + Future _removeAllLocalAlbumsAndAssets() async { + try { + final assets = await _db.assets.where().localIdIsNotNull().findAll(); + final (toDelete, toUpdate) = + _handleAssetRemoval(assets, [], remote: false); + await _db.writeTxn(() async { + await _db.assets.deleteAll(toDelete); + await _db.assets.putAll(toUpdate); + await _db.albums.where().localIdIsNotNull().deleteAll(); + }); + return true; + } catch (e) { + _log.severe("Failed to remove all local albums and assets: $e"); + return false; + } + } } /// Returns a triple(toAdd, toUpdate, toRemove)