mirror of
https://github.com/immich-app/immich
synced 2025-11-14 17:36:12 +00:00
feat(mobile): Manual asset upload (#3445)
* fix: exclude albums filter in backup provider * refactor: Separate builder methods for Top Control App Bar buttons * fix: Show download button only for Remote only assets * fix(mobile): Force Refresh duration is too low to trigger it consistently * feat(mobile): Make Buttons dynamic in Home Selection DraggableScrollableSheet * feat(mobile): Manual Asset upload * refactor(mobile): Replace _showToast with ImmichToast calls * refactor(mobile): home_page selectionAssetState handling * chore(mobile): min and initial size of DraggableScrollState increased This is to prevent the buttons in the bottom sheet getting clipped behind the 3 way navigation buttons in the default density of Android devices * feat(mobile): notifications for manual upload progress * wording --------- Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
parent
f1b92718d5
commit
deaf81e2a4
27 changed files with 887 additions and 163 deletions
|
|
@ -18,6 +18,7 @@ import 'package:immich_mobile/modules/backup/services/backup.service.dart';
|
|||
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
|
||||
import 'package:immich_mobile/shared/models/store.dart';
|
||||
import 'package:immich_mobile/shared/services/api.service.dart';
|
||||
import 'package:immich_mobile/utils/backup_progress.dart';
|
||||
import 'package:immich_mobile/utils/diff.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:path_provider_ios/path_provider_ios.dart';
|
||||
|
|
@ -34,7 +35,6 @@ class BackgroundService {
|
|||
MethodChannel('immich/foregroundChannel');
|
||||
static const MethodChannel _backgroundChannel =
|
||||
MethodChannel('immich/backgroundChannel');
|
||||
static final NumberFormat numberFormat = NumberFormat("###0.##");
|
||||
static const notifyInterval = Duration(milliseconds: 400);
|
||||
bool _isBackgroundInitialized = false;
|
||||
CancellationToken? _cancellationToken;
|
||||
|
|
@ -48,10 +48,10 @@ class BackgroundService {
|
|||
int _assetsToUploadCount = 0;
|
||||
String _lastPrintedDetailContent = "";
|
||||
String? _lastPrintedDetailTitle;
|
||||
late final _Throttle _throttledNotifiy =
|
||||
_Throttle(_updateProgress, notifyInterval);
|
||||
late final _Throttle _throttledDetailNotify =
|
||||
_Throttle(_updateDetailProgress, notifyInterval);
|
||||
late final ThrottleProgressUpdate _throttledNotifiy =
|
||||
ThrottleProgressUpdate(_updateProgress, notifyInterval);
|
||||
late final ThrottleProgressUpdate _throttledDetailNotify =
|
||||
ThrottleProgressUpdate(_updateDetailProgress, notifyInterval);
|
||||
|
||||
bool get isBackgroundInitialized {
|
||||
return _isBackgroundInitialized;
|
||||
|
|
@ -439,7 +439,12 @@ class BackgroundService {
|
|||
_uploadedAssetsCount = 0;
|
||||
_updateNotification(
|
||||
title: "backup_background_service_in_progress_notification".tr(),
|
||||
content: notifyTotalProgress ? _formatAssetBackupProgress() : null,
|
||||
content: notifyTotalProgress
|
||||
? formatAssetBackupProgress(
|
||||
_uploadedAssetsCount,
|
||||
_assetsToUploadCount,
|
||||
)
|
||||
: null,
|
||||
progress: 0,
|
||||
max: notifyTotalProgress ? _assetsToUploadCount : 0,
|
||||
indeterminate: !notifyTotalProgress,
|
||||
|
|
@ -464,11 +469,6 @@ class BackgroundService {
|
|||
return ok;
|
||||
}
|
||||
|
||||
String _formatAssetBackupProgress() {
|
||||
final int percent = (_uploadedAssetsCount * 100) ~/ _assetsToUploadCount;
|
||||
return "$percent% ($_uploadedAssetsCount/$_assetsToUploadCount)";
|
||||
}
|
||||
|
||||
void _onAssetUploaded(String deviceAssetId, String deviceId, bool isDup) {
|
||||
_uploadedAssetsCount++;
|
||||
_throttledNotifiy();
|
||||
|
|
@ -480,7 +480,7 @@ class BackgroundService {
|
|||
|
||||
void _updateDetailProgress(String? title, int progress, int total) {
|
||||
final String msg =
|
||||
total > 0 ? _humanReadableBytesProgress(progress, total) : "";
|
||||
total > 0 ? humanReadableBytesProgress(progress, total) : "";
|
||||
// only update if message actually differs (to stop many useless notification updates on large assets or slow connections)
|
||||
if (msg != _lastPrintedDetailContent || _lastPrintedDetailTitle != title) {
|
||||
_lastPrintedDetailContent = msg;
|
||||
|
|
@ -500,7 +500,10 @@ class BackgroundService {
|
|||
progress: _uploadedAssetsCount,
|
||||
max: _assetsToUploadCount,
|
||||
title: title,
|
||||
content: _formatAssetBackupProgress(),
|
||||
content: formatAssetBackupProgress(
|
||||
_uploadedAssetsCount,
|
||||
_assetsToUploadCount,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -546,26 +549,6 @@ class BackgroundService {
|
|||
return true;
|
||||
}
|
||||
|
||||
/// prints percentage and absolute progress in useful (kilo/mega/giga)bytes
|
||||
static String _humanReadableBytesProgress(int bytes, int bytesTotal) {
|
||||
String unit = "KB"; // Kilobyte
|
||||
if (bytesTotal >= 0x40000000) {
|
||||
unit = "GB"; // Gigabyte
|
||||
bytes >>= 20;
|
||||
bytesTotal >>= 20;
|
||||
} else if (bytesTotal >= 0x100000) {
|
||||
unit = "MB"; // Megabyte
|
||||
bytes >>= 10;
|
||||
bytesTotal >>= 10;
|
||||
} else if (bytesTotal < 0x400) {
|
||||
return "$bytes / $bytesTotal B";
|
||||
}
|
||||
final int percent = (bytes * 100) ~/ bytesTotal;
|
||||
final String done = numberFormat.format(bytes / 1024.0);
|
||||
final String total = numberFormat.format(bytesTotal / 1024.0);
|
||||
return "$percent% ($done/$total$unit)";
|
||||
}
|
||||
|
||||
Future<DateTime?> getIOSBackupLastRun(IosBackgroundTask task) async {
|
||||
if (!Platform.isIOS) {
|
||||
return null;
|
||||
|
|
@ -598,43 +581,6 @@ class BackgroundService {
|
|||
|
||||
enum IosBackgroundTask { fetch, processing }
|
||||
|
||||
class _Throttle {
|
||||
_Throttle(this._fun, Duration interval) : _interval = interval.inMicroseconds;
|
||||
final void Function(String?, int, int) _fun;
|
||||
final int _interval;
|
||||
int _invokedAt = 0;
|
||||
Timer? _timer;
|
||||
|
||||
String? title;
|
||||
int progress = 0;
|
||||
int total = 0;
|
||||
|
||||
void call({
|
||||
final String? title,
|
||||
final int progress = 0,
|
||||
final int total = 0,
|
||||
}) {
|
||||
final time = Timeline.now;
|
||||
this.title = title ?? this.title;
|
||||
this.progress = progress;
|
||||
this.total = total;
|
||||
if (time > _invokedAt + _interval) {
|
||||
_timer?.cancel();
|
||||
_onTimeElapsed();
|
||||
} else {
|
||||
_timer ??= Timer(Duration(microseconds: _interval), _onTimeElapsed);
|
||||
}
|
||||
}
|
||||
|
||||
void _onTimeElapsed() {
|
||||
_invokedAt = Timeline.now;
|
||||
_fun(title, progress, total);
|
||||
_timer = null;
|
||||
// clear title to not send/overwrite it next time if unchanged
|
||||
title = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// entry point called by Kotlin/Java code; needs to be a top-level function
|
||||
@pragma('vm:entry-point')
|
||||
void _nativeEntry() {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,13 @@ import 'package:photo_manager/photo_manager.dart';
|
|||
import 'package:immich_mobile/modules/backup/models/available_album.model.dart';
|
||||
import 'package:immich_mobile/modules/backup/models/current_upload_asset.model.dart';
|
||||
|
||||
enum BackUpProgressEnum { idle, inProgress, inBackground, done }
|
||||
enum BackUpProgressEnum {
|
||||
idle,
|
||||
inProgress,
|
||||
manualInProgress,
|
||||
inBackground,
|
||||
done
|
||||
}
|
||||
|
||||
class BackUpState {
|
||||
// enum
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
import 'package:cancellation_token_http/http.dart';
|
||||
import 'package:immich_mobile/modules/backup/models/current_upload_asset.model.dart';
|
||||
|
||||
class ManualUploadState {
|
||||
final CancellationToken cancelToken;
|
||||
|
||||
final double progressInPercentage;
|
||||
|
||||
// Current Backup Asset
|
||||
final CurrentUploadAsset currentUploadAsset;
|
||||
|
||||
/// Manual Upload
|
||||
final int manualUploadsTotal;
|
||||
final int manualUploadFailures;
|
||||
final int manualUploadSuccess;
|
||||
|
||||
const ManualUploadState({
|
||||
required this.progressInPercentage,
|
||||
required this.cancelToken,
|
||||
required this.currentUploadAsset,
|
||||
required this.manualUploadsTotal,
|
||||
required this.manualUploadFailures,
|
||||
required this.manualUploadSuccess,
|
||||
});
|
||||
|
||||
ManualUploadState copyWith({
|
||||
double? progressInPercentage,
|
||||
CancellationToken? cancelToken,
|
||||
CurrentUploadAsset? currentUploadAsset,
|
||||
int? manualUploadsTotal,
|
||||
int? manualUploadFailures,
|
||||
int? manualUploadSuccess,
|
||||
}) {
|
||||
return ManualUploadState(
|
||||
progressInPercentage: progressInPercentage ?? this.progressInPercentage,
|
||||
cancelToken: cancelToken ?? this.cancelToken,
|
||||
currentUploadAsset: currentUploadAsset ?? this.currentUploadAsset,
|
||||
manualUploadsTotal: manualUploadsTotal ?? this.manualUploadsTotal,
|
||||
manualUploadFailures: manualUploadFailures ?? this.manualUploadFailures,
|
||||
manualUploadSuccess: manualUploadSuccess ?? this.manualUploadSuccess,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ManualUploadState(progressInPercentage: $progressInPercentage, cancelToken: $cancelToken, currentUploadAsset: $currentUploadAsset, manualUploadsTotal: $manualUploadsTotal, manualUploadSuccess: $manualUploadSuccess, manualUploadFailures: $manualUploadFailures)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is ManualUploadState &&
|
||||
other.progressInPercentage == progressInPercentage &&
|
||||
other.cancelToken == cancelToken &&
|
||||
other.currentUploadAsset == currentUploadAsset &&
|
||||
other.manualUploadsTotal == manualUploadsTotal &&
|
||||
other.manualUploadFailures == manualUploadFailures &&
|
||||
other.manualUploadSuccess == manualUploadSuccess;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return progressInPercentage.hashCode ^
|
||||
cancelToken.hashCode ^
|
||||
currentUploadAsset.hashCode ^
|
||||
manualUploadsTotal.hashCode ^
|
||||
manualUploadFailures.hashCode ^
|
||||
manualUploadSuccess.hashCode;
|
||||
}
|
||||
}
|
||||
|
|
@ -388,7 +388,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||
|
||||
if (state.backupProgress != BackUpProgressEnum.inBackground) {
|
||||
await _getBackupAlbumsInfo();
|
||||
await _updateServerInfo();
|
||||
await updateServerInfo();
|
||||
await _updateBackupAssetCount();
|
||||
}
|
||||
}
|
||||
|
|
@ -465,7 +465,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||
_onSetCurrentBackupAsset,
|
||||
_onBackupError,
|
||||
);
|
||||
await _notifyBackgroundServiceCanRun();
|
||||
await notifyBackgroundServiceCanRun();
|
||||
} else {
|
||||
openAppSettings();
|
||||
}
|
||||
|
|
@ -487,7 +487,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||
|
||||
void cancelBackup() {
|
||||
if (state.backupProgress != BackUpProgressEnum.inProgress) {
|
||||
_notifyBackgroundServiceCanRun();
|
||||
notifyBackgroundServiceCanRun();
|
||||
}
|
||||
state.cancelToken.cancel();
|
||||
state = state.copyWith(
|
||||
|
|
@ -537,7 +537,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||
_updatePersistentAlbumsSelection();
|
||||
}
|
||||
|
||||
_updateServerInfo();
|
||||
updateServerInfo();
|
||||
}
|
||||
|
||||
void _onUploadProgress(int sent, int total) {
|
||||
|
|
@ -546,7 +546,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||
);
|
||||
}
|
||||
|
||||
Future<void> _updateServerInfo() async {
|
||||
Future<void> updateServerInfo() async {
|
||||
final serverInfo = await _serverInfoService.getServerInfo();
|
||||
|
||||
// Update server info
|
||||
|
|
@ -569,9 +569,9 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||
|
||||
// Check if this device is enable backup by the user
|
||||
if (state.autoBackup) {
|
||||
// check if backup is alreayd in process - then return
|
||||
// check if backup is already in process - then return
|
||||
if (state.backupProgress == BackUpProgressEnum.inProgress) {
|
||||
log.info("[_resumeBackup] Backup is already in progress - abort");
|
||||
log.info("[_resumeBackup] Auto Backup is already in progress - abort");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -580,6 +580,11 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||
return;
|
||||
}
|
||||
|
||||
if (state.backupProgress == BackUpProgressEnum.manualInProgress) {
|
||||
log.info("[_resumeBackup] Manual upload is running - abort");
|
||||
return;
|
||||
}
|
||||
|
||||
// Run backup
|
||||
log.info("[_resumeBackup] Start back up");
|
||||
await startBackupProcess();
|
||||
|
|
@ -594,7 +599,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||
.findAll();
|
||||
final List<BackupAlbum> excludedBackupAlbums = await _db.backupAlbums
|
||||
.filter()
|
||||
.selectionEqualTo(BackupSelection.select)
|
||||
.selectionEqualTo(BackupSelection.exclude)
|
||||
.findAll();
|
||||
Set<AvailableAlbum> selectedAlbums = state.selectedBackupAlbums;
|
||||
Set<AvailableAlbum> excludedAlbums = state.excludedBackupAlbums;
|
||||
|
|
@ -646,7 +651,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||
return result;
|
||||
}
|
||||
|
||||
Future<void> _notifyBackgroundServiceCanRun() async {
|
||||
Future<void> notifyBackgroundServiceCanRun() async {
|
||||
const allowedStates = [
|
||||
AppStateEnum.inactive,
|
||||
AppStateEnum.paused,
|
||||
|
|
@ -656,6 +661,11 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||
_backgroundService.releaseLock();
|
||||
}
|
||||
}
|
||||
|
||||
BackUpProgressEnum get backupProgress => state.backupProgress;
|
||||
void updateBackupProgress(BackUpProgressEnum backupProgress) {
|
||||
state = state.copyWith(backupProgress: backupProgress);
|
||||
}
|
||||
}
|
||||
|
||||
final backupProvider =
|
||||
|
|
|
|||
300
mobile/lib/modules/backup/providers/manual_upload.provider.dart
Normal file
300
mobile/lib/modules/backup/providers/manual_upload.provider.dart
Normal file
|
|
@ -0,0 +1,300 @@
|
|||
import 'package:cancellation_token_http/http.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:fluttertoast/fluttertoast.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/models/current_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/providers/backup.provider.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/settings/providers/app_settings.provider.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/services/local_notification.service.dart';
|
||||
import 'package:immich_mobile/shared/ui/immich_toast.dart';
|
||||
import 'package:immich_mobile/utils/backup_progress.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:photo_manager/photo_manager.dart';
|
||||
|
||||
final manualUploadProvider =
|
||||
StateNotifierProvider<ManualUploadNotifier, ManualUploadState>((ref) {
|
||||
return ManualUploadNotifier(
|
||||
ref.watch(localNotificationService),
|
||||
ref.watch(backgroundServiceProvider),
|
||||
ref.watch(backupServiceProvider),
|
||||
ref.watch(backupProvider.notifier),
|
||||
ref,
|
||||
);
|
||||
});
|
||||
|
||||
class ManualUploadNotifier extends StateNotifier<ManualUploadState> {
|
||||
final LocalNotificationService _localNotificationService;
|
||||
final BackgroundService _backgroundService;
|
||||
final BackupService _backupService;
|
||||
final BackupNotifier _backupProvider;
|
||||
final Ref ref;
|
||||
|
||||
ManualUploadNotifier(
|
||||
this._localNotificationService,
|
||||
this._backgroundService,
|
||||
this._backupService,
|
||||
this._backupProvider,
|
||||
this.ref,
|
||||
) : super(
|
||||
ManualUploadState(
|
||||
progressInPercentage: 0,
|
||||
cancelToken: CancellationToken(),
|
||||
currentUploadAsset: CurrentUploadAsset(
|
||||
id: '...',
|
||||
fileCreatedAt: DateTime.parse('2020-10-04'),
|
||||
fileName: '...',
|
||||
fileType: '...',
|
||||
),
|
||||
manualUploadsTotal: 0,
|
||||
manualUploadSuccess: 0,
|
||||
manualUploadFailures: 0,
|
||||
),
|
||||
);
|
||||
|
||||
int get _uploadedAssetsCount =>
|
||||
state.manualUploadSuccess + state.manualUploadFailures;
|
||||
|
||||
String _lastPrintedDetailContent = '';
|
||||
String? _lastPrintedDetailTitle;
|
||||
|
||||
static const notifyInterval = Duration(milliseconds: 500);
|
||||
late final ThrottleProgressUpdate _throttledNotifiy =
|
||||
ThrottleProgressUpdate(_updateProgress, notifyInterval);
|
||||
late final ThrottleProgressUpdate _throttledDetailNotify =
|
||||
ThrottleProgressUpdate(_updateDetailProgress, notifyInterval);
|
||||
|
||||
void _updateProgress(String? title, int progress, int total) {
|
||||
// Guard against throttling calling this method after the upload is done
|
||||
if (_backupProvider.backupProgress == BackUpProgressEnum.manualInProgress) {
|
||||
_localNotificationService.showOrUpdateManualUploadStatus(
|
||||
"backup_background_service_in_progress_notification".tr(),
|
||||
formatAssetBackupProgress(
|
||||
_uploadedAssetsCount,
|
||||
state.manualUploadsTotal,
|
||||
),
|
||||
maxProgress: state.manualUploadsTotal,
|
||||
progress: _uploadedAssetsCount,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _updateDetailProgress(String? title, int progress, int total) {
|
||||
// Guard against throttling calling this method after the upload is done
|
||||
if (_backupProvider.backupProgress == BackUpProgressEnum.manualInProgress) {
|
||||
final String msg =
|
||||
total > 0 ? humanReadableBytesProgress(progress, total) : "";
|
||||
// only update if message actually differs (to stop many useless notification updates on large assets or slow connections)
|
||||
if (msg != _lastPrintedDetailContent ||
|
||||
title != _lastPrintedDetailTitle) {
|
||||
_lastPrintedDetailContent = msg;
|
||||
_lastPrintedDetailTitle = title;
|
||||
_localNotificationService.showOrUpdateManualUploadStatus(
|
||||
title ?? 'Uploading',
|
||||
msg,
|
||||
progress: total > 0 ? (progress * 1000) ~/ total : 0,
|
||||
maxProgress: 1000,
|
||||
isDetailed: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _onManualAssetUploaded(
|
||||
String deviceAssetId,
|
||||
String deviceId,
|
||||
bool isDuplicated,
|
||||
) {
|
||||
state = state.copyWith(manualUploadSuccess: state.manualUploadSuccess + 1);
|
||||
_backupProvider.updateServerInfo();
|
||||
if (state.manualUploadsTotal > 1) {
|
||||
_throttledNotifiy();
|
||||
}
|
||||
}
|
||||
|
||||
void _onManualBackupError(ErrorUploadAsset errorAssetInfo) {
|
||||
state =
|
||||
state.copyWith(manualUploadFailures: state.manualUploadFailures + 1);
|
||||
if (state.manualUploadsTotal > 1) {
|
||||
_throttledNotifiy();
|
||||
}
|
||||
}
|
||||
|
||||
void _onProgress(int sent, int total) {
|
||||
final title = "backup_background_service_current_upload_notification"
|
||||
.tr(args: [state.currentUploadAsset.fileName]);
|
||||
_throttledDetailNotify(title: title, progress: sent, total: total);
|
||||
}
|
||||
|
||||
void _onSetCurrentBackupAsset(CurrentUploadAsset currentUploadAsset) {
|
||||
state = state.copyWith(currentUploadAsset: currentUploadAsset);
|
||||
_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 {
|
||||
try {
|
||||
_backupProvider.updateBackupProgress(BackUpProgressEnum.manualInProgress);
|
||||
|
||||
if (ref.read(galleryPermissionNotifier.notifier).hasPermission) {
|
||||
await PhotoManager.clearFileCache();
|
||||
|
||||
Set<AssetEntity> allUploadAssets = allManualUploads
|
||||
.where((e) => e.isLocal && e.local != null)
|
||||
.map((e) => e.local!)
|
||||
.toSet();
|
||||
|
||||
if (allUploadAssets.isEmpty) {
|
||||
debugPrint("[_startUpload] No Assets to upload - Abort Process");
|
||||
_backupProvider.updateBackupProgress(BackUpProgressEnum.idle);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reset state
|
||||
state = state.copyWith(
|
||||
manualUploadsTotal: allManualUploads.length,
|
||||
manualUploadSuccess: 0,
|
||||
manualUploadFailures: 0,
|
||||
currentUploadAsset: CurrentUploadAsset(
|
||||
id: '...',
|
||||
fileCreatedAt: DateTime.parse('2020-10-04'),
|
||||
fileName: '...',
|
||||
fileType: '...',
|
||||
),
|
||||
cancelToken: CancellationToken(),
|
||||
);
|
||||
|
||||
if (state.manualUploadsTotal > 1) {
|
||||
_throttledNotifiy();
|
||||
}
|
||||
|
||||
// Show detailed asset if enabled in settings or if a single asset is uploaded
|
||||
bool showDetailedNotification =
|
||||
ref.read(appSettingsServiceProvider).getSetting<bool>(
|
||||
AppSettingsEnum.backgroundBackupSingleProgress,
|
||||
) ||
|
||||
state.manualUploadsTotal == 1;
|
||||
|
||||
final bool ok = await _backupService.backupAsset(
|
||||
allUploadAssets,
|
||||
state.cancelToken,
|
||||
_onManualAssetUploaded,
|
||||
showDetailedNotification ? _onProgress : (sent, total) {},
|
||||
showDetailedNotification ? _onSetCurrentBackupAsset : (asset) {},
|
||||
_onManualBackupError,
|
||||
);
|
||||
|
||||
// Close detailed notification
|
||||
await _localNotificationService.closeNotification(
|
||||
LocalNotificationService.manualUploadDetailedNotificationID,
|
||||
);
|
||||
|
||||
bool hasErrors = false;
|
||||
if ((state.manualUploadFailures != 0 &&
|
||||
state.manualUploadSuccess == 0) ||
|
||||
(!ok && !state.cancelToken.isCancelled)) {
|
||||
await _localNotificationService.showOrUpdateManualUploadStatus(
|
||||
"backup_manual_title".tr(),
|
||||
"backup_manual_failed".tr(),
|
||||
presentBanner: true,
|
||||
);
|
||||
hasErrors = true;
|
||||
} else if (state.manualUploadSuccess != 0) {
|
||||
await _localNotificationService.showOrUpdateManualUploadStatus(
|
||||
"backup_manual_title".tr(),
|
||||
"backup_manual_success".tr(),
|
||||
presentBanner: true,
|
||||
);
|
||||
}
|
||||
|
||||
_backupProvider.updateBackupProgress(BackUpProgressEnum.idle);
|
||||
await _backupProvider.notifyBackgroundServiceCanRun();
|
||||
return !hasErrors;
|
||||
} else {
|
||||
openAppSettings();
|
||||
debugPrint("[_startUpload] Do not have permission to the gallery");
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint("ERROR _startUpload: ${e.toString()}");
|
||||
}
|
||||
await _localNotificationService.closeNotification(
|
||||
LocalNotificationService.manualUploadDetailedNotificationID,
|
||||
);
|
||||
_backupProvider.updateBackupProgress(BackUpProgressEnum.idle);
|
||||
await _backupProvider.notifyBackgroundServiceCanRun();
|
||||
return false;
|
||||
}
|
||||
|
||||
void cancelBackup() {
|
||||
if (_backupProvider.backupProgress != BackUpProgressEnum.manualInProgress) {
|
||||
_backupProvider.notifyBackgroundServiceCanRun();
|
||||
}
|
||||
state.cancelToken.cancel();
|
||||
_backupProvider.updateBackupProgress(BackUpProgressEnum.idle);
|
||||
}
|
||||
|
||||
Future<bool> uploadAssets(
|
||||
BuildContext context,
|
||||
Iterable<Asset> allManualUploads,
|
||||
) async {
|
||||
// assumes the background service is currently running and
|
||||
// waits until it has stopped to start the backup.
|
||||
final bool hasLock = await _backgroundService.acquireLock();
|
||||
if (!hasLock) {
|
||||
debugPrint("[uploadAssets] could not acquire lock, exiting");
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: "backup_manual_failed".tr(),
|
||||
toastType: ToastType.info,
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
durationInSecond: 3,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool showInProgress = false;
|
||||
|
||||
// check if backup is already in process - then return
|
||||
if (_backupProvider.backupProgress == BackUpProgressEnum.manualInProgress) {
|
||||
debugPrint("[uploadAssets] Manual upload is already running - abort");
|
||||
showInProgress = true;
|
||||
}
|
||||
|
||||
if (_backupProvider.backupProgress == BackUpProgressEnum.inProgress) {
|
||||
debugPrint("[uploadAssets] Auto Backup is already in progress - abort");
|
||||
showInProgress = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_backupProvider.backupProgress == BackUpProgressEnum.inBackground) {
|
||||
debugPrint("[uploadAssets] Background backup is running - abort");
|
||||
showInProgress = true;
|
||||
}
|
||||
|
||||
if (showInProgress) {
|
||||
if (context.mounted) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: "backup_manual_in_progress".tr(),
|
||||
toastType: ToastType.info,
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
durationInSecond: 3,
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return _startUpload(allManualUploads);
|
||||
}
|
||||
}
|
||||
|
|
@ -53,7 +53,8 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||
|
||||
useEffect(
|
||||
() {
|
||||
if (backupState.backupProgress != BackUpProgressEnum.inProgress) {
|
||||
if (backupState.backupProgress != BackUpProgressEnum.inProgress &&
|
||||
backupState.backupProgress != BackUpProgressEnum.manualInProgress) {
|
||||
ref.watch(backupProvider.notifier).getBackupInfo();
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue