diff --git a/mobile/lib/entities/asset.entity.dart b/mobile/lib/entities/asset.entity.dart index 2f04f61c5a..24b88701cc 100644 --- a/mobile/lib/entities/asset.entity.dart +++ b/mobile/lib/entities/asset.entity.dart @@ -168,6 +168,9 @@ class Asset { @ignore bool get isImage => type == AssetType.image; + @ignore + bool get isVideo => type == AssetType.video; + @ignore bool get isMotionPhoto => livePhotoVideoId != null; diff --git a/mobile/lib/pages/common/gallery_viewer.page.dart b/mobile/lib/pages/common/gallery_viewer.page.dart index 6cc36867f4..2ef23e77f3 100644 --- a/mobile/lib/pages/common/gallery_viewer.page.dart +++ b/mobile/lib/pages/common/gallery_viewer.page.dart @@ -79,10 +79,8 @@ class GalleryViewerPage extends HookConsumerWidget { ref.listen( videoPlaybackValueProvider.select( (playback) => playback.state == VideoPlaybackState.playing, - ), (wasPlaying, isPlaying) { - if (wasPlaying != null && wasPlaying && !isPlaying) { - isPlayingMotionVideo.value = false; - } + ), (_, isPlaying) { + isPlayingMotionVideo.value = isPlaying; }); } @@ -271,11 +269,13 @@ class GalleryViewerPage extends HookConsumerWidget { onTapDown: (_, __, ___) { ref.read(showControlsProvider.notifier).toggle(); }, - onLongPressStart: (_, __, ___) { - if (asset.livePhotoVideoId != null) { - isPlayingMotionVideo.value = true; - } - }, + onLongPressStart: asset.isMotionPhoto + ? (_, __, ___) { + if (asset.isMotionPhoto) { + isPlayingMotionVideo.value = true; + } + } + : null, imageProvider: ImmichImage.imageProvider(asset: asset), heroAttributes: PhotoViewHeroAttributes( tag: getHeroTag(asset), @@ -336,10 +336,6 @@ class GalleryViewerPage extends HookConsumerWidget { PhotoViewGalleryPageOptions buildAsset(BuildContext context, int index) { final newAsset = index == currentIndex.value ? asset : loadAsset(index); - if (newAsset.isImage) { - ref.read(showControlsProvider.notifier).show = false; - } - if (newAsset.isImage && !isPlayingMotionVideo.value) { return buildImage(context, newAsset); } @@ -357,8 +353,11 @@ class GalleryViewerPage extends HookConsumerWidget { PhotoViewGallery.builder( key: ValueKey(asset), scaleStateChangedCallback: (state) { - isZoomed.value = state != PhotoViewScaleState.initial; - ref.read(showControlsProvider.notifier).show = !isZoomed.value; + if (asset.isImage && !isPlayingMotionVideo.value) { + isZoomed.value = state != PhotoViewScaleState.initial; + ref.read(showControlsProvider.notifier).show = + !isZoomed.value; + } }, // wantKeepAlive: true, gaplessPlayback: true, @@ -396,15 +395,15 @@ class GalleryViewerPage extends HookConsumerWidget { final newAsset = value == currentIndex.value ? asset : loadAsset(value); - if (!newAsset.isImage || newAsset.isMotionPhoto) { - ref.read(videoPlaybackValueProvider.notifier).reset(); - } currentIndex.value = value; stackIndex.value = -1; isPlayingMotionVideo.value = false; ref.read(currentAssetProvider.notifier).set(newAsset); + if (newAsset.isVideo || newAsset.isMotionPhoto) { + ref.read(videoPlaybackValueProvider.notifier).reset(); + } // Wait for page change animation to finish, then precache the next image Timer(const Duration(milliseconds: 400), () { @@ -418,11 +417,8 @@ class GalleryViewerPage extends HookConsumerWidget { left: 0, right: 0, child: GalleryAppBar( - asset: asset, showInfo: showInfo, - isPlayingVideo: isPlayingMotionVideo.value, - onToggleMotionVideo: () => - isPlayingMotionVideo.value = !isPlayingMotionVideo.value, + isPlayingMotionVideo: isPlayingMotionVideo, ), ), Positioned( @@ -444,10 +440,7 @@ class GalleryViewerPage extends HookConsumerWidget { controller: controller, showStack: showStack, stackIndex: stackIndex.value, - asset: asset, assetIndex: currentIndex, - showVideoPlayerControls: - !asset.isImage && !asset.isMotionPhoto, ), ], ), diff --git a/mobile/lib/pages/common/native_video_viewer.page.dart b/mobile/lib/pages/common/native_video_viewer.page.dart index e9ee79275d..663f4e0774 100644 --- a/mobile/lib/pages/common/native_video_viewer.page.dart +++ b/mobile/lib/pages/common/native_video_viewer.page.dart @@ -184,8 +184,8 @@ class NativeVideoViewerPage extends HookConsumerWidget { try { if (mute && playbackInfo.volume != 0.0) { await playerController.setVolume(0.0); - } else if (!mute && playbackInfo.volume != 0.7) { - await playerController.setVolume(0.7); + } else if (!mute && playbackInfo.volume != 0.9) { + await playerController.setVolume(0.9); } } catch (error) { log.severe('Error setting volume: $error'); diff --git a/mobile/lib/widgets/asset_viewer/bottom_gallery_bar.dart b/mobile/lib/widgets/asset_viewer/bottom_gallery_bar.dart index eadaf0bf9f..9199720c32 100644 --- a/mobile/lib/widgets/asset_viewer/bottom_gallery_bar.dart +++ b/mobile/lib/widgets/asset_viewer/bottom_gallery_bar.dart @@ -10,6 +10,7 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/providers/album/album.provider.dart'; import 'package:immich_mobile/providers/album/current_album.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/download.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart'; import 'package:immich_mobile/services/stack.service.dart'; @@ -26,12 +27,10 @@ import 'package:immich_mobile/widgets/common/immich_toast.dart'; import 'package:immich_mobile/pages/editing/edit.page.dart'; class BottomGalleryBar extends ConsumerWidget { - final Asset asset; final ValueNotifier assetIndex; final bool showStack; final int stackIndex; final ValueNotifier totalAssets; - final bool showVideoPlayerControls; final PageController controller; final RenderList renderList; @@ -39,17 +38,20 @@ class BottomGalleryBar extends ConsumerWidget { super.key, required this.showStack, required this.stackIndex, - required this.asset, required this.assetIndex, required this.controller, required this.totalAssets, - required this.showVideoPlayerControls, required this.renderList, }); @override Widget build(BuildContext context, WidgetRef ref) { + final asset = ref.watch(currentAssetProvider); + if (asset == null) { + return const SizedBox(); + } final isOwner = asset.ownerId == ref.watch(currentUserProvider)?.isarId; + final showControls = ref.watch(showControlsProvider); final stackItems = showStack && asset.stackCount > 0 ? ref.watch(assetStackStateProvider(asset)) @@ -324,7 +326,7 @@ class BottomGalleryBar extends ConsumerWidget { }, ]; return IgnorePointer( - ignoring: !ref.watch(showControlsProvider), + ignoring: !showControls, child: AnimatedOpacity( duration: const Duration(milliseconds: 100), opacity: ref.watch(showControlsProvider) ? 1.0 : 0.0, @@ -341,7 +343,7 @@ class BottomGalleryBar extends ConsumerWidget { padding: EdgeInsets.only(top: 40.0), child: Column( children: [ - if (showVideoPlayerControls) const VideoControls(), + if (asset.isVideo) const VideoControls(), BottomNavigationBar( elevation: 0.0, backgroundColor: Colors.transparent, diff --git a/mobile/lib/widgets/asset_viewer/custom_video_player_controls.dart b/mobile/lib/widgets/asset_viewer/custom_video_player_controls.dart index 2fd0a38edc..168c4ebffb 100644 --- a/mobile/lib/widgets/asset_viewer/custom_video_player_controls.dart +++ b/mobile/lib/widgets/asset_viewer/custom_video_player_controls.dart @@ -17,10 +17,15 @@ class CustomVideoPlayerControls extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final showControls = ref.watch(showControlsProvider); // A timer to hide the controls final hideTimer = useTimer( hideTimerDuration, () { + if (!context.mounted) { + return; + } + final state = ref.read(videoPlaybackValueProvider).state; // Do not hide on paused if (state != VideoPlaybackState.paused) { @@ -66,7 +71,7 @@ class CustomVideoPlayerControls extends HookConsumerWidget { behavior: HitTestBehavior.opaque, onTap: showControlsAndStartHideTimer, child: AbsorbPointer( - absorbing: !ref.watch(showControlsProvider), + absorbing: !showControls, child: Stack( children: [ if (showBuffering) @@ -84,7 +89,7 @@ class CustomVideoPlayerControls extends HookConsumerWidget { iconColor: Colors.white, isFinished: state == VideoPlaybackState.completed, isPlaying: state == VideoPlaybackState.playing, - show: ref.watch(showControlsProvider), + show: showControls, onPressed: togglePlay, ), ), diff --git a/mobile/lib/widgets/asset_viewer/gallery_app_bar.dart b/mobile/lib/widgets/asset_viewer/gallery_app_bar.dart index f400224e0a..30cc709452 100644 --- a/mobile/lib/widgets/asset_viewer/gallery_app_bar.dart +++ b/mobile/lib/widgets/asset_viewer/gallery_app_bar.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/providers/album/current_album.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart'; import 'package:immich_mobile/widgets/album/add_to_album_bottom_sheet.dart'; import 'package:immich_mobile/providers/asset_viewer/download.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart'; @@ -19,23 +20,24 @@ import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; class GalleryAppBar extends ConsumerWidget { - final Asset asset; final void Function() showInfo; - final void Function() onToggleMotionVideo; - final bool isPlayingVideo; + final ValueNotifier isPlayingMotionVideo; const GalleryAppBar({ super.key, - required this.asset, required this.showInfo, - required this.onToggleMotionVideo, - required this.isPlayingVideo, + required this.isPlayingMotionVideo, }); @override Widget build(BuildContext context, WidgetRef ref) { + final asset = ref.watch(currentAssetProvider); + if (asset == null) { + return const SizedBox(); + } final album = ref.watch(currentAlbumProvider); final isOwner = asset.ownerId == ref.watch(currentUserProvider)?.isarId; + final showControls = ref.watch(showControlsProvider); final isPartner = ref .watch(partnerSharedWithProvider) @@ -98,23 +100,24 @@ class GalleryAppBar extends ConsumerWidget { } return IgnorePointer( - ignoring: !ref.watch(showControlsProvider), + ignoring: !showControls, child: AnimatedOpacity( duration: const Duration(milliseconds: 100), - opacity: ref.watch(showControlsProvider) ? 1.0 : 0.0, + opacity: showControls ? 1.0 : 0.0, child: Container( color: Colors.black.withOpacity(0.4), child: TopControlAppBar( isOwner: isOwner, isPartner: isPartner, - isPlayingMotionVideo: isPlayingVideo, + isPlayingMotionVideo: isPlayingMotionVideo.value, asset: asset, onMoreInfoPressed: showInfo, onFavorite: toggleFavorite, onRestorePressed: () => handleRestore(asset), onUploadPressed: asset.isLocal ? () => handleUpload(asset) : null, onDownloadPressed: asset.isLocal ? null : handleDownloadAsset, - onToggleMotionVideo: onToggleMotionVideo, + onToggleMotionVideo: () => + isPlayingMotionVideo.value = !isPlayingMotionVideo.value, onAddToAlbumPressed: () => addToAlbum(asset), onActivitiesPressed: handleActivities, ),