mirror of
https://github.com/immich-app/immich.git
synced 2026-05-22 07:32:32 -04:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0152132703 | |||
| a11bfe5801 | |||
| 2d07d535b5 |
@@ -0,0 +1,13 @@
|
||||
class AssetIndex {
|
||||
final int currentIndex;
|
||||
final int stackIndex;
|
||||
|
||||
AssetIndex({required this.currentIndex, this.stackIndex = 0});
|
||||
|
||||
AssetIndex copyWith({int? currentIndex, int? stackIndex}) {
|
||||
return AssetIndex(
|
||||
currentIndex: currentIndex ?? this.currentIndex,
|
||||
stackIndex: stackIndex ?? this.stackIndex,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/models/store.dart';
|
||||
|
||||
/// The stacked children assets for the gallyer viewer page
|
||||
class StackedChildren extends StatelessWidget {
|
||||
|
||||
/// The stack index
|
||||
final int stackIndex;
|
||||
|
||||
/// The elements in this stack
|
||||
final List<Asset> stackElements;
|
||||
|
||||
/// A callback function to get the stack index of the tapped element
|
||||
final Function(int)? onTap;
|
||||
|
||||
const StackedChildren({
|
||||
super.key,
|
||||
required this.stackIndex,
|
||||
required this.stackElements,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: stackElements.length,
|
||||
itemBuilder: (context, i) {
|
||||
final assetId = stackElements.elementAt(i).remoteId;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 10),
|
||||
child: GestureDetector(
|
||||
onTap: () => onTap?.call(i),
|
||||
child: Container(
|
||||
width: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
border: (stackIndex == -1 && i == 0) || i == stackIndex
|
||||
? Border.all(
|
||||
color: Colors.white,
|
||||
width: 2,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: CachedNetworkImage(
|
||||
fit: BoxFit.cover,
|
||||
imageUrl:
|
||||
'${Store.get(StoreKey.serverEndpoint)}/asset/thumbnail/$assetId',
|
||||
httpHeaders: {
|
||||
"Authorization":
|
||||
"Bearer ${Store.get(StoreKey.accessToken)}",
|
||||
},
|
||||
errorWidget: (context, url, error) =>
|
||||
const Icon(Icons.image_not_supported_outlined),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/modules/album/providers/current_album.provider.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/models/asset_index.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/providers/asset_stack.provider.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/providers/current_asset.provider.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/providers/show_controls.provider.dart';
|
||||
@@ -20,6 +21,7 @@ import 'package:immich_mobile/modules/asset_viewer/providers/video_player_value_
|
||||
import 'package:immich_mobile/modules/asset_viewer/services/asset_stack.service.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/ui/advanced_bottom_sheet.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/ui/exif_bottom_sheet.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/ui/stacked_children.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/ui/top_control_app_bar.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/views/video_viewer_page.dart';
|
||||
import 'package:immich_mobile/modules/backup/providers/manual_upload.provider.dart';
|
||||
@@ -78,16 +80,19 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
final isPlayingMotionVideo = useState(false);
|
||||
final isPlayingVideo = useState(false);
|
||||
Offset? localPosition;
|
||||
final header = {"x-immich-user-token": Store.get(StoreKey.accessToken)};
|
||||
final currentIndex = useState(initialIndex);
|
||||
final currentAsset = loadAsset(currentIndex.value);
|
||||
final authToken = 'Bearer ${Store.get(StoreKey.accessToken)}';
|
||||
final header = {"Authorization": authToken};
|
||||
final isTrashEnabled =
|
||||
ref.watch(serverInfoProvider.select((v) => v.serverFeatures.trash));
|
||||
final navStack = AutoRouter.of(context).stackData;
|
||||
final isFromTrash = isTrashEnabled &&
|
||||
navStack.length > 2 &&
|
||||
navStack.elementAt(navStack.length - 2).name == TrashRoute.name;
|
||||
final stackIndex = useState(-1);
|
||||
final index = useState(AssetIndex(currentIndex: initialIndex));
|
||||
final stackIndex = index.value.stackIndex;
|
||||
final currentIndex = index.value.currentIndex;
|
||||
final currentAsset = loadAsset(currentIndex);
|
||||
|
||||
final stack = showStack && currentAsset.stackChildrenCount > 0
|
||||
? ref.watch(assetStackStateProvider(currentAsset))
|
||||
: <Asset>[];
|
||||
@@ -96,16 +101,15 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
final isFromDto = currentAsset.id == Isar.autoIncrement;
|
||||
final album = ref.watch(currentAlbumProvider);
|
||||
|
||||
Asset asset() => stackIndex.value == -1
|
||||
? currentAsset
|
||||
: stackElements.elementAt(stackIndex.value);
|
||||
Asset asset() =>
|
||||
stackIndex == -1 ? currentAsset : stackElements.elementAt(stackIndex);
|
||||
final isOwner = asset().ownerId == ref.watch(currentUserProvider)?.isarId;
|
||||
final isPartner = ref
|
||||
.watch(partnerSharedWithProvider)
|
||||
.map((e) => e.isarId)
|
||||
.contains(asset().ownerId);
|
||||
|
||||
bool isParent = stackIndex.value == -1 || stackIndex.value == 0;
|
||||
bool isParent = stackIndex == -1 || stackIndex == 0;
|
||||
|
||||
// Listen provider to prevent autoDispose when navigating to other routes from within the gallery page
|
||||
ref.listen(currentAssetProvider, (_, __) {});
|
||||
@@ -211,11 +215,11 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
void removeAssetFromStack() {
|
||||
if (stackIndex.value > 0 && showStack) {
|
||||
if (stackIndex > 0 && showStack) {
|
||||
ref
|
||||
.read(assetStackStateProvider(currentAsset).notifier)
|
||||
.removeChild(stackIndex.value - 1);
|
||||
stackIndex.value = stackIndex.value - 1;
|
||||
.removeChild(stackIndex - 1);
|
||||
index.value = index.value.copyWith(stackIndex: stackIndex - 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -492,50 +496,6 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildStackedChildren() {
|
||||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: stackElements.length,
|
||||
itemBuilder: (context, index) {
|
||||
final assetId = stackElements.elementAt(index).remoteId;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 10),
|
||||
child: GestureDetector(
|
||||
onTap: () => stackIndex.value = index,
|
||||
child: Container(
|
||||
width: 40,
|
||||
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: CachedNetworkImage(
|
||||
fit: BoxFit.cover,
|
||||
imageUrl:
|
||||
'${Store.get(StoreKey.serverEndpoint)}/asset/thumbnail/$assetId',
|
||||
httpHeaders: {
|
||||
"x-immich-user-token": Store.get(StoreKey.accessToken),
|
||||
},
|
||||
errorWidget: (context, url, error) =>
|
||||
const Icon(Icons.image_not_supported_outlined),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void showStackActionItems() {
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
@@ -558,7 +518,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
.read(assetStackServiceProvider)
|
||||
.updateStackParent(
|
||||
currentAsset,
|
||||
stackElements.elementAt(stackIndex.value),
|
||||
stackElements.elementAt(stackIndex),
|
||||
);
|
||||
ctx.pop();
|
||||
context.popRoute();
|
||||
@@ -593,7 +553,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
await ref.read(assetStackServiceProvider).updateStack(
|
||||
currentAsset,
|
||||
childrenToRemove: [
|
||||
stackElements.elementAt(stackIndex.value),
|
||||
stackElements.elementAt(stackIndex),
|
||||
],
|
||||
);
|
||||
removeAssetFromStack();
|
||||
@@ -697,7 +657,12 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
),
|
||||
child: SizedBox(
|
||||
height: 40,
|
||||
child: buildStackedChildren(),
|
||||
child: StackedChildren(
|
||||
stackIndex: stackIndex,
|
||||
stackElements: stackElements,
|
||||
onTap: (i) =>
|
||||
index.value = index.value.copyWith(stackIndex: i),
|
||||
),
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
@@ -775,10 +740,10 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
itemCount: totalAssets,
|
||||
scrollDirection: Axis.horizontal,
|
||||
onPageChanged: (value) {
|
||||
final next = currentIndex.value < value ? value + 1 : value - 1;
|
||||
final next = currentIndex < value ? value + 1 : value - 1;
|
||||
precacheNextImage(next);
|
||||
currentIndex.value = value;
|
||||
stackIndex.value = -1;
|
||||
index.value = index.value
|
||||
.copyWith(currentIndex: value, stackIndex: - 1);
|
||||
HapticFeedback.selectionClick();
|
||||
},
|
||||
loadingBuilder: (context, event, index) {
|
||||
@@ -819,8 +784,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
: webPThumbnail;
|
||||
},
|
||||
builder: (context, index) {
|
||||
final a =
|
||||
index == currentIndex.value ? asset() : loadAsset(index);
|
||||
final a = index == currentIndex ? asset() : loadAsset(index);
|
||||
final ImageProvider provider = finalImageProvider(a);
|
||||
|
||||
if (a.isImage && !isPlayingMotionVideo.value) {
|
||||
|
||||
Reference in New Issue
Block a user