1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-01-01 08:31:59 +00:00

fix stack handling

This commit is contained in:
mertalev 2024-11-16 18:05:22 -05:00
parent 3b9a3d4037
commit d1c7ed5464
No known key found for this signature in database
GPG key ID: CA85EF6600C9E8AD
4 changed files with 123 additions and 100 deletions

View file

@ -0,0 +1,82 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/providers/asset_viewer/asset_stack.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart';
import 'package:immich_mobile/providers/image/immich_remote_image_provider.dart';
class GalleryStackedChildren extends HookConsumerWidget {
final ValueNotifier<int> stackIndex;
const GalleryStackedChildren(this.stackIndex, {super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final asset = ref.watch(currentAssetProvider);
if (asset == null) {
return const SizedBox();
}
final stackId = asset.stackId;
if (stackId == null) {
return const SizedBox();
}
final stackElements = ref.watch(assetStackStateProvider(stackId));
return SizedBox(
height: 80,
child: ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: stackElements.length,
padding: const EdgeInsets.only(
left: 5,
right: 5,
bottom: 30,
),
itemBuilder: (context, index) {
final currentAsset = stackElements.elementAt(index);
final assetId = currentAsset.remoteId;
if (assetId == null) {
return const SizedBox();
}
return Padding(
key: ValueKey(assetId),
padding: const EdgeInsets.only(right: 5),
child: GestureDetector(
onTap: () {
stackIndex.value = index;
ref.read(currentAssetProvider.notifier).set(currentAsset);
},
child: Container(
width: 60,
height: 60,
decoration: index == stackIndex.value
? const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(6)),
border: Border.fromBorderSide(
BorderSide(color: Colors.white, width: 2),
),
)
: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(6)),
border: null,
),
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(4)),
child: Image(
fit: BoxFit.cover,
image: ImmichRemoteImageProvider(assetId: assetId),
),
),
),
),
);
},
),
);
}
}

View file

