mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
feat: manually upload local assets to album
This commit is contained in:
@@ -9,6 +9,7 @@ import 'package:immich_mobile/infrastructure/repositories/remote_album.repositor
|
||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/remote_album.provider.dart';
|
||||
import 'package:immich_mobile/repositories/drift_album_api_repository.dart';
|
||||
import 'package:immich_mobile/services/foreground_upload.service.dart';
|
||||
|
||||
final localAlbumRepository = Provider<DriftLocalAlbumRepository>(
|
||||
(ref) => DriftLocalAlbumRepository(ref.watch(driftProvider)),
|
||||
@@ -33,7 +34,11 @@ final remoteAlbumRepository = Provider<DriftRemoteAlbumRepository>(
|
||||
);
|
||||
|
||||
final remoteAlbumServiceProvider = Provider<RemoteAlbumService>(
|
||||
(ref) => RemoteAlbumService(ref.watch(remoteAlbumRepository), ref.watch(driftAlbumApiRepositoryProvider)),
|
||||
(ref) => RemoteAlbumService(
|
||||
ref.watch(remoteAlbumRepository),
|
||||
ref.watch(driftAlbumApiRepositoryProvider),
|
||||
ref.watch(foregroundUploadServiceProvider),
|
||||
),
|
||||
dependencies: [remoteAlbumRepository],
|
||||
);
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/album/album.model.dart';
|
||||
@@ -6,8 +8,10 @@ import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
import 'package:immich_mobile/domain/services/remote_album.service.dart';
|
||||
import 'package:immich_mobile/models/albums/album_search.model.dart';
|
||||
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
||||
import 'package:immich_mobile/providers/backup/asset_upload_progress.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
import 'package:immich_mobile/services/foreground_upload.service.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
class RemoteAlbumState {
|
||||
@@ -103,6 +107,42 @@ class RemoteAlbumNotifier extends Notifier<RemoteAlbumState> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an album from a heterogeneous asset selection. Already-remote
|
||||
/// assets seed the album immediately; local-only assets are uploaded and
|
||||
/// linked one-by-one as each upload completes. Per-asset progress is
|
||||
/// reported via [assetUploadProgressProvider].
|
||||
Future<RemoteAlbum?> createAlbumWithAssets({
|
||||
required String title,
|
||||
String? description,
|
||||
Iterable<BaseAsset> assets = const [],
|
||||
}) async {
|
||||
try {
|
||||
final currentUser = ref.read(currentUserProvider);
|
||||
if (currentUser == null) {
|
||||
throw Exception('User not logged in');
|
||||
}
|
||||
|
||||
final candidates = RemoteAlbumService.categorizeCandidates(assets);
|
||||
final album = await _runWithProgressBridge(
|
||||
candidates.localAssetsToUpload,
|
||||
(callbacks) => _remoteAlbumService.createAlbumWithAssets(
|
||||
title: title,
|
||||
owner: currentUser,
|
||||
description: description,
|
||||
candidates: candidates,
|
||||
uploadCallbacks: callbacks,
|
||||
),
|
||||
);
|
||||
|
||||
state = state.copyWith(albums: [...state.albums, album]);
|
||||
|
||||
return album;
|
||||
} catch (error, stack) {
|
||||
_logger.severe('Failed to create album with assets', error, stack);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<RemoteAlbum?> updateAlbum(
|
||||
String albumId, {
|
||||
String? name,
|
||||
@@ -157,6 +197,73 @@ class RemoteAlbumNotifier extends Notifier<RemoteAlbumState> {
|
||||
return _remoteAlbumService.addAssets(albumId: albumId, assetIds: assetIds);
|
||||
}
|
||||
|
||||
/// Adds a heterogeneous asset selection to an album. Already-remote assets
|
||||
/// are linked immediately; local-only assets are uploaded and linked one-by-
|
||||
/// one as each upload completes. Per-asset progress is reported via
|
||||
/// [assetUploadProgressProvider].
|
||||
Future<int> addAssetsToAlbum(String albumId, Iterable<BaseAsset> assets) async {
|
||||
final currentUser = ref.read(currentUserProvider);
|
||||
if (currentUser == null) {
|
||||
throw Exception('User not logged in');
|
||||
}
|
||||
|
||||
final candidates = RemoteAlbumService.categorizeCandidates(assets);
|
||||
try {
|
||||
return await _runWithProgressBridge(
|
||||
candidates.localAssetsToUpload,
|
||||
(callbacks) => _remoteAlbumService.addAssetsToAlbum(
|
||||
albumId: albumId,
|
||||
uploader: currentUser,
|
||||
candidates: candidates,
|
||||
uploadCallbacks: callbacks,
|
||||
),
|
||||
);
|
||||
} catch (error, stack) {
|
||||
_logger.severe('Failed to add assets to album $albumId', error, stack);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Bridges [UploadCallbacks] from the service to [assetUploadProgressProvider]
|
||||
/// and the manual upload cancel token, so the UI's existing progress overlays
|
||||
/// pick up the work without each caller wiring it manually.
|
||||
Future<T> _runWithProgressBridge<T>(
|
||||
List<LocalAsset> localAssets,
|
||||
Future<T> Function(UploadCallbacks callbacks) action,
|
||||
) async {
|
||||
if (localAssets.isEmpty) {
|
||||
return action(const UploadCallbacks());
|
||||
}
|
||||
|
||||
final progressNotifier = ref.read(assetUploadProgressProvider.notifier);
|
||||
final cancelToken = Completer<void>();
|
||||
ref.read(manualUploadCancelTokenProvider.notifier).state = cancelToken;
|
||||
|
||||
for (final asset in localAssets) {
|
||||
progressNotifier.setProgress(asset.id, 0.0);
|
||||
}
|
||||
|
||||
try {
|
||||
return await action(
|
||||
UploadCallbacks(
|
||||
onProgress: (localAssetId, _, bytes, totalBytes) {
|
||||
final progress = totalBytes > 0 ? bytes / totalBytes : 0.0;
|
||||
progressNotifier.setProgress(localAssetId, progress);
|
||||
},
|
||||
onSuccess: (localAssetId, _) {
|
||||
progressNotifier.remove(localAssetId);
|
||||
},
|
||||
onError: (localAssetId, _) {
|
||||
progressNotifier.setError(localAssetId);
|
||||
},
|
||||
),
|
||||
);
|
||||
} finally {
|
||||
ref.read(manualUploadCancelTokenProvider.notifier).state = null;
|
||||
Future.delayed(const Duration(seconds: 2), progressNotifier.clear);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> addUsers(String albumId, List<String> userIds) {
|
||||
return _remoteAlbumService.addUsers(albumId: albumId, userIds: userIds);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user