Compare commits

...

3 Commits

3 changed files with 109 additions and 63 deletions
@@ -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) {