@ -8,19 +8,18 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/constants.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/scroll_extensions.dart';
import 'package:immich_mobile/pages/common/download_panel.dart';
import 'package:immich_mobile/pages/common/native_video_viewer.page.dart';
import 'package:immich_mobile/pages/common/gallery_stacked_children.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/asset_stack.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/video_player_value_provider.dart';
import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
import 'package:immich_mobile/providers/image/immich_remote_image_provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
import 'package:immich_mobile/widgets/asset_viewer/advanced_bottom_sheet.dart';
@ -57,16 +56,10 @@ class GalleryViewerPage extends HookConsumerWidget {
final totalAssets = useState(renderList.totalAssets);
final isZoomed = useState(false);
final isPlayingMotionVideo = useState(false);
final stackIndex = useState(-1);
final stackIndex = useState(0);
final localPosition = useRef<Offset?>(null);
final currentIndex = useValueNotifier(initialIndex);
final loadAsset = renderList.loadAsset;
final currentAsset = loadAsset(currentIndex.value);
final stack = showStack && currentAsset.stackCount > 0
? ref.watch(assetStackStateProvider(currentAsset))
: <Asset>[];
final stackElements = showStack ? [currentAsset, ...stack] : <Asset>[];
// // Update is playing motion video
ref.listen(
@ -197,58 +190,6 @@ class GalleryViewerPage extends HookConsumerWidget {
}
});
Widget buildStackedChildren() {
if (!showStack) {
return const SizedBox();
}
return ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: stackElements.length,
padding: const EdgeInsets.only(
left: 5,
right: 5,
bottom: 30,
),
itemBuilder: (context, index) {
final assetId = stackElements.elementAt(index).remoteId;
if (assetId == null) {
return const SizedBox();
}
return Padding(
key: ValueKey(assetId),
padding: const EdgeInsets.only(right: 5),
child: GestureDetector(
onTap: () => stackIndex.value = index,
child: Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(6),
border: (stackIndex.value == -1 && index == 0) ||
index == stackIndex.value
? Border.all(
color: Colors.white,
width: 2,
)
: null,
),
child: ClipRRect(
borderRadius: BorderRadius.circular(4),
child: Image(
fit: BoxFit.cover,
image: ImmichRemoteImageProvider(assetId: assetId),
),
),
),
),
);
},
);
}
PhotoViewGalleryPageOptions buildImage(BuildContext context, Asset asset) {
return PhotoViewGalleryPageOptions(
onDragStart: (_, details, __) {
@ -262,9 +203,7 @@ class GalleryViewerPage extends HookConsumerWidget {
},
onLongPressStart: asset.isMotionPhoto
? (_, __, ___) {
if (asset.isMotionPhoto) {
isPlayingMotionVideo.value = true;
}
isPlayingMotionVideo.value = true;
}
: null,
imageProvider: ImmichImage.imageProvider(asset: asset),
@ -316,7 +255,16 @@ class GalleryViewerPage extends HookConsumerWidget {
}
PhotoViewGalleryPageOptions buildAsset(BuildContext context, int index) {
final newAsset = loadAsset(index);
isPlayingMotionVideo.value = false;
var newAsset = loadAsset(index);
final stackId = newAsset.stackId;
if (stackId != null && currentIndex.value == index) {
final stackElements =
ref.read(assetStackStateProvider(newAsset.stackId!));
if (stackIndex.value < stackElements.length) {
newAsset = stackElements.elementAt(stackIndex.value);
}
}
if (newAsset.isImage && !isPlayingMotionVideo.value) {
return buildImage(context, newAsset);
@ -324,8 +272,6 @@ class GalleryViewerPage extends HookConsumerWidget {
return buildVideo(context, newAsset);
}
log.info('GalleryViewerPage: Building gallery viewer page');
return PopScope(
// Change immersive mode back to normal "edgeToEdge" mode
onPopInvokedWithResult: (didPop, _) =>
@ -387,7 +333,7 @@ class GalleryViewerPage extends HookConsumerWidget {
final newAsset = loadAsset(value);
currentIndex.value = value;
stackIndex.value = -1;
stackIndex.value = 0;
isPlayingMotionVideo.value = false;
ref.read(currentAssetProvider.notifier).set(newAsset);
@ -418,13 +364,7 @@ class GalleryViewerPage extends HookConsumerWidget {
right: 0,
child: Column(
children: [
Visibility(
visible: stack.isNotEmpty,
child: SizedBox(
height: 80,
child: buildStackedChildren(),
),
),
GalleryStackedChildren(stackIndex),
BottomGalleryBar(
key: const ValueKey('bottom-bar'),
renderList: renderList,

View file

@ -7,49 +7,49 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'asset_stack.provider.g.dart';
class AssetStackNotifier extends StateNotifier<List<Asset>> {
final Asset _asset;
final String _stackId;
final Ref _ref;
AssetStackNotifier(
this._asset,
this._ref,
) : super([]) {
fetchStackChildren();
AssetStackNotifier(this._stackId, this._ref) : super([]) {
_fetchStack(_stackId);
}
void fetchStackChildren() async {
if (mounted) {
state = await _ref.read(assetStackProvider(_asset).future);
void _fetchStack(String stackId) async {
if (!mounted) {
return;
}
final stack = await _ref.read(assetStackProvider(stackId).future);
if (stack.isNotEmpty) {
state = stack;
}
}
void removeChild(int index) {
if (index < state.length) {
state.removeAt(index);
state = List<Asset>.from(state);
}
}
}
final assetStackStateProvider = StateNotifierProvider.autoDispose
.family<AssetStackNotifier, List<Asset>, Asset>(
(ref, asset) => AssetStackNotifier(asset, ref),
.family<AssetStackNotifier, List<Asset>, String>(
(ref, stackId) => AssetStackNotifier(stackId, ref),
);
final assetStackProvider =
FutureProvider.autoDispose.family<List<Asset>, Asset>((ref, asset) async {
// Guard [local asset]
if (asset.remoteId == null) {
return [];
}
return await ref
FutureProvider.autoDispose.family<List<Asset>, String>((ref, stackId) {
return ref
.watch(dbProvider)
.assets
.filter()
.isArchivedEqualTo(false)
.isTrashedEqualTo(false)
.stackPrimaryAssetIdEqualTo(asset.remoteId)
.sortByFileCreatedAtDesc()
.stackIdEqualTo(stackId)
// orders primary asset first as its ID is null
.sortByStackPrimaryAssetId()
.thenByFileCreatedAtDesc()
.findAll();
});

View file

@ -52,9 +52,10 @@ class BottomGalleryBar extends ConsumerWidget {
}
final isOwner = asset.ownerId == ref.watch(currentUserProvider)?.isarId;
final showControls = ref.watch(showControlsProvider);
final stackId = asset.stackId;
final stackItems = showStack && asset.stackCount > 0
? ref.watch(assetStackStateProvider(asset))
final stackItems = showStack && stackId != null
? ref.watch(assetStackStateProvider(stackId))
: <Asset>[];
bool isStackPrimaryAsset = asset.stackPrimaryAssetId == null;
final navStack = AutoRouter.of(context).stackData;
@ -66,9 +67,9 @@ class BottomGalleryBar extends ConsumerWidget {
final isInAlbum = ref.watch(currentAlbumProvider)?.isRemote ?? false;
void removeAssetFromStack() {
if (stackIndex > 0 && showStack) {
if (stackIndex > 0 && showStack && stackId != null) {
ref
.read(assetStackStateProvider(asset).notifier)
.read(assetStackStateProvider(stackId).notifier)
.removeChild(stackIndex - 1);
}
}
@ -137,7 +138,7 @@ class BottomGalleryBar extends ConsumerWidget {
await ref
.read(stackServiceProvider)
.deleteStack(asset.stackId!, [asset, ...stackItems]);
.deleteStack(asset.stackId!, stackItems);
}
void showStackActionItems() {