mirror of
https://github.com/immich-app/immich.git
synced 2024-12-25 10:43:13 +02: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:
parent
b790354f9a
commit
77a5820c3c
@ -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,44 +100,52 @@ 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) {
|
||||||
final title = "backup_background_service_current_upload_notification"
|
state = state.copyWith(
|
||||||
.tr(args: [state.currentUploadAsset.fileName]);
|
progressInPercentage: (sent.toDouble() / total.toDouble() * 100),
|
||||||
_throttledDetailNotify(title: title, progress: sent, total: total);
|
);
|
||||||
|
if (state.showDetailedNotification) {
|
||||||
|
final title = "backup_background_service_current_upload_notification"
|
||||||
|
.tr(args: [state.currentUploadAsset.fileName]);
|
||||||
|
_throttledDetailNotify(title: title, progress: sent, total: total);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onSetCurrentBackupAsset(CurrentUploadAsset currentUploadAsset) {
|
void _onSetCurrentBackupAsset(CurrentUploadAsset currentUploadAsset) {
|
||||||
state = state.copyWith(currentUploadAsset: currentUploadAsset);
|
state = state.copyWith(
|
||||||
_throttledDetailNotify.title =
|
currentUploadAsset: currentUploadAsset,
|
||||||
"backup_background_service_current_upload_notification"
|
currentAssetIndex: state.currentAssetIndex + 1,
|
||||||
.tr(args: [currentUploadAsset.fileName]);
|
);
|
||||||
_throttledDetailNotify.progress = 0;
|
if (state.totalAssetsToUpload > 1) {
|
||||||
_throttledDetailNotify.total = 0;
|
_throttledNotifiy();
|
||||||
|
}
|
||||||
|
if (state.showDetailedNotification) {
|
||||||
|
_throttledDetailNotify.title =
|
||||||
|
"backup_background_service_current_upload_notification"
|
||||||
|
.tr(args: [currentUploadAsset.fileName]);
|
||||||
|
_throttledDetailNotify.progress = 0;
|
||||||
|
_throttledDetailNotify.total = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> _startUpload(Iterable<Asset> allManualUploads) async {
|
Future<bool> _startUpload(Iterable<Asset> allManualUploads) async {
|
||||||
@ -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,25 +191,38 @@ 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
|
||||||
await _localNotificationService.closeNotification(
|
await _localNotificationService.closeNotification(
|
||||||
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,20 +249,34 @@ 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();
|
||||||
_backupProvider.updateBackupProgress(BackUpProgressEnum.idle);
|
if (_backupProvider.backupProgress != BackUpProgressEnum.manualInProgress) {
|
||||||
|
_backupProvider.updateBackupProgress(BackUpProgressEnum.idle);
|
||||||
|
}
|
||||||
|
state = state.copyWith(progressInPercentage: 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> uploadAssets(
|
Future<bool> uploadAssets(
|
||||||
@ -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: () {
|
||||||
ref.read(backupProvider.notifier).cancelBackup();
|
if (backupState.backupProgress ==
|
||||||
|
BackUpProgressEnum.manualInProgress) {
|
||||||
|
ref.read(manualUploadProvider.notifier).cancelBackup();
|
||||||
|
} else {
|
||||||
|
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,56 +35,28 @@ 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,
|
||||||
);
|
);
|
||||||
|
|
||||||
await _localNotificationPlugin.show(id, title, body, notificationDetails);
|
if (_permissionStatus == PermissionStatus.granted) {
|
||||||
|
await _localNotificationPlugin.show(id, title, body, notificationDetails);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> closeNotification(int id) {
|
Future<void> closeNotification(int 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,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
: null,
|
||||||
)
|
)
|
||||||
: _showOrUpdateNotification(
|
// Non-progress notification
|
||||||
notificationlId,
|
: AndroidNotificationDetails(
|
||||||
channelId,
|
androidChannelID,
|
||||||
channelName,
|
androidChannelName,
|
||||||
title,
|
playSound: false,
|
||||||
body,
|
|
||||||
presentList: true,
|
|
||||||
presentBadge: true,
|
|
||||||
presentBanner: presentBanner,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final iosNotificationDetails = DarwinNotificationDetails(
|
||||||
|
presentBadge: true,
|
||||||
|
presentList: true,
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user