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

use current asset provider and loadAsset

This commit is contained in:
mertalev 2024-11-15 12:36:34 -05:00
parent 613ce513cd
commit 3b9a3d4037
No known key found for this signature in database
GPG key ID: CA85EF6600C9E8AD
6 changed files with 145 additions and 125 deletions

View file

@ -67,22 +67,17 @@ class GalleryViewerPage extends HookConsumerWidget {
? ref.watch(assetStackStateProvider(currentAsset))
: <Asset>[];
final stackElements = showStack ? [currentAsset, ...stack] : <Asset>[];
// Assets from response DTOs do not have an isar id, querying which would give us the default autoIncrement id
final isFromDto = currentAsset.id == noDbId;
Asset asset = stackIndex.value == -1
? currentAsset
: stackElements.elementAt(stackIndex.value);
// // Update is playing motion video
if (asset.isMotionPhoto) {
ref.listen(
videoPlaybackValueProvider.select(
(playback) => playback.state == VideoPlaybackState.playing,
), (_, isPlaying) {
final asset = ref.read(currentAssetProvider);
if (asset != null && asset.isMotionPhoto) {
isPlayingMotionVideo.value = isPlaying;
});
}
});
Future<void> precacheNextImage(int index) async {
if (!context.mounted) {
@ -114,11 +109,8 @@ class GalleryViewerPage extends HookConsumerWidget {
}
}
// Listen provider to prevent autoDispose when navigating to other routes from within the gallery page
ref.listen(currentAssetProvider, (prev, cur) {});
useEffect(() {
ref.read(currentAssetProvider.notifier).set(asset);
useEffect(
() {
if (ref.read(showControlsProvider)) {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
} else {
@ -131,9 +123,15 @@ class GalleryViewerPage extends HookConsumerWidget {
});
return null;
});
},
[],
);
void showInfo() {
final asset = ref.read(currentAssetProvider);
if (asset == null) {
return;
}
showModalBottomSheet(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(15.0)),
@ -205,7 +203,6 @@ class GalleryViewerPage extends HookConsumerWidget {
}
return ListView.builder(
key: ValueKey(currentAsset),
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: stackElements.length,
@ -252,12 +249,6 @@ class GalleryViewerPage extends HookConsumerWidget {
);
}
Object getHeroTag(Asset asset) {
return isFromDto
? '${asset.remoteId}-$heroOffset'
: asset.id + heroOffset;
}
PhotoViewGalleryPageOptions buildImage(BuildContext context, Asset asset) {
return PhotoViewGalleryPageOptions(
onDragStart: (_, details, __) {
@ -277,10 +268,7 @@ class GalleryViewerPage extends HookConsumerWidget {
}
: null,
imageProvider: ImmichImage.imageProvider(asset: asset),
heroAttributes: PhotoViewHeroAttributes(
tag: getHeroTag(asset),
transitionOnUserGestures: true,
),
heroAttributes: _getHeroAttributes(asset),
filterQuality: FilterQuality.high,
tightMode: true,
minScale: PhotoViewComputedScale.contained,
@ -294,22 +282,16 @@ class GalleryViewerPage extends HookConsumerWidget {
PhotoViewGalleryPageOptions buildVideo(BuildContext context, Asset asset) {
// This key is to prevent the video player from being re-initialized during the hero animation
final key = GlobalKey();
final tag = getHeroTag(asset);
return PhotoViewGalleryPageOptions.customChild(
onDragStart: (_, details, __) =>
localPosition.value = details.localPosition,
onDragUpdate: (_, details, __) => handleSwipeUpDown(details),
heroAttributes: PhotoViewHeroAttributes(
tag: tag,
transitionOnUserGestures: true,
),
heroAttributes: _getHeroAttributes(asset),
filterQuality: FilterQuality.high,
initialScale: 1.0,
maxScale: 1.0,
minScale: 1.0,
basePosition: Alignment.center,
child: Hero(
tag: tag,
child: SizedBox(
width: context.width,
height: context.height,
@ -330,12 +312,11 @@ class GalleryViewerPage extends HookConsumerWidget {
),
),
),
),
);
}
PhotoViewGalleryPageOptions buildAsset(BuildContext context, int index) {
final newAsset = index == currentIndex.value ? asset : loadAsset(index);
final newAsset = loadAsset(index);
if (newAsset.isImage && !isPlayingMotionVideo.value) {
return buildImage(context, newAsset);
@ -343,6 +324,8 @@ 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, _) =>
@ -352,17 +335,23 @@ class GalleryViewerPage extends HookConsumerWidget {
body: Stack(
children: [
PhotoViewGallery.builder(
key: ValueKey(asset),
key: const ValueKey('gallery'),
scaleStateChangedCallback: (state) {
final asset = ref.read(currentAssetProvider);
if (asset == null) {
return;
}
if (asset.isImage && !isPlayingMotionVideo.value) {
isZoomed.value = state != PhotoViewScaleState.initial;
ref.read(showControlsProvider.notifier).show =
!isZoomed.value;
}
},
// wantKeepAlive: true,
gaplessPlayback: true,
loadingBuilder: (context, event, index) => ClipRect(
loadingBuilder: (context, event, index) {
final asset = loadAsset(index);
return ClipRect(
child: Stack(
fit: StackFit.expand,
children: [
@ -379,7 +368,8 @@ class GalleryViewerPage extends HookConsumerWidget {
),
],
),
),
);
},
pageController: controller,
scrollPhysics: isZoomed.value
? const NeverScrollableScrollPhysics() // Don't allow paging while scrolled in
@ -394,8 +384,7 @@ class GalleryViewerPage extends HookConsumerWidget {
ref.read(hapticFeedbackProvider.notifier).selectionClick();
final newAsset =
value == currentIndex.value ? asset : loadAsset(value);
final newAsset = loadAsset(value);
currentIndex.value = value;
stackIndex.value = -1;
@ -418,6 +407,7 @@ class GalleryViewerPage extends HookConsumerWidget {
left: 0,
right: 0,
child: GalleryAppBar(
key: const ValueKey('app-bar'),
showInfo: showInfo,
isPlayingMotionVideo: isPlayingMotionVideo,
),
@ -436,6 +426,7 @@ class GalleryViewerPage extends HookConsumerWidget {
),
),
BottomGalleryBar(
key: const ValueKey('bottom-bar'),
renderList: renderList,
totalAssets: totalAssets,
controller: controller,
@ -452,4 +443,14 @@ class GalleryViewerPage extends HookConsumerWidget {
),
);
}
@pragma('vm:prefer-inline')
PhotoViewHeroAttributes _getHeroAttributes(Asset asset) {
return PhotoViewHeroAttributes(
tag: asset.isInDb
? asset.id + heroOffset
: '${asset.remoteId}-$heroOffset',
transitionOnUserGestures: true,
);
}
}

View file

@ -24,7 +24,6 @@ import 'package:wakelock_plus/wakelock_plus.dart';
class NativeVideoViewerPage extends HookConsumerWidget {
final Asset asset;
final bool showControls;
final Duration hideControlsTimer;
final Widget placeholder;
const NativeVideoViewerPage({
@ -32,7 +31,6 @@ class NativeVideoViewerPage extends HookConsumerWidget {
required this.asset,
required this.placeholder,
this.showControls = true,
this.hideControlsTimer = const Duration(seconds: 5),
});
@override
@ -370,6 +368,18 @@ class NativeVideoViewerPage extends HookConsumerWidget {
removeListeners(playerController);
}
final curAsset = currentAsset.value;
if (curAsset == asset) {
return;
}
// no need to delay video playback when swiping from an image to a video
if (curAsset != null && !curAsset.isVideo) {
currentAsset.value = value;
onPlaybackReady();
return;
}
// Delay the video playback to avoid a stutter in the swipe animation
Timer(const Duration(milliseconds: 300), () {
if (!context.mounted) {
@ -395,20 +405,19 @@ class NativeVideoViewerPage extends HookConsumerWidget {
log.severe('Error stopping video: $error');
});
controller.value = null;
WakelockPlus.disable();
};
},
[videoSource],
[],
);
return Stack(
children: [
placeholder, // this is always under the video to avoid flickering
if (aspectRatio.value != null)
Center(
key: ValueKey('player-${asset.hashCode}'),
child: aspectRatio.value != null
? AspectRatio(
key: ValueKey(asset),
child: AspectRatio(
key: ValueKey(asset),
aspectRatio: aspectRatio.value!,
child: isCurrent
@ -417,16 +426,9 @@ class NativeVideoViewerPage extends HookConsumerWidget {
onViewReady: initController,
)
: null,
)
: null,
),
if (showControls)
Center(
key: ValueKey('controls-${asset.hashCode}'),
child: CustomVideoPlayerControls(
hideTimerDuration: hideControlsTimer,
),
),
if (showControls) const Center(child: CustomVideoPlayerControls()),
],
);
}

View file

@ -1087,7 +1087,6 @@ class NativeVideoViewerRoute extends PageRouteInfo<NativeVideoViewerRouteArgs> {
required Asset asset,
required Widget placeholder,
bool showControls = true,
Duration hideControlsTimer = const Duration(seconds: 5),
List<PageRouteInfo>? children,
}) : super(
NativeVideoViewerRoute.name,
@ -1096,7 +1095,6 @@ class NativeVideoViewerRoute extends PageRouteInfo<NativeVideoViewerRouteArgs> {
asset: asset,
placeholder: placeholder,
showControls: showControls,
hideControlsTimer: hideControlsTimer,
),
initialChildren: children,
);
@ -1112,7 +1110,6 @@ class NativeVideoViewerRoute extends PageRouteInfo<NativeVideoViewerRouteArgs> {
asset: args.asset,
placeholder: args.placeholder,
showControls: args.showControls,
hideControlsTimer: args.hideControlsTimer,
);
},
);
@ -1124,7 +1121,6 @@ class NativeVideoViewerRouteArgs {
required this.asset,
required this.placeholder,
this.showControls = true,
this.hideControlsTimer = const Duration(seconds: 5),
});
final Key? key;
@ -1135,11 +1131,9 @@ class NativeVideoViewerRouteArgs {
final bool showControls;
final Duration hideControlsTimer;
@override
String toString() {
return 'NativeVideoViewerRouteArgs{key: $key, asset: $asset, placeholder: $placeholder, showControls: $showControls, hideControlsTimer: $hideControlsTimer}';
return 'NativeVideoViewerRouteArgs{key: $key, asset: $asset, placeholder: $placeholder, showControls: $showControls}';
}
}

View file

@ -12,7 +12,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/collection_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/scroll_notifier.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart';
import 'package:immich_mobile/widgets/asset_grid/asset_drag_region.dart';
import 'package:immich_mobile/widgets/asset_grid/thumbnail_image.dart';
import 'package:immich_mobile/widgets/asset_grid/thumbnail_placeholder.dart';
@ -89,6 +91,7 @@ class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
ScrollOffsetController();
final ItemPositionsListener _itemPositionsListener =
ItemPositionsListener.create();
late final KeepAliveLink currentAssetLink;
/// The timestamp when the haptic feedback was last invoked
int _hapticFeedbackTS = 0;
@ -201,6 +204,12 @@ class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
allAssetsSelected: _allAssetsSelected,
showStack: widget.showStack,
heroOffset: widget.heroOffset,
onAssetTap: (asset) {
ref.read(currentAssetProvider.notifier).set(asset);
if (asset.isVideo) {
ref.read(showControlsProvider.notifier).show = false;
}
},
);
}
@ -348,6 +357,7 @@ class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
@override
void initState() {
super.initState();
currentAssetLink = ref.read(currentAssetProvider.notifier).ref.keepAlive();
scrollToTopNotifierProvider.addListener(_scrollToTop);
scrollToDateNotifierProvider.addListener(_scrollToDate);
@ -369,6 +379,7 @@ class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
_itemPositionsListener.itemPositions.removeListener(_positionListener);
}
_itemPositionsListener.itemPositions.removeListener(_hapticsListener);
currentAssetLink.close();
super.dispose();
}
@ -595,12 +606,13 @@ class _Section extends StatelessWidget {
final RenderList renderList;
final bool selectionActive;
final bool dynamicLayout;
final Function(List<Asset>) selectAssets;
final Function(List<Asset>) deselectAssets;
final void Function(List<Asset>) selectAssets;
final void Function(List<Asset>) deselectAssets;
final bool Function(List<Asset>) allAssetsSelected;
final bool showStack;
final int heroOffset;
final bool showStorageIndicator;
final void Function(Asset) onAssetTap;
const _Section({
required this.section,
@ -618,6 +630,7 @@ class _Section extends StatelessWidget {
required this.showStack,
required this.heroOffset,
required this.showStorageIndicator,
required this.onAssetTap,
});
@override
@ -683,6 +696,7 @@ class _Section extends StatelessWidget {
selectionActive: selectionActive,
onSelect: (asset) => selectAssets([asset]),
onDeselect: (asset) => deselectAssets([asset]),
onAssetTap: onAssetTap,
),
],
);
@ -724,9 +738,9 @@ class _Title extends StatelessWidget {
final String title;
final List<Asset> assets;
final bool selectionActive;
final Function(List<Asset>) selectAssets;
final Function(List<Asset>) deselectAssets;
final Function(List<Asset>) allAssetsSelected;
final void Function(List<Asset>) selectAssets;
final void Function(List<Asset>) deselectAssets;
final bool Function(List<Asset>) allAssetsSelected;
const _Title({
required this.title,
@ -765,8 +779,9 @@ class _AssetRow extends StatelessWidget {
final bool showStorageIndicator;
final int heroOffset;
final bool showStack;
final Function(Asset)? onSelect;
final Function(Asset)? onDeselect;
final void Function(Asset) onAssetTap;
final void Function(Asset)? onSelect;
final void Function(Asset)? onDeselect;
final bool isSelectionActive;
const _AssetRow({
@ -786,6 +801,7 @@ class _AssetRow extends StatelessWidget {
required this.showStack,
required this.isSelectionActive,
required this.selectedAssets,
required this.onAssetTap,
this.onSelect,
this.onDeselect,
});
@ -838,6 +854,8 @@ class _AssetRow extends StatelessWidget {
onSelect?.call(asset);
}
} else {
final asset = renderList.loadAsset(absoluteOffset + index);
onAssetTap(asset);
context.pushRoute(
GalleryViewerRoute(
renderList: renderList,

View file

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.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_controls_provider.dart';
import 'package:immich_mobile/providers/asset_viewer/video_player_value_provider.dart';
@ -12,7 +13,7 @@ class CustomVideoPlayerControls extends HookConsumerWidget {
const CustomVideoPlayerControls({
super.key,
this.hideTimerDuration = const Duration(seconds: 3),
this.hideTimerDuration = const Duration(seconds: 5),
});
@override
@ -28,7 +29,12 @@ class CustomVideoPlayerControls extends HookConsumerWidget {
final state = ref.read(videoPlaybackValueProvider).state;
// Do not hide on paused
if (state != VideoPlaybackState.paused) {
if (state == VideoPlaybackState.paused) {
return;
}
final asset = ref.read(currentAssetProvider);
if (asset != null && asset.isVideo) {
ref.read(showControlsProvider.notifier).show = false;
}
},

View file

@ -71,7 +71,6 @@ class MemoryCard extends StatelessWidget {
child: NativeVideoViewerPage(
key: ValueKey(asset.id),
asset: asset,
hideControlsTimer: const Duration(seconds: 2),
showControls: false,
placeholder: SizedBox.expand(
child: ImmichImage(