mirror of
				https://github.com/KevinMidboe/immich.git
				synced 2025-10-29 17:40:28 +00:00 
			
		
		
		
	fix(mobile): manual asset upload - app state handling + cancel button (#3611)
* feat(mobile): Cancel manual asset upload * fix(mobile): re-add the missing translation keys * feat(mobile): show manual upload error in backup page * refactor: manual upload in-progress count * fix(mobile): handle app state properly during manual asset upload
This commit is contained in:
		@@ -92,6 +92,11 @@
 | 
				
			|||||||
  "backup_controller_page_uploading_file_info": "Uploading file info",
 | 
					  "backup_controller_page_uploading_file_info": "Uploading file info",
 | 
				
			||||||
  "backup_err_only_album": "Cannot remove the only album",
 | 
					  "backup_err_only_album": "Cannot remove the only album",
 | 
				
			||||||
  "backup_info_card_assets": "assets",
 | 
					  "backup_info_card_assets": "assets",
 | 
				
			||||||
 | 
					  "backup_manual_success": "Success",
 | 
				
			||||||
 | 
					  "backup_manual_failed": "Failed",
 | 
				
			||||||
 | 
					  "backup_manual_cancelled": "Cancelled",
 | 
				
			||||||
 | 
					  "backup_manual_title": "Upload status",
 | 
				
			||||||
 | 
					  "backup_manual_in_progress": "Upload already in progress. Try after sometime",
 | 
				
			||||||
  "cache_settings_album_thumbnails": "Library page thumbnails ({} assets)",
 | 
					  "cache_settings_album_thumbnails": "Library page thumbnails ({} assets)",
 | 
				
			||||||
  "cache_settings_clear_cache_button": "Clear cache",
 | 
					  "cache_settings_clear_cache_button": "Clear cache",
 | 
				
			||||||
  "cache_settings_clear_cache_button_title": "Clears the app's cache. This will significantly impact the app's performance until the cache has rebuilt.",
 | 
					  "cache_settings_clear_cache_button_title": "Clears the app's cache. This will significantly impact the app's performance until the cache has rebuilt.",
 | 
				
			||||||
@@ -138,6 +143,10 @@
 | 
				
			|||||||
  "delete_dialog_cancel": "Cancel",
 | 
					  "delete_dialog_cancel": "Cancel",
 | 
				
			||||||
  "delete_dialog_ok": "Delete",
 | 
					  "delete_dialog_ok": "Delete",
 | 
				
			||||||
  "delete_dialog_title": "Delete Permanently",
 | 
					  "delete_dialog_title": "Delete Permanently",
 | 
				
			||||||
 | 
					  "upload_dialog_title": "Upload Asset",
 | 
				
			||||||
 | 
					  "upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?",
 | 
				
			||||||
 | 
					  "upload_dialog_ok": "Upload",
 | 
				
			||||||
 | 
					  "upload_dialog_cancel": "Cancel",
 | 
				
			||||||
  "description_input_hint_text": "Add description...",
 | 
					  "description_input_hint_text": "Add description...",
 | 
				
			||||||
  "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",
 | 
				
			||||||
  "exif_bottom_sheet_description": "Add Description...",
 | 
					  "exif_bottom_sheet_description": "Add Description...",
 | 
				
			||||||
@@ -153,6 +162,7 @@
 | 
				
			|||||||
  "home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping",
 | 
					  "home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping",
 | 
				
			||||||
  "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_archive_err_local": "Can not archive local assets yet, skipping",
 | 
					  "home_page_archive_err_local": "Can not archive local assets yet, skipping",
 | 
				
			||||||
 | 
					  "home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
 | 
				
			||||||
  "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_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).",
 | 
				
			||||||
@@ -186,6 +196,7 @@
 | 
				
			|||||||
  "login_form_save_login": "Stay logged in",
 | 
					  "login_form_save_login": "Stay logged in",
 | 
				
			||||||
  "login_form_server_empty": "Enter a server URL.",
 | 
					  "login_form_server_empty": "Enter a server URL.",
 | 
				
			||||||
  "login_form_server_error": "Could not connect to server.",
 | 
					  "login_form_server_error": "Could not connect to server.",
 | 
				
			||||||
 | 
					  "login_disabled": "Login has been disabled",
 | 
				
			||||||
  "monthly_title_text_date_format": "MMMM y",
 | 
					  "monthly_title_text_date_format": "MMMM y",
 | 
				
			||||||
  "motion_photos_page_title": "Motion Photos",
 | 
					  "motion_photos_page_title": "Motion Photos",
 | 
				
			||||||
  "notification_permission_dialog_cancel": "Cancel",
 | 
					  "notification_permission_dialog_cancel": "Cancel",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,13 +11,6 @@ import 'package:immich_mobile/constants/locales.dart';
 | 
				
			|||||||
import 'package:immich_mobile/modules/backup/background_service/background.service.dart';
 | 
					import 'package:immich_mobile/modules/backup/background_service/background.service.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/backup/models/backup_album.model.dart';
 | 
					import 'package:immich_mobile/modules/backup/models/backup_album.model.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/backup/models/duplicated_asset.model.dart';
 | 
					import 'package:immich_mobile/modules/backup/models/duplicated_asset.model.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
 | 
					 | 
				
			||||||
import 'package:immich_mobile/modules/backup/providers/ios_background_settings.provider.dart';
 | 
					 | 
				
			||||||
import 'package:immich_mobile/modules/backup/providers/manual_upload.provider.dart';
 | 
					 | 
				
			||||||
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
 | 
					 | 
				
			||||||
import 'package:immich_mobile/modules/memories/providers/memory.provider.dart';
 | 
					 | 
				
			||||||
import 'package:immich_mobile/modules/onboarding/providers/gallery_permission.provider.dart';
 | 
					 | 
				
			||||||
import 'package:immich_mobile/modules/settings/providers/notification_permission.provider.dart';
 | 
					 | 
				
			||||||
import 'package:immich_mobile/routing/router.dart';
 | 
					import 'package:immich_mobile/routing/router.dart';
 | 
				
			||||||
import 'package:immich_mobile/routing/tab_navigation_observer.dart';
 | 
					import 'package:immich_mobile/routing/tab_navigation_observer.dart';
 | 
				
			||||||
import 'package:immich_mobile/shared/models/album.dart';
 | 
					import 'package:immich_mobile/shared/models/album.dart';
 | 
				
			||||||
@@ -30,11 +23,8 @@ import 'package:immich_mobile/shared/models/logger_message.model.dart';
 | 
				
			|||||||
import 'package:immich_mobile/shared/models/store.dart';
 | 
					import 'package:immich_mobile/shared/models/store.dart';
 | 
				
			||||||
import 'package:immich_mobile/shared/models/user.dart';
 | 
					import 'package:immich_mobile/shared/models/user.dart';
 | 
				
			||||||
import 'package:immich_mobile/shared/providers/app_state.provider.dart';
 | 
					import 'package:immich_mobile/shared/providers/app_state.provider.dart';
 | 
				
			||||||
import 'package:immich_mobile/shared/providers/asset.provider.dart';
 | 
					 | 
				
			||||||
import 'package:immich_mobile/shared/providers/db.provider.dart';
 | 
					import 'package:immich_mobile/shared/providers/db.provider.dart';
 | 
				
			||||||
import 'package:immich_mobile/shared/providers/release_info.provider.dart';
 | 
					import 'package:immich_mobile/shared/providers/release_info.provider.dart';
 | 
				
			||||||
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
 | 
					 | 
				
			||||||
import 'package:immich_mobile/shared/providers/websocket.provider.dart';
 | 
					 | 
				
			||||||
import 'package:immich_mobile/shared/services/immich_logger.service.dart';
 | 
					import 'package:immich_mobile/shared/services/immich_logger.service.dart';
 | 
				
			||||||
import 'package:immich_mobile/shared/services/local_notification.service.dart';
 | 
					import 'package:immich_mobile/shared/services/local_notification.service.dart';
 | 
				
			||||||
import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
 | 
					import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
 | 
				
			||||||
@@ -44,7 +34,6 @@ import 'package:immich_mobile/utils/migration.dart';
 | 
				
			|||||||
import 'package:isar/isar.dart';
 | 
					import 'package:isar/isar.dart';
 | 
				
			||||||
import 'package:logging/logging.dart';
 | 
					import 'package:logging/logging.dart';
 | 
				
			||||||
import 'package:path_provider/path_provider.dart';
 | 
					import 'package:path_provider/path_provider.dart';
 | 
				
			||||||
import 'package:permission_handler/permission_handler.dart';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
void main() async {
 | 
					void main() async {
 | 
				
			||||||
  WidgetsFlutterBinding.ensureInitialized();
 | 
					  WidgetsFlutterBinding.ensureInitialized();
 | 
				
			||||||
@@ -133,54 +122,22 @@ class ImmichAppState extends ConsumerState<ImmichApp>
 | 
				
			|||||||
    switch (state) {
 | 
					    switch (state) {
 | 
				
			||||||
      case AppLifecycleState.resumed:
 | 
					      case AppLifecycleState.resumed:
 | 
				
			||||||
        debugPrint("[APP STATE] resumed");
 | 
					        debugPrint("[APP STATE] resumed");
 | 
				
			||||||
        ref.watch(appStateProvider.notifier).state = AppStateEnum.resumed;
 | 
					        ref.read(appStateProvider.notifier).handleAppResume();
 | 
				
			||||||
 | 
					 | 
				
			||||||
        var isAuthenticated = ref.watch(authenticationProvider).isAuthenticated;
 | 
					 | 
				
			||||||
        final permission = ref.watch(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();
 | 
					 | 
				
			||||||
          ref.watch(assetProvider.notifier).getAllAsset();
 | 
					 | 
				
			||||||
          ref.watch(serverInfoProvider.notifier).getServerVersion();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        ref.watch(websocketProvider.notifier).connect();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        ref.watch(releaseInfoProvider.notifier).checkGithubReleaseInfo();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        ref
 | 
					 | 
				
			||||||
            .watch(notificationPermissionProvider.notifier)
 | 
					 | 
				
			||||||
            .getNotificationPermission();
 | 
					 | 
				
			||||||
        ref
 | 
					 | 
				
			||||||
            .watch(galleryPermissionNotifier.notifier)
 | 
					 | 
				
			||||||
            .getGalleryPermissionStatus();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        ref.read(iOSBackgroundSettingsProvider.notifier).refresh();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        ref.invalidate(memoryFutureProvider);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      case AppLifecycleState.inactive:
 | 
					      case AppLifecycleState.inactive:
 | 
				
			||||||
        debugPrint("[APP STATE] inactive");
 | 
					        debugPrint("[APP STATE] inactive");
 | 
				
			||||||
        ref.watch(appStateProvider.notifier).state = AppStateEnum.inactive;
 | 
					        ref.read(appStateProvider.notifier).handleAppInactivity();
 | 
				
			||||||
        ImmichLogger().flush();
 | 
					 | 
				
			||||||
        ref.watch(websocketProvider.notifier).disconnect();
 | 
					 | 
				
			||||||
        ref.watch(manualUploadProvider.notifier).cancelBackup();
 | 
					 | 
				
			||||||
        ref.read(backupProvider.notifier).cancelBackup();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      case AppLifecycleState.paused:
 | 
					      case AppLifecycleState.paused:
 | 
				
			||||||
        debugPrint("[APP STATE] paused");
 | 
					        debugPrint("[APP STATE] paused");
 | 
				
			||||||
        ref.watch(appStateProvider.notifier).state = AppStateEnum.paused;
 | 
					        ref.read(appStateProvider.notifier).handleAppPause();
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      case AppLifecycleState.detached:
 | 
					      case AppLifecycleState.detached:
 | 
				
			||||||
        debugPrint("[APP STATE] detached");
 | 
					        debugPrint("[APP STATE] detached");
 | 
				
			||||||
        ref.watch(appStateProvider.notifier).state = AppStateEnum.detached;
 | 
					        ref.read(appStateProvider.notifier).handleAppDetached();
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,46 +4,51 @@ import 'package:immich_mobile/modules/backup/models/current_upload_asset.model.d
 | 
				
			|||||||
class ManualUploadState {
 | 
					class ManualUploadState {
 | 
				
			||||||
  final CancellationToken cancelToken;
 | 
					  final CancellationToken cancelToken;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  final double progressInPercentage;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Current Backup Asset
 | 
					  // Current Backup Asset
 | 
				
			||||||
  final CurrentUploadAsset currentUploadAsset;
 | 
					  final CurrentUploadAsset currentUploadAsset;
 | 
				
			||||||
 | 
					  final int currentAssetIndex;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /// Manual Upload
 | 
					  final bool showDetailedNotification;
 | 
				
			||||||
  final int manualUploadsTotal;
 | 
					
 | 
				
			||||||
  final int manualUploadFailures;
 | 
					  /// Manual Upload Stats
 | 
				
			||||||
  final int manualUploadSuccess;
 | 
					  final int totalAssetsToUpload;
 | 
				
			||||||
 | 
					  final int successfulUploads;
 | 
				
			||||||
 | 
					  final double progressInPercentage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const ManualUploadState({
 | 
					  const ManualUploadState({
 | 
				
			||||||
    required this.progressInPercentage,
 | 
					    required this.progressInPercentage,
 | 
				
			||||||
    required this.cancelToken,
 | 
					    required this.cancelToken,
 | 
				
			||||||
    required this.currentUploadAsset,
 | 
					    required this.currentUploadAsset,
 | 
				
			||||||
    required this.manualUploadsTotal,
 | 
					    required this.totalAssetsToUpload,
 | 
				
			||||||
    required this.manualUploadFailures,
 | 
					    required this.currentAssetIndex,
 | 
				
			||||||
    required this.manualUploadSuccess,
 | 
					    required this.successfulUploads,
 | 
				
			||||||
 | 
					    required this.showDetailedNotification,
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ManualUploadState copyWith({
 | 
					  ManualUploadState copyWith({
 | 
				
			||||||
    double? progressInPercentage,
 | 
					    double? progressInPercentage,
 | 
				
			||||||
    CancellationToken? cancelToken,
 | 
					    CancellationToken? cancelToken,
 | 
				
			||||||
    CurrentUploadAsset? currentUploadAsset,
 | 
					    CurrentUploadAsset? currentUploadAsset,
 | 
				
			||||||
    int? manualUploadsTotal,
 | 
					    int? totalAssetsToUpload,
 | 
				
			||||||
    int? manualUploadFailures,
 | 
					    int? successfulUploads,
 | 
				
			||||||
    int? manualUploadSuccess,
 | 
					    int? currentAssetIndex,
 | 
				
			||||||
 | 
					    bool? showDetailedNotification,
 | 
				
			||||||
  }) {
 | 
					  }) {
 | 
				
			||||||
    return ManualUploadState(
 | 
					    return ManualUploadState(
 | 
				
			||||||
      progressInPercentage: progressInPercentage ?? this.progressInPercentage,
 | 
					      progressInPercentage: progressInPercentage ?? this.progressInPercentage,
 | 
				
			||||||
      cancelToken: cancelToken ?? this.cancelToken,
 | 
					      cancelToken: cancelToken ?? this.cancelToken,
 | 
				
			||||||
      currentUploadAsset: currentUploadAsset ?? this.currentUploadAsset,
 | 
					      currentUploadAsset: currentUploadAsset ?? this.currentUploadAsset,
 | 
				
			||||||
      manualUploadsTotal: manualUploadsTotal ?? this.manualUploadsTotal,
 | 
					      totalAssetsToUpload: totalAssetsToUpload ?? this.totalAssetsToUpload,
 | 
				
			||||||
      manualUploadFailures: manualUploadFailures ?? this.manualUploadFailures,
 | 
					      currentAssetIndex: currentAssetIndex ?? this.currentAssetIndex,
 | 
				
			||||||
      manualUploadSuccess: manualUploadSuccess ?? this.manualUploadSuccess,
 | 
					      successfulUploads: successfulUploads ?? this.successfulUploads,
 | 
				
			||||||
 | 
					      showDetailedNotification:
 | 
				
			||||||
 | 
					          showDetailedNotification ?? this.showDetailedNotification,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  String toString() {
 | 
					  String toString() {
 | 
				
			||||||
    return 'ManualUploadState(progressInPercentage: $progressInPercentage, cancelToken: $cancelToken, currentUploadAsset: $currentUploadAsset, manualUploadsTotal: $manualUploadsTotal, manualUploadSuccess: $manualUploadSuccess, manualUploadFailures: $manualUploadFailures)';
 | 
					    return 'ManualUploadState(progressInPercentage: $progressInPercentage, cancelToken: $cancelToken, currentUploadAsset: $currentUploadAsset, totalAssetsToUpload: $totalAssetsToUpload, successfulUploads: $successfulUploads, currentAssetIndex: $currentAssetIndex, showDetailedNotification: $showDetailedNotification)';
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
@@ -54,9 +59,10 @@ class ManualUploadState {
 | 
				
			|||||||
        other.progressInPercentage == progressInPercentage &&
 | 
					        other.progressInPercentage == progressInPercentage &&
 | 
				
			||||||
        other.cancelToken == cancelToken &&
 | 
					        other.cancelToken == cancelToken &&
 | 
				
			||||||
        other.currentUploadAsset == currentUploadAsset &&
 | 
					        other.currentUploadAsset == currentUploadAsset &&
 | 
				
			||||||
        other.manualUploadsTotal == manualUploadsTotal &&
 | 
					        other.totalAssetsToUpload == totalAssetsToUpload &&
 | 
				
			||||||
        other.manualUploadFailures == manualUploadFailures &&
 | 
					        other.currentAssetIndex == currentAssetIndex &&
 | 
				
			||||||
        other.manualUploadSuccess == manualUploadSuccess;
 | 
					        other.successfulUploads == successfulUploads &&
 | 
				
			||||||
 | 
					        other.showDetailedNotification == showDetailedNotification;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
@@ -64,8 +70,9 @@ class ManualUploadState {
 | 
				
			|||||||
    return progressInPercentage.hashCode ^
 | 
					    return progressInPercentage.hashCode ^
 | 
				
			||||||
        cancelToken.hashCode ^
 | 
					        cancelToken.hashCode ^
 | 
				
			||||||
        currentUploadAsset.hashCode ^
 | 
					        currentUploadAsset.hashCode ^
 | 
				
			||||||
        manualUploadsTotal.hashCode ^
 | 
					        totalAssetsToUpload.hashCode ^
 | 
				
			||||||
        manualUploadFailures.hashCode ^
 | 
					        currentAssetIndex.hashCode ^
 | 
				
			||||||
        manualUploadSuccess.hashCode;
 | 
					        successfulUploads.hashCode ^
 | 
				
			||||||
 | 
					        showDetailedNotification.hashCode;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,14 +9,17 @@ import 'package:immich_mobile/modules/backup/models/current_upload_asset.model.d
 | 
				
			|||||||
import 'package:immich_mobile/modules/backup/models/error_upload_asset.model.dart';
 | 
					import 'package:immich_mobile/modules/backup/models/error_upload_asset.model.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/backup/models/manual_upload_state.model.dart';
 | 
					import 'package:immich_mobile/modules/backup/models/manual_upload_state.model.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
 | 
					import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/modules/backup/providers/error_backup_list.provider.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/backup/services/backup.service.dart';
 | 
					import 'package:immich_mobile/modules/backup/services/backup.service.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/onboarding/providers/gallery_permission.provider.dart';
 | 
					import 'package:immich_mobile/modules/onboarding/providers/gallery_permission.provider.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
 | 
					import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
 | 
					import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
 | 
				
			||||||
import 'package:immich_mobile/shared/models/asset.dart';
 | 
					import 'package:immich_mobile/shared/models/asset.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/shared/providers/app_state.provider.dart';
 | 
				
			||||||
import 'package:immich_mobile/shared/services/local_notification.service.dart';
 | 
					import 'package:immich_mobile/shared/services/local_notification.service.dart';
 | 
				
			||||||
import 'package:immich_mobile/shared/ui/immich_toast.dart';
 | 
					import 'package:immich_mobile/shared/ui/immich_toast.dart';
 | 
				
			||||||
import 'package:immich_mobile/utils/backup_progress.dart';
 | 
					import 'package:immich_mobile/utils/backup_progress.dart';
 | 
				
			||||||
 | 
					import 'package:logging/logging.dart';
 | 
				
			||||||
import 'package:permission_handler/permission_handler.dart';
 | 
					import 'package:permission_handler/permission_handler.dart';
 | 
				
			||||||
import 'package:photo_manager/photo_manager.dart';
 | 
					import 'package:photo_manager/photo_manager.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -24,24 +27,19 @@ final manualUploadProvider =
 | 
				
			|||||||
    StateNotifierProvider<ManualUploadNotifier, ManualUploadState>((ref) {
 | 
					    StateNotifierProvider<ManualUploadNotifier, ManualUploadState>((ref) {
 | 
				
			||||||
  return ManualUploadNotifier(
 | 
					  return ManualUploadNotifier(
 | 
				
			||||||
    ref.watch(localNotificationService),
 | 
					    ref.watch(localNotificationService),
 | 
				
			||||||
    ref.watch(backgroundServiceProvider),
 | 
					 | 
				
			||||||
    ref.watch(backupServiceProvider),
 | 
					 | 
				
			||||||
    ref.watch(backupProvider.notifier),
 | 
					    ref.watch(backupProvider.notifier),
 | 
				
			||||||
    ref,
 | 
					    ref,
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ManualUploadNotifier extends StateNotifier<ManualUploadState> {
 | 
					class ManualUploadNotifier extends StateNotifier<ManualUploadState> {
 | 
				
			||||||
 | 
					  final Logger _log = Logger("ManualUploadNotifier");
 | 
				
			||||||
  final LocalNotificationService _localNotificationService;
 | 
					  final LocalNotificationService _localNotificationService;
 | 
				
			||||||
  final BackgroundService _backgroundService;
 | 
					 | 
				
			||||||
  final BackupService _backupService;
 | 
					 | 
				
			||||||
  final BackupNotifier _backupProvider;
 | 
					  final BackupNotifier _backupProvider;
 | 
				
			||||||
  final Ref ref;
 | 
					  final Ref ref;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ManualUploadNotifier(
 | 
					  ManualUploadNotifier(
 | 
				
			||||||
    this._localNotificationService,
 | 
					    this._localNotificationService,
 | 
				
			||||||
    this._backgroundService,
 | 
					 | 
				
			||||||
    this._backupService,
 | 
					 | 
				
			||||||
    this._backupProvider,
 | 
					    this._backupProvider,
 | 
				
			||||||
    this.ref,
 | 
					    this.ref,
 | 
				
			||||||
  ) : super(
 | 
					  ) : super(
 | 
				
			||||||
@@ -54,15 +52,13 @@ class ManualUploadNotifier extends StateNotifier<ManualUploadState> {
 | 
				
			|||||||
              fileName: '...',
 | 
					              fileName: '...',
 | 
				
			||||||
              fileType: '...',
 | 
					              fileType: '...',
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            manualUploadsTotal: 0,
 | 
					            totalAssetsToUpload: 0,
 | 
				
			||||||
            manualUploadSuccess: 0,
 | 
					            successfulUploads: 0,
 | 
				
			||||||
            manualUploadFailures: 0,
 | 
					            currentAssetIndex: 0,
 | 
				
			||||||
 | 
					            showDetailedNotification: false,
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  int get _uploadedAssetsCount =>
 | 
					 | 
				
			||||||
      state.manualUploadSuccess + state.manualUploadFailures;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  String _lastPrintedDetailContent = '';
 | 
					  String _lastPrintedDetailContent = '';
 | 
				
			||||||
  String? _lastPrintedDetailTitle;
 | 
					  String? _lastPrintedDetailTitle;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -78,11 +74,12 @@ class ManualUploadNotifier extends StateNotifier<ManualUploadState> {
 | 
				
			|||||||
      _localNotificationService.showOrUpdateManualUploadStatus(
 | 
					      _localNotificationService.showOrUpdateManualUploadStatus(
 | 
				
			||||||
        "backup_background_service_in_progress_notification".tr(),
 | 
					        "backup_background_service_in_progress_notification".tr(),
 | 
				
			||||||
        formatAssetBackupProgress(
 | 
					        formatAssetBackupProgress(
 | 
				
			||||||
          _uploadedAssetsCount,
 | 
					          state.currentAssetIndex,
 | 
				
			||||||
          state.manualUploadsTotal,
 | 
					          state.totalAssetsToUpload,
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        maxProgress: state.manualUploadsTotal,
 | 
					        maxProgress: state.totalAssetsToUpload,
 | 
				
			||||||
        progress: _uploadedAssetsCount,
 | 
					        progress: state.currentAssetIndex,
 | 
				
			||||||
 | 
					        showActions: true,
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -103,45 +100,53 @@ class ManualUploadNotifier extends StateNotifier<ManualUploadState> {
 | 
				
			|||||||
          progress: total > 0 ? (progress * 1000) ~/ total : 0,
 | 
					          progress: total > 0 ? (progress * 1000) ~/ total : 0,
 | 
				
			||||||
          maxProgress: 1000,
 | 
					          maxProgress: 1000,
 | 
				
			||||||
          isDetailed: true,
 | 
					          isDetailed: true,
 | 
				
			||||||
 | 
					          // Detailed noitifcation is displayed for Single asset uploads. Show actions for such case
 | 
				
			||||||
 | 
					          showActions: state.totalAssetsToUpload == 1,
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void _onManualAssetUploaded(
 | 
					  void _onAssetUploaded(
 | 
				
			||||||
    String deviceAssetId,
 | 
					    String deviceAssetId,
 | 
				
			||||||
    String deviceId,
 | 
					    String deviceId,
 | 
				
			||||||
    bool isDuplicated,
 | 
					    bool isDuplicated,
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    state = state.copyWith(manualUploadSuccess: state.manualUploadSuccess + 1);
 | 
					    state = state.copyWith(successfulUploads: state.successfulUploads + 1);
 | 
				
			||||||
    _backupProvider.updateServerInfo();
 | 
					    _backupProvider.updateServerInfo();
 | 
				
			||||||
    if (state.manualUploadsTotal > 1) {
 | 
					 | 
				
			||||||
      _throttledNotifiy();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void _onManualBackupError(ErrorUploadAsset errorAssetInfo) {
 | 
					  void _onAssetUploadError(ErrorUploadAsset errorAssetInfo) {
 | 
				
			||||||
    state =
 | 
					    ref.watch(errorBackupListProvider.notifier).add(errorAssetInfo);
 | 
				
			||||||
        state.copyWith(manualUploadFailures: state.manualUploadFailures + 1);
 | 
					 | 
				
			||||||
    if (state.manualUploadsTotal > 1) {
 | 
					 | 
				
			||||||
      _throttledNotifiy();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void _onProgress(int sent, int total) {
 | 
					  void _onProgress(int sent, int total) {
 | 
				
			||||||
 | 
					    state = state.copyWith(
 | 
				
			||||||
 | 
					      progressInPercentage: (sent.toDouble() / total.toDouble() * 100),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    if (state.showDetailedNotification) {
 | 
				
			||||||
      final title = "backup_background_service_current_upload_notification"
 | 
					      final title = "backup_background_service_current_upload_notification"
 | 
				
			||||||
          .tr(args: [state.currentUploadAsset.fileName]);
 | 
					          .tr(args: [state.currentUploadAsset.fileName]);
 | 
				
			||||||
      _throttledDetailNotify(title: title, progress: sent, total: total);
 | 
					      _throttledDetailNotify(title: title, progress: sent, total: total);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void _onSetCurrentBackupAsset(CurrentUploadAsset currentUploadAsset) {
 | 
					  void _onSetCurrentBackupAsset(CurrentUploadAsset currentUploadAsset) {
 | 
				
			||||||
    state = state.copyWith(currentUploadAsset: currentUploadAsset);
 | 
					    state = state.copyWith(
 | 
				
			||||||
 | 
					      currentUploadAsset: currentUploadAsset,
 | 
				
			||||||
 | 
					      currentAssetIndex: state.currentAssetIndex + 1,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    if (state.totalAssetsToUpload > 1) {
 | 
				
			||||||
 | 
					      _throttledNotifiy();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (state.showDetailedNotification) {
 | 
				
			||||||
      _throttledDetailNotify.title =
 | 
					      _throttledDetailNotify.title =
 | 
				
			||||||
          "backup_background_service_current_upload_notification"
 | 
					          "backup_background_service_current_upload_notification"
 | 
				
			||||||
              .tr(args: [currentUploadAsset.fileName]);
 | 
					              .tr(args: [currentUploadAsset.fileName]);
 | 
				
			||||||
      _throttledDetailNotify.progress = 0;
 | 
					      _throttledDetailNotify.progress = 0;
 | 
				
			||||||
      _throttledDetailNotify.total = 0;
 | 
					      _throttledDetailNotify.total = 0;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Future<bool> _startUpload(Iterable<Asset> allManualUploads) async {
 | 
					  Future<bool> _startUpload(Iterable<Asset> allManualUploads) async {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
@@ -161,11 +166,11 @@ class ManualUploadNotifier extends StateNotifier<ManualUploadState> {
 | 
				
			|||||||
          return false;
 | 
					          return false;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Reset state
 | 
					 | 
				
			||||||
        state = state.copyWith(
 | 
					        state = state.copyWith(
 | 
				
			||||||
          manualUploadsTotal: allManualUploads.length,
 | 
					          progressInPercentage: 0,
 | 
				
			||||||
          manualUploadSuccess: 0,
 | 
					          totalAssetsToUpload: allUploadAssets.length,
 | 
				
			||||||
          manualUploadFailures: 0,
 | 
					          successfulUploads: 0,
 | 
				
			||||||
 | 
					          currentAssetIndex: 0,
 | 
				
			||||||
          currentUploadAsset: CurrentUploadAsset(
 | 
					          currentUploadAsset: CurrentUploadAsset(
 | 
				
			||||||
            id: '...',
 | 
					            id: '...',
 | 
				
			||||||
            fileCreatedAt: DateTime.parse('2020-10-04'),
 | 
					            fileCreatedAt: DateTime.parse('2020-10-04'),
 | 
				
			||||||
@@ -174,8 +179,10 @@ class ManualUploadNotifier extends StateNotifier<ManualUploadState> {
 | 
				
			|||||||
          ),
 | 
					          ),
 | 
				
			||||||
          cancelToken: CancellationToken(),
 | 
					          cancelToken: CancellationToken(),
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					        // Reset Error List
 | 
				
			||||||
 | 
					        ref.watch(errorBackupListProvider.notifier).empty();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (state.manualUploadsTotal > 1) {
 | 
					        if (state.totalAssetsToUpload > 1) {
 | 
				
			||||||
          _throttledNotifiy();
 | 
					          _throttledNotifiy();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -184,15 +191,17 @@ class ManualUploadNotifier extends StateNotifier<ManualUploadState> {
 | 
				
			|||||||
            ref.read(appSettingsServiceProvider).getSetting<bool>(
 | 
					            ref.read(appSettingsServiceProvider).getSetting<bool>(
 | 
				
			||||||
                      AppSettingsEnum.backgroundBackupSingleProgress,
 | 
					                      AppSettingsEnum.backgroundBackupSingleProgress,
 | 
				
			||||||
                    ) ||
 | 
					                    ) ||
 | 
				
			||||||
                state.manualUploadsTotal == 1;
 | 
					                state.totalAssetsToUpload == 1;
 | 
				
			||||||
 | 
					        state =
 | 
				
			||||||
 | 
					            state.copyWith(showDetailedNotification: showDetailedNotification);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        final bool ok = await _backupService.backupAsset(
 | 
					        final bool ok = await ref.read(backupServiceProvider).backupAsset(
 | 
				
			||||||
              allUploadAssets,
 | 
					              allUploadAssets,
 | 
				
			||||||
              state.cancelToken,
 | 
					              state.cancelToken,
 | 
				
			||||||
          _onManualAssetUploaded,
 | 
					              _onAssetUploaded,
 | 
				
			||||||
          showDetailedNotification ? _onProgress : (sent, total) {},
 | 
					              _onProgress,
 | 
				
			||||||
          showDetailedNotification ? _onSetCurrentBackupAsset : (asset) {},
 | 
					              _onSetCurrentBackupAsset,
 | 
				
			||||||
          _onManualBackupError,
 | 
					              _onAssetUploadError,
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Close detailed notification
 | 
					        // Close detailed notification
 | 
				
			||||||
@@ -200,9 +209,20 @@ class ManualUploadNotifier extends StateNotifier<ManualUploadState> {
 | 
				
			|||||||
          LocalNotificationService.manualUploadDetailedNotificationID,
 | 
					          LocalNotificationService.manualUploadDetailedNotificationID,
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _log.info(
 | 
				
			||||||
 | 
					          '[_startUpload] Manual Upload Completed - success: ${state.successfulUploads},'
 | 
				
			||||||
 | 
					          ' failed: ${state.totalAssetsToUpload - state.successfulUploads}',
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
        bool hasErrors = false;
 | 
					        bool hasErrors = false;
 | 
				
			||||||
        if ((state.manualUploadFailures != 0 &&
 | 
					        // User cancelled upload
 | 
				
			||||||
                state.manualUploadSuccess == 0) ||
 | 
					        if (!ok && state.cancelToken.isCancelled) {
 | 
				
			||||||
 | 
					          await _localNotificationService.showOrUpdateManualUploadStatus(
 | 
				
			||||||
 | 
					            "backup_manual_title".tr(),
 | 
				
			||||||
 | 
					            "backup_manual_cancelled".tr(),
 | 
				
			||||||
 | 
					            presentBanner: true,
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					          hasErrors = true;
 | 
				
			||||||
 | 
					        } else if (state.successfulUploads == 0 ||
 | 
				
			||||||
            (!ok && !state.cancelToken.isCancelled)) {
 | 
					            (!ok && !state.cancelToken.isCancelled)) {
 | 
				
			||||||
          await _localNotificationService.showOrUpdateManualUploadStatus(
 | 
					          await _localNotificationService.showOrUpdateManualUploadStatus(
 | 
				
			||||||
            "backup_manual_title".tr(),
 | 
					            "backup_manual_title".tr(),
 | 
				
			||||||
@@ -210,7 +230,7 @@ class ManualUploadNotifier extends StateNotifier<ManualUploadState> {
 | 
				
			|||||||
            presentBanner: true,
 | 
					            presentBanner: true,
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
          hasErrors = true;
 | 
					          hasErrors = true;
 | 
				
			||||||
        } else if (state.manualUploadSuccess != 0) {
 | 
					        } else {
 | 
				
			||||||
          await _localNotificationService.showOrUpdateManualUploadStatus(
 | 
					          await _localNotificationService.showOrUpdateManualUploadStatus(
 | 
				
			||||||
            "backup_manual_title".tr(),
 | 
					            "backup_manual_title".tr(),
 | 
				
			||||||
            "backup_manual_success".tr(),
 | 
					            "backup_manual_success".tr(),
 | 
				
			||||||
@@ -219,6 +239,7 @@ class ManualUploadNotifier extends StateNotifier<ManualUploadState> {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        _backupProvider.updateBackupProgress(BackUpProgressEnum.idle);
 | 
					        _backupProvider.updateBackupProgress(BackUpProgressEnum.idle);
 | 
				
			||||||
 | 
					        _handleAppInActivity();
 | 
				
			||||||
        await _backupProvider.notifyBackgroundServiceCanRun();
 | 
					        await _backupProvider.notifyBackgroundServiceCanRun();
 | 
				
			||||||
        return !hasErrors;
 | 
					        return !hasErrors;
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
@@ -228,21 +249,35 @@ class ManualUploadNotifier extends StateNotifier<ManualUploadState> {
 | 
				
			|||||||
    } catch (e) {
 | 
					    } catch (e) {
 | 
				
			||||||
      debugPrint("ERROR _startUpload: ${e.toString()}");
 | 
					      debugPrint("ERROR _startUpload: ${e.toString()}");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    _backupProvider.updateBackupProgress(BackUpProgressEnum.idle);
 | 
				
			||||||
 | 
					    _handleAppInActivity();
 | 
				
			||||||
    await _localNotificationService.closeNotification(
 | 
					    await _localNotificationService.closeNotification(
 | 
				
			||||||
      LocalNotificationService.manualUploadDetailedNotificationID,
 | 
					      LocalNotificationService.manualUploadDetailedNotificationID,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    _backupProvider.updateBackupProgress(BackUpProgressEnum.idle);
 | 
					 | 
				
			||||||
    await _backupProvider.notifyBackgroundServiceCanRun();
 | 
					    await _backupProvider.notifyBackgroundServiceCanRun();
 | 
				
			||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void _handleAppInActivity() {
 | 
				
			||||||
 | 
					    final appState = ref.read(appStateProvider.notifier).getAppState();
 | 
				
			||||||
 | 
					    // The app is currently in background. Perform the necessary cleanups which
 | 
				
			||||||
 | 
					    // are on-hold for upload completion
 | 
				
			||||||
 | 
					    if (appState != AppStateEnum.active || appState != AppStateEnum.resumed) {
 | 
				
			||||||
 | 
					      ref.read(appStateProvider.notifier).handleAppInactivity();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void cancelBackup() {
 | 
					  void cancelBackup() {
 | 
				
			||||||
    if (_backupProvider.backupProgress != BackUpProgressEnum.manualInProgress) {
 | 
					    if (_backupProvider.backupProgress != BackUpProgressEnum.inProgress &&
 | 
				
			||||||
 | 
					        _backupProvider.backupProgress != BackUpProgressEnum.manualInProgress) {
 | 
				
			||||||
      _backupProvider.notifyBackgroundServiceCanRun();
 | 
					      _backupProvider.notifyBackgroundServiceCanRun();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    state.cancelToken.cancel();
 | 
					    state.cancelToken.cancel();
 | 
				
			||||||
 | 
					    if (_backupProvider.backupProgress != BackUpProgressEnum.manualInProgress) {
 | 
				
			||||||
      _backupProvider.updateBackupProgress(BackUpProgressEnum.idle);
 | 
					      _backupProvider.updateBackupProgress(BackUpProgressEnum.idle);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    state = state.copyWith(progressInPercentage: 0);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Future<bool> uploadAssets(
 | 
					  Future<bool> uploadAssets(
 | 
				
			||||||
    BuildContext context,
 | 
					    BuildContext context,
 | 
				
			||||||
@@ -250,7 +285,8 @@ class ManualUploadNotifier extends StateNotifier<ManualUploadState> {
 | 
				
			|||||||
  ) async {
 | 
					  ) async {
 | 
				
			||||||
    // assumes the background service is currently running and
 | 
					    // assumes the background service is currently running and
 | 
				
			||||||
    // waits until it has stopped to start the backup.
 | 
					    // waits until it has stopped to start the backup.
 | 
				
			||||||
    final bool hasLock = await _backgroundService.acquireLock();
 | 
					    final bool hasLock =
 | 
				
			||||||
 | 
					        await ref.read(backgroundServiceProvider).acquireLock();
 | 
				
			||||||
    if (!hasLock) {
 | 
					    if (!hasLock) {
 | 
				
			||||||
      debugPrint("[uploadAssets] could not acquire lock, exiting");
 | 
					      debugPrint("[uploadAssets] could not acquire lock, exiting");
 | 
				
			||||||
      ImmichToast.show(
 | 
					      ImmichToast.show(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,8 +4,10 @@ import 'package:flutter/foundation.dart';
 | 
				
			|||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:flutter_hooks/flutter_hooks.dart';
 | 
					import 'package:flutter_hooks/flutter_hooks.dart';
 | 
				
			||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/modules/backup/models/backup_state.model.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
 | 
					import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/backup/providers/error_backup_list.provider.dart';
 | 
					import 'package:immich_mobile/modules/backup/providers/error_backup_list.provider.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/modules/backup/providers/manual_upload.provider.dart';
 | 
				
			||||||
import 'package:immich_mobile/routing/router.dart';
 | 
					import 'package:immich_mobile/routing/router.dart';
 | 
				
			||||||
import 'package:photo_manager/photo_manager.dart';
 | 
					import 'package:photo_manager/photo_manager.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -13,8 +15,14 @@ class CurrentUploadingAssetInfoBox extends HookConsumerWidget {
 | 
				
			|||||||
  const CurrentUploadingAssetInfoBox({super.key});
 | 
					  const CurrentUploadingAssetInfoBox({super.key});
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Widget build(BuildContext context, WidgetRef ref) {
 | 
					  Widget build(BuildContext context, WidgetRef ref) {
 | 
				
			||||||
    var asset = ref.watch(backupProvider).currentUploadAsset;
 | 
					    var isManualUpload = ref.watch(backupProvider).backupProgress ==
 | 
				
			||||||
    var uploadProgress = ref.watch(backupProvider).progressInPercentage;
 | 
					        BackUpProgressEnum.manualInProgress;
 | 
				
			||||||
 | 
					    var asset = !isManualUpload
 | 
				
			||||||
 | 
					        ? ref.watch(backupProvider).currentUploadAsset
 | 
				
			||||||
 | 
					        : ref.watch(manualUploadProvider).currentUploadAsset;
 | 
				
			||||||
 | 
					    var uploadProgress = !isManualUpload
 | 
				
			||||||
 | 
					        ? ref.watch(backupProvider).progressInPercentage
 | 
				
			||||||
 | 
					        : ref.watch(manualUploadProvider).progressInPercentage;
 | 
				
			||||||
    final isShowThumbnail = useState(false);
 | 
					    final isShowThumbnail = useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    String getAssetCreationDate() {
 | 
					    String getAssetCreationDate() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			|||||||
import 'package:immich_mobile/modules/backup/background_service/background.service.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/error_backup_list.provider.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/backup/providers/ios_background_settings.provider.dart';
 | 
					import 'package:immich_mobile/modules/backup/providers/ios_background_settings.provider.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/modules/backup/providers/manual_upload.provider.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/backup/services/backup_verification.service.dart';
 | 
					import 'package:immich_mobile/modules/backup/services/backup_verification.service.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/backup/ui/current_backup_asset_info_box.dart';
 | 
					import 'package:immich_mobile/modules/backup/ui/current_backup_asset_info_box.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/backup/ui/ios_debug_info_tile.dart';
 | 
					import 'package:immich_mobile/modules/backup/ui/ios_debug_info_tile.dart';
 | 
				
			||||||
@@ -657,7 +658,9 @@ class BackupControllerPage extends HookConsumerWidget {
 | 
				
			|||||||
          top: 24,
 | 
					          top: 24,
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        child: Container(
 | 
					        child: Container(
 | 
				
			||||||
          child: backupState.backupProgress == BackUpProgressEnum.inProgress
 | 
					          child: backupState.backupProgress == BackUpProgressEnum.inProgress ||
 | 
				
			||||||
 | 
					                  backupState.backupProgress ==
 | 
				
			||||||
 | 
					                      BackUpProgressEnum.manualInProgress
 | 
				
			||||||
              ? ElevatedButton(
 | 
					              ? ElevatedButton(
 | 
				
			||||||
                  style: ElevatedButton.styleFrom(
 | 
					                  style: ElevatedButton.styleFrom(
 | 
				
			||||||
                    foregroundColor: Colors.grey[50],
 | 
					                    foregroundColor: Colors.grey[50],
 | 
				
			||||||
@@ -665,7 +668,12 @@ class BackupControllerPage extends HookConsumerWidget {
 | 
				
			|||||||
                    // padding: const EdgeInsets.all(14),
 | 
					                    // padding: const EdgeInsets.all(14),
 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
                  onPressed: () {
 | 
					                  onPressed: () {
 | 
				
			||||||
 | 
					                    if (backupState.backupProgress ==
 | 
				
			||||||
 | 
					                        BackUpProgressEnum.manualInProgress) {
 | 
				
			||||||
 | 
					                      ref.read(manualUploadProvider.notifier).cancelBackup();
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
                      ref.read(backupProvider.notifier).cancelBackup();
 | 
					                      ref.read(backupProvider.notifier).cancelBackup();
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                  },
 | 
					                  },
 | 
				
			||||||
                  child: const Text(
 | 
					                  child: const Text(
 | 
				
			||||||
                    "backup_controller_page_cancel",
 | 
					                    "backup_controller_page_cancel",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,19 @@
 | 
				
			|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/modules/backup/background_service/background.service.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/modules/backup/models/backup_state.model.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/modules/backup/providers/ios_background_settings.provider.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/modules/backup/providers/manual_upload.provider.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/modules/memories/providers/memory.provider.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/modules/onboarding/providers/gallery_permission.provider.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/modules/settings/providers/notification_permission.provider.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/shared/providers/asset.provider.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/shared/providers/release_info.provider.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/shared/providers/server_info.provider.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/shared/providers/websocket.provider.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/shared/services/immich_logger.service.dart';
 | 
				
			||||||
 | 
					import 'package:permission_handler/permission_handler.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum AppStateEnum {
 | 
					enum AppStateEnum {
 | 
				
			||||||
  active,
 | 
					  active,
 | 
				
			||||||
@@ -8,6 +23,70 @@ enum AppStateEnum {
 | 
				
			|||||||
  detached,
 | 
					  detached,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
final appStateProvider = StateProvider<AppStateEnum>((ref) {
 | 
					class AppStateNotiifer extends StateNotifier<AppStateEnum> {
 | 
				
			||||||
  return AppStateEnum.active;
 | 
					  final Ref ref;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  AppStateNotiifer(this.ref) : super(AppStateEnum.active);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void updateAppState(AppStateEnum appState) {
 | 
				
			||||||
 | 
					    state = appState;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  AppStateEnum getAppState() {
 | 
				
			||||||
 | 
					    return state;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void handleAppResume() {
 | 
				
			||||||
 | 
					    state = AppStateEnum.resumed;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var isAuthenticated = ref.watch(authenticationProvider).isAuthenticated;
 | 
				
			||||||
 | 
					    final permission = ref.watch(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();
 | 
				
			||||||
 | 
					      ref.watch(assetProvider.notifier).getAllAsset();
 | 
				
			||||||
 | 
					      ref.watch(serverInfoProvider.notifier).getServerVersion();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ref.watch(websocketProvider.notifier).connect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ref.watch(releaseInfoProvider.notifier).checkGithubReleaseInfo();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ref
 | 
				
			||||||
 | 
					        .watch(notificationPermissionProvider.notifier)
 | 
				
			||||||
 | 
					        .getNotificationPermission();
 | 
				
			||||||
 | 
					    ref.watch(galleryPermissionNotifier.notifier).getGalleryPermissionStatus();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ref.read(iOSBackgroundSettingsProvider.notifier).refresh();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ref.invalidate(memoryFutureProvider);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void handleAppInactivity() {
 | 
				
			||||||
 | 
					    state = AppStateEnum.inactive;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Do not handle inactivity if manual upload is in progress
 | 
				
			||||||
 | 
					    if (ref.watch(backupProvider.notifier).backupProgress !=
 | 
				
			||||||
 | 
					        BackUpProgressEnum.manualInProgress) {
 | 
				
			||||||
 | 
					      ImmichLogger().flush();
 | 
				
			||||||
 | 
					      ref.read(websocketProvider.notifier).disconnect();
 | 
				
			||||||
 | 
					      ref.read(backupProvider.notifier).cancelBackup();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void handleAppPause() {
 | 
				
			||||||
 | 
					    state = AppStateEnum.paused;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void handleAppDetached() {
 | 
				
			||||||
 | 
					    state = AppStateEnum.detached;
 | 
				
			||||||
 | 
					    ref.watch(manualUploadProvider.notifier).cancelBackup();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final appStateProvider =
 | 
				
			||||||
 | 
					    StateNotifierProvider<AppStateNotiifer, AppStateEnum>((ref) {
 | 
				
			||||||
 | 
					  return AppStateNotiifer(ref);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,13 +1,24 @@
 | 
				
			|||||||
 | 
					import 'package:flutter/foundation.dart';
 | 
				
			||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
 | 
					import 'package:flutter_local_notifications/flutter_local_notifications.dart';
 | 
				
			||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/modules/backup/providers/manual_upload.provider.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/modules/settings/providers/notification_permission.provider.dart';
 | 
				
			||||||
 | 
					import 'package:permission_handler/permission_handler.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
final localNotificationService = Provider((ref) => LocalNotificationService());
 | 
					final localNotificationService = Provider(
 | 
				
			||||||
 | 
					  (ref) => LocalNotificationService(
 | 
				
			||||||
 | 
					    ref.watch(notificationPermissionProvider),
 | 
				
			||||||
 | 
					    ref,
 | 
				
			||||||
 | 
					  ),
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class LocalNotificationService {
 | 
					class LocalNotificationService {
 | 
				
			||||||
  static final LocalNotificationService _instance =
 | 
					 | 
				
			||||||
      LocalNotificationService._internal();
 | 
					 | 
				
			||||||
  final FlutterLocalNotificationsPlugin _localNotificationPlugin =
 | 
					  final FlutterLocalNotificationsPlugin _localNotificationPlugin =
 | 
				
			||||||
      FlutterLocalNotificationsPlugin();
 | 
					      FlutterLocalNotificationsPlugin();
 | 
				
			||||||
 | 
					  final PermissionStatus _permissionStatus;
 | 
				
			||||||
 | 
					  final Ref ref;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  LocalNotificationService(this._permissionStatus, this.ref);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static const manualUploadNotificationID = 4;
 | 
					  static const manualUploadNotificationID = 4;
 | 
				
			||||||
  static const manualUploadDetailedNotificationID = 5;
 | 
					  static const manualUploadDetailedNotificationID = 5;
 | 
				
			||||||
@@ -15,9 +26,7 @@ class LocalNotificationService {
 | 
				
			|||||||
  static const manualUploadChannelID = 'immich/manualUpload';
 | 
					  static const manualUploadChannelID = 'immich/manualUpload';
 | 
				
			||||||
  static const manualUploadChannelNameDetailed = 'Manual Asset Upload Detailed';
 | 
					  static const manualUploadChannelNameDetailed = 'Manual Asset Upload Detailed';
 | 
				
			||||||
  static const manualUploadDetailedChannelID = 'immich/manualUploadDetailed';
 | 
					  static const manualUploadDetailedChannelID = 'immich/manualUploadDetailed';
 | 
				
			||||||
 | 
					  static const cancelUploadActionID = 'cancel_upload';
 | 
				
			||||||
  factory LocalNotificationService() => _instance;
 | 
					 | 
				
			||||||
  LocalNotificationService._internal();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Future<void> setup() async {
 | 
					  Future<void> setup() async {
 | 
				
			||||||
    const androidSetting = AndroidInitializationSettings('notification_icon');
 | 
					    const androidSetting = AndroidInitializationSettings('notification_icon');
 | 
				
			||||||
@@ -26,57 +35,29 @@ class LocalNotificationService {
 | 
				
			|||||||
    const initSettings =
 | 
					    const initSettings =
 | 
				
			||||||
        InitializationSettings(android: androidSetting, iOS: iosSetting);
 | 
					        InitializationSettings(android: androidSetting, iOS: iosSetting);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await _localNotificationPlugin.initialize(initSettings);
 | 
					    await _localNotificationPlugin.initialize(
 | 
				
			||||||
 | 
					      initSettings,
 | 
				
			||||||
 | 
					      onDidReceiveNotificationResponse:
 | 
				
			||||||
 | 
					          _onDidReceiveForegroundNotificationResponse,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Future<void> _showOrUpdateNotification(
 | 
					  Future<void> _showOrUpdateNotification(
 | 
				
			||||||
    int id,
 | 
					    int id,
 | 
				
			||||||
    String channelId,
 | 
					 | 
				
			||||||
    String channelName,
 | 
					 | 
				
			||||||
    String title,
 | 
					    String title,
 | 
				
			||||||
    String body, {
 | 
					    String body,
 | 
				
			||||||
    bool? ongoing,
 | 
					    AndroidNotificationDetails androidNotificationDetails,
 | 
				
			||||||
    bool? playSound,
 | 
					    DarwinNotificationDetails iosNotificationDetails,
 | 
				
			||||||
    bool? showProgress,
 | 
					  ) async {
 | 
				
			||||||
    Priority? priority,
 | 
					 | 
				
			||||||
    Importance? importance,
 | 
					 | 
				
			||||||
    bool? onlyAlertOnce,
 | 
					 | 
				
			||||||
    int? maxProgress,
 | 
					 | 
				
			||||||
    int? progress,
 | 
					 | 
				
			||||||
    bool? indeterminate,
 | 
					 | 
				
			||||||
    bool? presentBadge,
 | 
					 | 
				
			||||||
    bool? presentBanner,
 | 
					 | 
				
			||||||
    bool? presentList,
 | 
					 | 
				
			||||||
  }) async {
 | 
					 | 
				
			||||||
    var androidNotificationDetails = AndroidNotificationDetails(
 | 
					 | 
				
			||||||
      channelId,
 | 
					 | 
				
			||||||
      channelName,
 | 
					 | 
				
			||||||
      ticker: title,
 | 
					 | 
				
			||||||
      playSound: playSound ?? false,
 | 
					 | 
				
			||||||
      showProgress: showProgress ?? false,
 | 
					 | 
				
			||||||
      maxProgress: maxProgress ?? 0,
 | 
					 | 
				
			||||||
      progress: progress ?? 0,
 | 
					 | 
				
			||||||
      onlyAlertOnce: onlyAlertOnce ?? false,
 | 
					 | 
				
			||||||
      indeterminate: indeterminate ?? false,
 | 
					 | 
				
			||||||
      priority: priority ?? Priority.defaultPriority,
 | 
					 | 
				
			||||||
      importance: importance ?? Importance.defaultImportance,
 | 
					 | 
				
			||||||
      ongoing: ongoing ?? false,
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    var iosNotificationDetails = DarwinNotificationDetails(
 | 
					 | 
				
			||||||
      presentBadge: presentBadge ?? false,
 | 
					 | 
				
			||||||
      presentBanner: presentBanner ?? false,
 | 
					 | 
				
			||||||
      presentList: presentList ?? false,
 | 
					 | 
				
			||||||
      
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    final notificationDetails = NotificationDetails(
 | 
					    final notificationDetails = NotificationDetails(
 | 
				
			||||||
      android: androidNotificationDetails,
 | 
					      android: androidNotificationDetails,
 | 
				
			||||||
      iOS: iosNotificationDetails,
 | 
					      iOS: iosNotificationDetails,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (_permissionStatus == PermissionStatus.granted) {
 | 
				
			||||||
      await _localNotificationPlugin.show(id, title, body, notificationDetails);
 | 
					      await _localNotificationPlugin.show(id, title, body, notificationDetails);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Future<void> closeNotification(int id) {
 | 
					  Future<void> closeNotification(int id) {
 | 
				
			||||||
    return _localNotificationPlugin.cancel(id);
 | 
					    return _localNotificationPlugin.cancel(id);
 | 
				
			||||||
@@ -87,46 +68,76 @@ class LocalNotificationService {
 | 
				
			|||||||
    String body, {
 | 
					    String body, {
 | 
				
			||||||
    bool? isDetailed,
 | 
					    bool? isDetailed,
 | 
				
			||||||
    bool? presentBanner,
 | 
					    bool? presentBanner,
 | 
				
			||||||
 | 
					    bool? showActions,
 | 
				
			||||||
    int? maxProgress,
 | 
					    int? maxProgress,
 | 
				
			||||||
    int? progress,
 | 
					    int? progress,
 | 
				
			||||||
  }) {
 | 
					  }) {
 | 
				
			||||||
    var notificationlId = manualUploadNotificationID;
 | 
					    var notificationlId = manualUploadNotificationID;
 | 
				
			||||||
    var channelId = manualUploadChannelID;
 | 
					    var androidChannelID = manualUploadChannelID;
 | 
				
			||||||
    var channelName = manualUploadChannelName;
 | 
					    var androidChannelName = manualUploadChannelName;
 | 
				
			||||||
    // Separate Notification for Info/Alerts and Progress
 | 
					    // Separate Notification for Info/Alerts and Progress
 | 
				
			||||||
    if (isDetailed != null && isDetailed) {
 | 
					    if (isDetailed != null && isDetailed) {
 | 
				
			||||||
      notificationlId = manualUploadDetailedNotificationID;
 | 
					      notificationlId = manualUploadDetailedNotificationID;
 | 
				
			||||||
      channelId = manualUploadDetailedChannelID;
 | 
					      androidChannelID = manualUploadDetailedChannelID;
 | 
				
			||||||
      channelName = manualUploadChannelNameDetailed;
 | 
					      androidChannelName = manualUploadChannelNameDetailed;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    final isProgressNotification = maxProgress != null && progress != null;
 | 
					    // Progress notification
 | 
				
			||||||
    return isProgressNotification
 | 
					    final androidNotificationDetails = (maxProgress != null && progress != null)
 | 
				
			||||||
        ? _showOrUpdateNotification(
 | 
					        ? AndroidNotificationDetails(
 | 
				
			||||||
            notificationlId,
 | 
					            androidChannelID,
 | 
				
			||||||
            channelId,
 | 
					            androidChannelName,
 | 
				
			||||||
            channelName,
 | 
					            ticker: title,
 | 
				
			||||||
            title,
 | 
					 | 
				
			||||||
            body,
 | 
					 | 
				
			||||||
            showProgress: true,
 | 
					            showProgress: true,
 | 
				
			||||||
            onlyAlertOnce: true,
 | 
					            onlyAlertOnce: true,
 | 
				
			||||||
            maxProgress: maxProgress,
 | 
					            maxProgress: maxProgress,
 | 
				
			||||||
            progress: progress,
 | 
					            progress: progress,
 | 
				
			||||||
            indeterminate: false,
 | 
					            indeterminate: false,
 | 
				
			||||||
            presentList: true,
 | 
					            playSound: false,
 | 
				
			||||||
            priority: Priority.low,
 | 
					            priority: Priority.low,
 | 
				
			||||||
            importance: Importance.low,
 | 
					            importance: Importance.low,
 | 
				
			||||||
            presentBadge: true,
 | 
					 | 
				
			||||||
            ongoing: true,
 | 
					            ongoing: true,
 | 
				
			||||||
 | 
					            actions: (showActions ?? false)
 | 
				
			||||||
 | 
					                ? <AndroidNotificationAction>[
 | 
				
			||||||
 | 
					                    const AndroidNotificationAction(
 | 
				
			||||||
 | 
					                      cancelUploadActionID,
 | 
				
			||||||
 | 
					                      'Cancel',
 | 
				
			||||||
 | 
					                      showsUserInterface: true,
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
        : _showOrUpdateNotification(
 | 
					                  ]
 | 
				
			||||||
            notificationlId,
 | 
					                : null,
 | 
				
			||||||
            channelId,
 | 
					          )
 | 
				
			||||||
            channelName,
 | 
					        // Non-progress notification
 | 
				
			||||||
            title,
 | 
					        : AndroidNotificationDetails(
 | 
				
			||||||
            body,
 | 
					            androidChannelID,
 | 
				
			||||||
            presentList: true,
 | 
					            androidChannelName,
 | 
				
			||||||
 | 
					            playSound: false,
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final iosNotificationDetails = DarwinNotificationDetails(
 | 
				
			||||||
      presentBadge: true,
 | 
					      presentBadge: true,
 | 
				
			||||||
 | 
					      presentList: true,
 | 
				
			||||||
      presentBanner: presentBanner,
 | 
					      presentBanner: presentBanner,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return _showOrUpdateNotification(
 | 
				
			||||||
 | 
					      notificationlId,
 | 
				
			||||||
 | 
					      title,
 | 
				
			||||||
 | 
					      body,
 | 
				
			||||||
 | 
					      androidNotificationDetails,
 | 
				
			||||||
 | 
					      iosNotificationDetails,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void _onDidReceiveForegroundNotificationResponse(
 | 
				
			||||||
 | 
					    NotificationResponse notificationResponse,
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    // Handle notification actions
 | 
				
			||||||
 | 
					    switch (notificationResponse.actionId) {
 | 
				
			||||||
 | 
					      case cancelUploadActionID:
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          debugPrint("User cancelled manual upload operation");
 | 
				
			||||||
 | 
					          ref.read(manualUploadProvider.notifier).cancelBackup();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user