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:
parent
3b9a3d4037
commit
d1c7ed5464
4 changed files with 123 additions and 100 deletions
82
mobile/lib/pages/common/gallery_stacked_children.dart
Normal file
82
mobile/lib/pages/common/gallery_stacked_children.dart
Normal 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),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
Loading…
Reference in a new issue