mirror of
https://github.com/immich-app/immich.git
synced 2025-07-07 10:14:08 -04:00
wip
This commit is contained in:
parent
09ea99f058
commit
1b752717bf
@ -15,3 +15,8 @@ const int kBatchHashSizeLimit = 1024 * 1024 * 1024; // 1GB
|
||||
|
||||
// Secure storage keys
|
||||
const String kSecuredPinCode = "secured_pin_code";
|
||||
|
||||
// background_downloader task groups
|
||||
const kManualUploadGroup = 'manual_upload_group';
|
||||
const kBackupGroup = 'backup_group';
|
||||
const kBackupLivePhotoGroup = 'backup_live_photo_group';
|
||||
|
@ -5,7 +5,6 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/domain/models/local_album.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
import "package:immich_mobile/utils/database.utils.dart";
|
||||
|
||||
final backupRepositoryProvider = Provider<IBackupRepository>(
|
||||
@ -15,10 +14,7 @@ final backupRepositoryProvider = Provider<IBackupRepository>(
|
||||
class DriftBackupRepository extends DriftDatabaseRepository
|
||||
implements IBackupRepository {
|
||||
final Drift _db;
|
||||
final Platform _platform;
|
||||
const DriftBackupRepository(this._db, {Platform? platform})
|
||||
: _platform = platform ?? const LocalPlatform(),
|
||||
super(_db);
|
||||
const DriftBackupRepository(this._db) : super(_db);
|
||||
|
||||
@override
|
||||
Future<List<LocalAsset>> getAssets(String albumId) {
|
||||
|
@ -6,9 +6,9 @@ abstract interface class IUploadRepository {
|
||||
|
||||
void enqueueAll(List<UploadTask> tasks);
|
||||
Future<bool> cancel(String id);
|
||||
Future<bool> cancelAll();
|
||||
Future<void> pauseAll();
|
||||
Future<void> deleteAllTrackingRecords();
|
||||
Future<bool> cancelAll(String group);
|
||||
Future<void> pauseAll(String group);
|
||||
Future<void> deleteAllTrackingRecords(String group);
|
||||
Future<void> deleteRecordsWithIds(List<String> id);
|
||||
Future<List<TaskRecord>> getRecords([TaskStatus? status]);
|
||||
}
|
||||
|
@ -1,6 +1,3 @@
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -134,7 +134,7 @@ class ShareIntentUploadStateNotifier
|
||||
}
|
||||
|
||||
Future<void> upload(File file) {
|
||||
return _uploadService.buildUploadTask(file);
|
||||
return _uploadService.buildUploadTask(file, group: kManualUploadGroup);
|
||||
}
|
||||
|
||||
Future<bool> cancelUpload(String id) {
|
||||
|
@ -1,12 +1,10 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:background_downloader/background_downloader.dart';
|
||||
import 'package:cancellation_token_http/http.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/constants.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/entities/album.entity.dart';
|
||||
import 'package:immich_mobile/entities/backup_album.entity.dart';
|
||||
@ -22,7 +20,6 @@ import 'package:immich_mobile/models/backup/current_upload_asset.model.dart';
|
||||
import 'package:immich_mobile/models/backup/error_upload_asset.model.dart';
|
||||
import 'package:immich_mobile/models/backup/success_upload_asset.model.dart';
|
||||
import 'package:immich_mobile/models/server_info/server_disk_info.model.dart';
|
||||
import 'package:immich_mobile/models/upload/share_intent_attachment.model.dart';
|
||||
import 'package:immich_mobile/providers/app_life_cycle.provider.dart';
|
||||
import 'package:immich_mobile/providers/auth.provider.dart';
|
||||
import 'package:immich_mobile/providers/backup/error_backup_list.provider.dart';
|
||||
@ -33,7 +30,6 @@ import 'package:immich_mobile/services/background.service.dart';
|
||||
import 'package:immich_mobile/services/backup.service.dart';
|
||||
import 'package:immich_mobile/services/backup_album.service.dart';
|
||||
import 'package:immich_mobile/services/server_info.service.dart';
|
||||
import 'package:immich_mobile/services/upload.service.dart';
|
||||
import 'package:immich_mobile/utils/backup_progress.dart';
|
||||
import 'package:immich_mobile/utils/diff.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
@ -51,7 +47,6 @@ final backupProvider =
|
||||
ref.watch(albumMediaRepositoryProvider),
|
||||
ref.watch(fileMediaRepositoryProvider),
|
||||
ref.watch(backupAlbumServiceProvider),
|
||||
ref.watch(uploadServiceProvider),
|
||||
ref,
|
||||
);
|
||||
});
|
||||
@ -66,7 +61,6 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
this._albumMediaRepository,
|
||||
this._fileMediaRepository,
|
||||
this._backupAlbumService,
|
||||
this._uploadService,
|
||||
this.ref,
|
||||
) : super(
|
||||
BackUpState(
|
||||
@ -106,10 +100,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
),
|
||||
iCloudDownloadProgress: 0.0,
|
||||
),
|
||||
) {
|
||||
_uploadService.onUploadStatus = _uploadStatusCallback;
|
||||
_uploadService.onTaskProgress = _taskProgressCallback;
|
||||
}
|
||||
);
|
||||
|
||||
final log = Logger('BackupNotifier');
|
||||
final BackupService _backupService;
|
||||
@ -120,7 +111,6 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
final IAlbumMediaRepository _albumMediaRepository;
|
||||
final IFileMediaRepository _fileMediaRepository;
|
||||
final BackupAlbumService _backupAlbumService;
|
||||
final UploadService _uploadService;
|
||||
final Ref ref;
|
||||
|
||||
///
|
||||
@ -498,7 +488,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
Future<void> startBackupProcess() async {
|
||||
debugPrint("Start backup process");
|
||||
assert(state.backupProgress == BackUpProgressEnum.idle);
|
||||
// state = state.copyWith(backupProgress: BackUpProgressEnum.inProgress);
|
||||
state = state.copyWith(backupProgress: BackUpProgressEnum.inProgress);
|
||||
|
||||
await getBackupInfo();
|
||||
|
||||
@ -532,89 +522,21 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
state = state.copyWith(iCloudDownloadProgress: progress);
|
||||
});
|
||||
|
||||
// await _backupService.backupAsset(
|
||||
// assetsWillBeBackup,
|
||||
// state.cancelToken,
|
||||
// pmProgressHandler: pmProgressHandler,
|
||||
// onSuccess: _onAssetUploaded,
|
||||
// onProgress: _onUploadProgress,
|
||||
// onCurrentAsset: _onSetCurrentBackupAsset,
|
||||
// onError: _onBackupError,
|
||||
// );
|
||||
|
||||
await _backupService.uploadAssets(assetsWillBeBackup);
|
||||
await _backupService.backupAsset(
|
||||
assetsWillBeBackup,
|
||||
state.cancelToken,
|
||||
pmProgressHandler: pmProgressHandler,
|
||||
onSuccess: _onAssetUploaded,
|
||||
onProgress: _onUploadProgress,
|
||||
onCurrentAsset: _onSetCurrentBackupAsset,
|
||||
onError: _onBackupError,
|
||||
);
|
||||
await notifyBackgroundServiceCanRun();
|
||||
} else {
|
||||
openAppSettings();
|
||||
}
|
||||
}
|
||||
|
||||
void _updateUploadStatus(TaskStatusUpdate task, TaskStatus status) async {
|
||||
if (status == TaskStatus.canceled) {
|
||||
return;
|
||||
}
|
||||
|
||||
final taskId = task.task.taskId;
|
||||
final uploadStatus = switch (task.status) {
|
||||
TaskStatus.complete => UploadStatus.complete,
|
||||
TaskStatus.failed => UploadStatus.failed,
|
||||
TaskStatus.canceled => UploadStatus.canceled,
|
||||
TaskStatus.enqueued => UploadStatus.enqueued,
|
||||
TaskStatus.running => UploadStatus.running,
|
||||
TaskStatus.paused => UploadStatus.paused,
|
||||
TaskStatus.notFound => UploadStatus.notFound,
|
||||
TaskStatus.waitingToRetry => UploadStatus.waitingtoRetry
|
||||
};
|
||||
|
||||
// state = [
|
||||
// for (final attachment in state)
|
||||
// if (attachment.id == taskId.toInt())
|
||||
// attachment.copyWith(status: uploadStatus)
|
||||
// else
|
||||
// attachment,
|
||||
// ];
|
||||
}
|
||||
|
||||
void _uploadStatusCallback(TaskStatusUpdate update) {
|
||||
_updateUploadStatus(update, update.status);
|
||||
|
||||
switch (update.status) {
|
||||
case TaskStatus.complete:
|
||||
// if (update.responseStatusCode == 200) {
|
||||
// if (kDebugMode) {
|
||||
// debugPrint("[COMPLETE] ${update.task.taskId} - DUPLICATE");
|
||||
// }
|
||||
// } else {
|
||||
// if (kDebugMode) {
|
||||
// debugPrint("[COMPLETE] ${update.task.taskId}");
|
||||
// }
|
||||
// }
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void _taskProgressCallback(TaskProgressUpdate update) {
|
||||
// Ignore if the task is canceled or completed
|
||||
if (update.progress == downloadFailed ||
|
||||
update.progress == downloadCompleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
// print("[_taskProgressCallback] $update");
|
||||
|
||||
final taskId = update.task.taskId;
|
||||
// state = [
|
||||
// for (final attachment in state)
|
||||
// if (attachment.id == taskId.toInt())
|
||||
// attachment.copyWith(uploadProgress: update.progress)
|
||||
// else
|
||||
// attachment,
|
||||
// ];
|
||||
}
|
||||
|
||||
void setAvailableAlbums(availableAlbums) {
|
||||
state = state.copyWith(
|
||||
availableAlbums: availableAlbums,
|
||||
|
@ -6,8 +6,6 @@ import 'package:background_downloader/background_downloader.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
import 'package:immich_mobile/domain/utils/background_sync.dart';
|
||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||
import 'package:immich_mobile/services/exp_backup.service.dart';
|
||||
import 'package:immich_mobile/services/upload.service.dart';
|
||||
|
||||
@ -158,7 +156,6 @@ final expBackupProvider =
|
||||
return ExpBackupNotifier(
|
||||
ref.watch(expBackupServiceProvider),
|
||||
ref.watch(uploadServiceProvider),
|
||||
ref.watch(backgroundSyncProvider),
|
||||
);
|
||||
});
|
||||
|
||||
@ -166,7 +163,6 @@ class ExpBackupNotifier extends StateNotifier<ExpBackupState> {
|
||||
ExpBackupNotifier(
|
||||
this._backupService,
|
||||
this._uploadService,
|
||||
this._backgroundSyncManager,
|
||||
) : super(
|
||||
ExpBackupState(
|
||||
totalCount: 0,
|
||||
@ -183,7 +179,6 @@ class ExpBackupNotifier extends StateNotifier<ExpBackupState> {
|
||||
|
||||
final ExpBackupService _backupService;
|
||||
final UploadService _uploadService;
|
||||
final BackgroundSyncManager _backgroundSyncManager;
|
||||
StreamSubscription<TaskStatusUpdate>? _statusSubscription;
|
||||
StreamSubscription<TaskProgressUpdate>? _progressSubscription;
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'package:background_downloader/background_downloader.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/constants.dart';
|
||||
import 'package:immich_mobile/interfaces/upload.interface.dart';
|
||||
import 'package:immich_mobile/utils/upload.dart';
|
||||
|
||||
final uploadRepositoryProvider = Provider((ref) => UploadRepository());
|
||||
|
||||
@ -19,12 +19,12 @@ class UploadRepository implements IUploadRepository {
|
||||
taskQueue.maxConcurrent = 5;
|
||||
FileDownloader().addTaskQueue(taskQueue);
|
||||
FileDownloader().registerCallbacks(
|
||||
group: kUploadGroup,
|
||||
group: kBackupGroup,
|
||||
taskStatusCallback: (update) => onUploadStatus?.call(update),
|
||||
taskProgressCallback: (update) => onTaskProgress?.call(update),
|
||||
);
|
||||
FileDownloader().registerCallbacks(
|
||||
group: kUploadLivePhotoGroup,
|
||||
group: kBackupLivePhotoGroup,
|
||||
taskStatusCallback: (update) => onUploadStatus?.call(update),
|
||||
taskProgressCallback: (update) => onTaskProgress?.call(update),
|
||||
);
|
||||
@ -36,8 +36,8 @@ class UploadRepository implements IUploadRepository {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteAllTrackingRecords() {
|
||||
return FileDownloader().database.deleteAllRecords(group: kUploadGroup);
|
||||
Future<void> deleteAllTrackingRecords(String group) {
|
||||
return FileDownloader().database.deleteAllRecords(group: group);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -46,14 +46,14 @@ class UploadRepository implements IUploadRepository {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> cancelAll() {
|
||||
taskQueue.removeTasksWithGroup(kUploadGroup);
|
||||
return FileDownloader().cancelAll(group: kUploadGroup);
|
||||
Future<bool> cancelAll(String group) {
|
||||
taskQueue.removeTasksWithGroup(group);
|
||||
return FileDownloader().cancelAll(group: group);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> pauseAll() {
|
||||
return FileDownloader().pauseAll(group: kUploadGroup);
|
||||
Future<void> pauseAll(String group) {
|
||||
return FileDownloader().pauseAll(group: group);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -64,7 +64,7 @@ class UploadRepository implements IUploadRepository {
|
||||
@override
|
||||
Future<List<TaskRecord>> getRecords([TaskStatus? status]) {
|
||||
if (status == null) {
|
||||
return FileDownloader().database.allRecords(group: kUploadGroup);
|
||||
return FileDownloader().database.allRecords(group: kBackupGroup);
|
||||
}
|
||||
|
||||
return FileDownloader().database.allRecordsWithStatus(status);
|
||||
|
@ -2,7 +2,6 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:background_downloader/background_downloader.dart';
|
||||
import 'package:cancellation_token_http/http.dart' as http;
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -29,7 +28,6 @@ import 'package:immich_mobile/repositories/file_media.repository.dart';
|
||||
import 'package:immich_mobile/services/album.service.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||
import 'package:immich_mobile/services/upload.service.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
@ -45,7 +43,6 @@ final backupServiceProvider = Provider(
|
||||
ref.watch(fileMediaRepositoryProvider),
|
||||
ref.watch(assetRepositoryProvider),
|
||||
ref.watch(assetMediaRepositoryProvider),
|
||||
ref.watch(uploadServiceProvider),
|
||||
),
|
||||
);
|
||||
|
||||
@ -59,7 +56,6 @@ class BackupService {
|
||||
final IFileMediaRepository _fileMediaRepository;
|
||||
final IAssetRepository _assetRepository;
|
||||
final IAssetMediaRepository _assetMediaRepository;
|
||||
final UploadService _uploadService;
|
||||
|
||||
BackupService(
|
||||
this._apiService,
|
||||
@ -69,7 +65,6 @@ class BackupService {
|
||||
this._fileMediaRepository,
|
||||
this._assetRepository,
|
||||
this._assetMediaRepository,
|
||||
this._uploadService,
|
||||
);
|
||||
|
||||
Future<List<String>?> getDeviceBackupAsset() async {
|
||||
@ -254,52 +249,6 @@ class BackupService {
|
||||
);
|
||||
}
|
||||
|
||||
uploadAssets(
|
||||
Iterable<BackupCandidate> assets,
|
||||
) async {
|
||||
final hasPermission = await _checkPermissions();
|
||||
if (!hasPermission) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<BackupCandidate> candidates = assets.toList();
|
||||
List<UploadTask> uploadTasks = [];
|
||||
for (final candidate in candidates) {
|
||||
final Asset asset = candidate.asset;
|
||||
File? file;
|
||||
File? livePhotoFile;
|
||||
file = await asset.local!.originFile.timeout(const Duration(seconds: 5));
|
||||
|
||||
if (asset.local!.isLivePhoto) {
|
||||
livePhotoFile = await asset.local!.originFileWithSubtype
|
||||
.timeout(const Duration(seconds: 5));
|
||||
}
|
||||
|
||||
if (file != null) {
|
||||
String? originalFileName =
|
||||
await _assetMediaRepository.getOriginalFilename(asset.localId!) ??
|
||||
asset.fileName;
|
||||
|
||||
final task = await _uploadService.buildUploadTask(
|
||||
file,
|
||||
originalFileName: originalFileName,
|
||||
deviceAssetId: asset.localId,
|
||||
);
|
||||
|
||||
uploadTasks.add(task);
|
||||
}
|
||||
}
|
||||
|
||||
if (uploadTasks.isEmpty) {
|
||||
debugPrint("No assets to upload");
|
||||
return false;
|
||||
}
|
||||
|
||||
print("Uploading ${uploadTasks.length} assets");
|
||||
|
||||
_uploadService.enqueueTasks(uploadTasks);
|
||||
}
|
||||
|
||||
Future<bool> backupAsset(
|
||||
Iterable<BackupCandidate> assets,
|
||||
http.CancellationToken cancelToken, {
|
||||
|
@ -5,9 +5,9 @@ import 'dart:io';
|
||||
|
||||
import 'package:background_downloader/background_downloader.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/constants/constants.dart';
|
||||
import 'package:immich_mobile/domain/interfaces/local_asset.interface.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||
import 'package:immich_mobile/utils/upload.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
@ -172,6 +172,7 @@ class ExpBackupService {
|
||||
originalFileName: originalFileName,
|
||||
deviceAssetId: asset.id,
|
||||
metadata: metadata,
|
||||
group: kBackupGroup,
|
||||
);
|
||||
}
|
||||
|
||||
@ -198,13 +199,13 @@ class ExpBackupService {
|
||||
originalFileName: asset.name,
|
||||
deviceAssetId: asset.id,
|
||||
fields: fields,
|
||||
group: kUploadLivePhotoGroup,
|
||||
group: kBackupLivePhotoGroup,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> cancel() async {
|
||||
shouldCancel = true;
|
||||
await _uploadService.cancel();
|
||||
await _uploadService.cancelAllForGroup(kBackupGroup);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,12 +3,12 @@ import 'dart:io';
|
||||
|
||||
import 'package:background_downloader/background_downloader.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/constants.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/interfaces/upload.interface.dart';
|
||||
import 'package:immich_mobile/repositories/upload.repository.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:immich_mobile/utils/upload.dart';
|
||||
import 'package:path/path.dart';
|
||||
|
||||
final uploadServiceProvider = Provider(
|
||||
@ -61,13 +61,13 @@ class UploadService {
|
||||
return FileDownloader().cancelTaskWithId(id);
|
||||
}
|
||||
|
||||
Future<void> cancel() async {
|
||||
await _uploadRepository.cancelAll();
|
||||
await _uploadRepository.deleteAllTrackingRecords();
|
||||
Future<void> cancelAllForGroup(String group) async {
|
||||
await _uploadRepository.cancelAll(group);
|
||||
await _uploadRepository.deleteAllTrackingRecords(group);
|
||||
}
|
||||
|
||||
Future<void> pause() {
|
||||
return _uploadRepository.pauseAll();
|
||||
Future<void> pauseAll() {
|
||||
return _uploadRepository.pauseAll(kBackupGroup);
|
||||
}
|
||||
|
||||
Future<List<TaskRecord>> getRecords() async {
|
||||
@ -81,11 +81,11 @@ class UploadService {
|
||||
|
||||
Future<UploadTask> buildUploadTask(
|
||||
File file, {
|
||||
required String group,
|
||||
Map<String, String>? fields,
|
||||
String? originalFileName,
|
||||
String? deviceAssetId,
|
||||
String? metadata,
|
||||
String group = kUploadGroup,
|
||||
}) async {
|
||||
return _buildTask(
|
||||
deviceAssetId ?? hash(file.path).toString(),
|
||||
@ -100,10 +100,10 @@ class UploadService {
|
||||
Future<UploadTask> _buildTask(
|
||||
String id,
|
||||
File file, {
|
||||
required String group,
|
||||
Map<String, String>? fields,
|
||||
String? originalFileName,
|
||||
String? metadata,
|
||||
String group = kUploadGroup,
|
||||
}) async {
|
||||
final serverEndpoint = Store.get(StoreKey.serverEndpoint);
|
||||
final url = Uri.parse('$serverEndpoint/assets').toString();
|
||||
|
@ -1,2 +0,0 @@
|
||||
const kUploadGroup = 'upload_group';
|
||||
const kUploadLivePhotoGroup = 'upload_live_photo_group';
|
@ -1,15 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/providers/backup/exp_backup.provider.dart';
|
||||
import 'package:immich_mobile/widgets/backup/asset_info_table.dart';
|
||||
import 'package:immich_mobile/widgets/backup/error_chip.dart';
|
||||
import 'package:immich_mobile/widgets/backup/icloud_download_progress_bar.dart';
|
||||
import 'package:immich_mobile/widgets/backup/upload_progress_bar.dart';
|
||||
import 'package:immich_mobile/widgets/backup/upload_stats.dart';
|
||||
|
||||
class CurrentUploadingAssetInfoBox extends ConsumerWidget {
|
||||
const CurrentUploadingAssetInfoBox({super.key});
|
||||
|
Loading…
x
Reference in New Issue
Block a user