refactor(server): stacks (#11453)

* refactor: stacks

* mobile: get it built

* chore: feedback

* fix: sync and duplicates

* mobile: remove old stack reference

* chore: add primary asset id

* revert change to asset entity

* mobile: refactor mobile api

* mobile: sync stack info after creating stack

* mobile: update timeline after deleting stack

* server: update asset updatedAt when stack is deleted

* mobile: simplify action

* mobile: rename to match dto property

* fix: web test

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
Jason Rasmussen 2024-08-19 13:37:15 -04:00 committed by GitHub
parent ca52cbace1
commit 8338657eaa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
63 changed files with 2321 additions and 1152 deletions

View file

@ -11,7 +11,7 @@ import 'package:immich_mobile/providers/album/shared_album.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/asset_stack.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/image_viewer_page_state.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart';
import 'package:immich_mobile/services/asset_stack.service.dart';
import 'package:immich_mobile/services/stack.service.dart';
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
import 'package:immich_mobile/widgets/asset_viewer/video_controls.dart';
import 'package:immich_mobile/widgets/asset_grid/delete_dialog.dart';
@ -49,11 +49,10 @@ class BottomGalleryBar extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final isOwner = asset.ownerId == ref.watch(currentUserProvider)?.isarId;
final stack = showStack && asset.stackChildrenCount > 0
final stackItems = showStack && asset.stackCount > 0
? ref.watch(assetStackStateProvider(asset))
: <Asset>[];
final stackElements = showStack ? [asset, ...stack] : <Asset>[];
bool isParent = stackIndex == -1 || stackIndex == 0;
bool isStackPrimaryAsset = asset.stackPrimaryAssetId == null;
final navStack = AutoRouter.of(context).stackData;
final isTrashEnabled =
ref.watch(serverInfoProvider.select((v) => v.serverFeatures.trash));
@ -76,7 +75,7 @@ class BottomGalleryBar extends ConsumerWidget {
{asset},
force: force,
);
if (isDeleted && isParent) {
if (isDeleted && isStackPrimaryAsset) {
// Workaround for asset remaining in the gallery
renderList.deleteAsset(asset);
@ -98,7 +97,7 @@ class BottomGalleryBar extends ConsumerWidget {
final isDeleted = await onDelete(false);
if (isDeleted) {
// Can only trash assets stored in server. Local assets are always permanently removed for now
if (context.mounted && asset.isRemote && isParent) {
if (context.mounted && asset.isRemote && isStackPrimaryAsset) {
ImmichToast.show(
durationInSecond: 1,
context: context,
@ -127,6 +126,16 @@ class BottomGalleryBar extends ConsumerWidget {
);
}
unStack() async {
if (asset.stackId == null) {
return;
}
await ref
.read(stackServiceProvider)
.deleteStack(asset.stackId!, [asset, ...stackItems]);
}
void showStackActionItems() {
showModalBottomSheet<void>(
context: context,
@ -138,74 +147,13 @@ class BottomGalleryBar extends ConsumerWidget {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (!isParent)
ListTile(
leading: const Icon(
Icons.bookmark_border_outlined,
size: 24,
),
onTap: () async {
await ref
.read(assetStackServiceProvider)
.updateStackParent(
asset,
stackElements.elementAt(stackIndex),
);
ctx.pop();
context.maybePop();
},
title: const Text(
"viewer_stack_use_as_main_asset",
style: TextStyle(fontWeight: FontWeight.bold),
).tr(),
),
ListTile(
leading: const Icon(
Icons.copy_all_outlined,
size: 24,
),
onTap: () async {
if (isParent) {
await ref
.read(assetStackServiceProvider)
.updateStackParent(
asset,
stackElements
.elementAt(1), // Next asset as parent
);
// Remove itself from stack
await ref.read(assetStackServiceProvider).updateStack(
stackElements.elementAt(1),
childrenToRemove: [asset],
);
ctx.pop();
context.maybePop();
} else {
await ref.read(assetStackServiceProvider).updateStack(
asset,
childrenToRemove: [
stackElements.elementAt(stackIndex),
],
);
removeAssetFromStack();
ctx.pop();
}
},
title: const Text(
"viewer_remove_from_stack",
style: TextStyle(fontWeight: FontWeight.bold),
).tr(),
),
ListTile(
leading: const Icon(
Icons.filter_none_outlined,
size: 18,
),
onTap: () async {
await ref.read(assetStackServiceProvider).updateStack(
asset,
childrenToRemove: stack,
);
await unStack();
ctx.pop();
context.maybePop();
},
@ -255,7 +203,7 @@ class BottomGalleryBar extends ConsumerWidget {
handleArchive() {
ref.read(assetProvider.notifier).toggleArchive([asset]);
if (isParent) {
if (isStackPrimaryAsset) {
context.maybePop();
return;
}
@ -346,7 +294,7 @@ class BottomGalleryBar extends ConsumerWidget {
tooltip: 'control_bottom_app_bar_archive'.tr(),
): (_) => handleArchive(),
},
if (isOwner && stack.isNotEmpty)
if (isOwner && asset.stackCount > 0)
{
BottomNavigationBarItem(
icon: const Icon(Icons.burst_mode_outlined),