mirror of
https://github.com/immich-app/immich
synced 2025-11-14 17:36:12 +00:00
refactor(server)!: add/remove album assets (#3109)
* refactor: add/remove album assets * chore: open api * feat: remove owned assets from album * refactor: move to bulk id req/res dto * chore: open api * chore: merge main * dev: mobile work * fix: adding asset from web not sync with mobile * remove print statement --------- Co-authored-by: Alex Tran <Alex.Tran@conductix.com>
This commit is contained in:
parent
ba71c83948
commit
b9cda59172
51 changed files with 890 additions and 1282 deletions
|
|
@ -0,0 +1,49 @@
|
|||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
class AddAssetsResponse {
|
||||
List<String> alreadyInAlbum;
|
||||
int successfullyAdded;
|
||||
|
||||
AddAssetsResponse({
|
||||
required this.alreadyInAlbum,
|
||||
required this.successfullyAdded,
|
||||
});
|
||||
|
||||
AddAssetsResponse copyWith({
|
||||
List<String>? alreadyInAlbum,
|
||||
int? successfullyAdded,
|
||||
}) {
|
||||
return AddAssetsResponse(
|
||||
alreadyInAlbum: alreadyInAlbum ?? this.alreadyInAlbum,
|
||||
successfullyAdded: successfullyAdded ?? this.successfullyAdded,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return <String, dynamic>{
|
||||
'alreadyInAlbum': alreadyInAlbum,
|
||||
'successfullyAdded': successfullyAdded,
|
||||
};
|
||||
}
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
'AddAssetsResponse(alreadyInAlbum: $alreadyInAlbum, successfullyAdded: $successfullyAdded)';
|
||||
|
||||
@override
|
||||
bool operator ==(covariant AddAssetsResponse other) {
|
||||
if (identical(this, other)) return true;
|
||||
final listEquals = const DeepCollectionEquality().equals;
|
||||
|
||||
return listEquals(other.alreadyInAlbum, alreadyInAlbum) &&
|
||||
other.successfullyAdded == successfullyAdded;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => alreadyInAlbum.hashCode ^ successfullyAdded.hashCode;
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/album/services/album.service.dart';
|
||||
import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart';
|
||||
import 'package:immich_mobile/shared/models/album.dart';
|
||||
import 'package:immich_mobile/shared/providers/user.provider.dart';
|
||||
|
||||
final albumDetailProvider =
|
||||
StreamProvider.family<Album, int>((ref, albumId) async* {
|
||||
final user = ref.watch(currentUserProvider);
|
||||
if (user == null) return;
|
||||
final AlbumService service = ref.watch(albumServiceProvider);
|
||||
|
||||
await for (final a in service.watchAlbum(albumId)) {
|
||||
if (a == null) {
|
||||
throw Exception("Album with ID=$albumId does not exist anymore!");
|
||||
}
|
||||
await for (final _ in a.watchRenderList(GroupAssetsBy.none)) {
|
||||
yield a;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -3,12 +3,10 @@ import 'dart:async';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/album/services/album.service.dart';
|
||||
import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart';
|
||||
import 'package:immich_mobile/shared/models/album.dart';
|
||||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/models/user.dart';
|
||||
import 'package:immich_mobile/shared/providers/db.provider.dart';
|
||||
import 'package:immich_mobile/shared/providers/user.provider.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
class SharedAlbumNotifier extends StateNotifier<List<Album>> {
|
||||
|
|
@ -72,19 +70,3 @@ final sharedAlbumProvider =
|
|||
ref.watch(dbProvider),
|
||||
);
|
||||
});
|
||||
|
||||
final sharedAlbumDetailProvider =
|
||||
StreamProvider.family<Album, int>((ref, albumId) async* {
|
||||
final user = ref.watch(currentUserProvider);
|
||||
if (user == null) return;
|
||||
final AlbumService sharedAlbumService = ref.watch(albumServiceProvider);
|
||||
|
||||
await for (final a in sharedAlbumService.watchAlbum(albumId)) {
|
||||
if (a == null) {
|
||||
throw Exception("Album with ID=$albumId does not exist anymore!");
|
||||
}
|
||||
await for (final _ in a.watchRenderList(GroupAssetsBy.none)) {
|
||||
yield a;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import 'dart:io';
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/album/models/add_asset_response.model.dart';
|
||||
import 'package:immich_mobile/modules/backup/models/backup_album.model.dart';
|
||||
import 'package:immich_mobile/modules/backup/services/backup.service.dart';
|
||||
import 'package:immich_mobile/shared/models/album.dart';
|
||||
|
|
@ -219,24 +220,43 @@ class AlbumService {
|
|||
yield* _db.albums.watchObject(albumId);
|
||||
}
|
||||
|
||||
Future<AddAssetsResponseDto?> addAdditionalAssetToAlbum(
|
||||
Future<AddAssetsResponse?> addAdditionalAssetToAlbum(
|
||||
Iterable<Asset> assets,
|
||||
Album album,
|
||||
) async {
|
||||
try {
|
||||
var result = await _apiService.albumApi.addAssetsToAlbum(
|
||||
var response = await _apiService.albumApi.addAssetsToAlbum(
|
||||
album.remoteId!,
|
||||
AddAssetsDto(assetIds: assets.map((asset) => asset.remoteId!).toList()),
|
||||
BulkIdsDto(ids: assets.map((asset) => asset.remoteId!).toList()),
|
||||
);
|
||||
if (result != null && result.successfullyAdded > 0) {
|
||||
album.assets.addAll(assets);
|
||||
|
||||
if (response != null) {
|
||||
List<Asset> successAssets = [];
|
||||
List<String> duplicatedAssets = [];
|
||||
|
||||
for (final result in response) {
|
||||
if (result.success) {
|
||||
successAssets
|
||||
.add(assets.firstWhere((asset) => asset.remoteId == result.id));
|
||||
} else if (!result.success &&
|
||||
result.error == BulkIdResponseDtoErrorEnum.duplicate) {
|
||||
duplicatedAssets.add(result.id);
|
||||
}
|
||||
}
|
||||
|
||||
album.assets.addAll(successAssets);
|
||||
await _db.writeTxn(() => album.assets.save());
|
||||
|
||||
return AddAssetsResponse(
|
||||
alreadyInAlbum: duplicatedAssets,
|
||||
successfullyAdded: successAssets.length,
|
||||
);
|
||||
}
|
||||
return result;
|
||||
} catch (e) {
|
||||
debugPrint("Error addAdditionalAssetToAlbum ${e.toString()}");
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<bool> addAdditionalUserToAlbum(
|
||||
|
|
@ -314,8 +334,8 @@ class AlbumService {
|
|||
try {
|
||||
await _apiService.albumApi.removeAssetFromAlbum(
|
||||
album.remoteId!,
|
||||
RemoveAssetsDto(
|
||||
assetIds: assets.map((e) => e.remoteId!).toList(growable: false),
|
||||
BulkIdsDto(
|
||||
ids: assets.map((asset) => asset.remoteId!).toList(),
|
||||
),
|
||||
);
|
||||
album.assets.removeAll(assets);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/album/providers/album.provider.dart';
|
||||
import 'package:immich_mobile/modules/album/providers/album_detail.provider.dart';
|
||||
import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart';
|
||||
import 'package:immich_mobile/modules/album/services/album.service.dart';
|
||||
import 'package:immich_mobile/modules/album/ui/add_to_album_sliverlist.dart';
|
||||
|
|
@ -63,9 +64,7 @@ class AddToAlbumBottomSheet extends HookConsumerWidget {
|
|||
}
|
||||
}
|
||||
|
||||
ref.read(albumProvider.notifier).getAllAlbums();
|
||||
ref.read(sharedAlbumProvider.notifier).getAllSharedAlbums();
|
||||
|
||||
ref.invalidate(albumDetailProvider(album.id));
|
||||
Navigator.pop(context);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/album/providers/album.provider.dart';
|
||||
import 'package:immich_mobile/modules/album/providers/album_detail.provider.dart';
|
||||
import 'package:immich_mobile/modules/album/providers/album_viewer.provider.dart';
|
||||
import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
|
|
@ -99,7 +100,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
|
|||
Navigator.pop(context);
|
||||
selectionDisabled();
|
||||
ref.watch(albumProvider.notifier).getAllAlbums();
|
||||
ref.invalidate(sharedAlbumDetailProvider(album.id));
|
||||
ref.invalidate(albumDetailProvider(album.id));
|
||||
} else {
|
||||
Navigator.pop(context);
|
||||
ImmichToast.show(
|
||||
|
|
|
|||
|
|
@ -6,13 +6,12 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/album/models/asset_selection_page_result.model.dart';
|
||||
import 'package:immich_mobile/modules/album/providers/album.provider.dart';
|
||||
import 'package:immich_mobile/modules/album/providers/album_detail.provider.dart';
|
||||
import 'package:immich_mobile/modules/album/services/album.service.dart';
|
||||
import 'package:immich_mobile/modules/album/ui/album_action_outlined_button.dart';
|
||||
import 'package:immich_mobile/modules/album/ui/album_viewer_editable_title.dart';
|
||||
import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart';
|
||||
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
||||
import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart';
|
||||
import 'package:immich_mobile/modules/album/ui/album_viewer_appbar.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/shared/models/album.dart';
|
||||
|
|
@ -28,11 +27,20 @@ class AlbumViewerPage extends HookConsumerWidget {
|
|||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
FocusNode titleFocusNode = useFocusNode();
|
||||
final album = ref.watch(sharedAlbumDetailProvider(albumId));
|
||||
final album = ref.watch(albumDetailProvider(albumId));
|
||||
final userId = ref.watch(authenticationProvider).userId;
|
||||
final selection = useState<Set<Asset>>({});
|
||||
final multiSelectEnabled = useState(false);
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
// Fetch album updates, e.g., cover image
|
||||
ref.invalidate(albumDetailProvider(albumId));
|
||||
return null;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
Future<bool> onWillPop() async {
|
||||
if (multiSelectEnabled.value) {
|
||||
selection.value = {};
|
||||
|
|
@ -77,8 +85,7 @@ class AlbumViewerPage extends HookConsumerWidget {
|
|||
|
||||
if (addAssetsResult != null &&
|
||||
addAssetsResult.successfullyAdded > 0) {
|
||||
ref.watch(albumProvider.notifier).getAllAlbums();
|
||||
ref.invalidate(sharedAlbumDetailProvider(albumId));
|
||||
ref.invalidate(albumDetailProvider(albumId));
|
||||
}
|
||||
|
||||
ImmichLoadingOverlayController.appLoader.hide();
|
||||
|
|
@ -100,7 +107,7 @@ class AlbumViewerPage extends HookConsumerWidget {
|
|||
.addAdditionalUserToAlbum(sharedUserIds, album);
|
||||
|
||||
if (isSuccess) {
|
||||
ref.invalidate(sharedAlbumDetailProvider(album.id));
|
||||
ref.invalidate(albumDetailProvider(album.id));
|
||||
}
|
||||
|
||||
ImmichLoadingOverlayController.appLoader.hide();
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/album/providers/album.provider.dart';
|
||||
import 'package:immich_mobile/modules/album/providers/album_detail.provider.dart';
|
||||
import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart';
|
||||
import 'package:immich_mobile/modules/album/services/album.service.dart';
|
||||
import 'package:immich_mobile/modules/home/providers/multiselect.provider.dart';
|
||||
|
|
@ -208,6 +209,9 @@ class HomePage extends HookConsumerWidget {
|
|||
),
|
||||
toastType: ToastType.success,
|
||||
);
|
||||
|
||||
ref.watch(albumProvider.notifier).getAllAlbums();
|
||||
ref.invalidate(albumDetailProvider(album.id));
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue