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

fix live photo play button not updating

This commit is contained in:
mertalev 2024-11-18 10:21:38 -05:00
parent 20b1572b8e
commit 128f19efa5
No known key found for this signature in database
GPG key ID: CA85EF6600C9E8AD
8 changed files with 78 additions and 86 deletions

View file

@ -21,6 +21,7 @@ const Color immichBrandColorLight = Color(0xFF4150AF);
const Color immichBrandColorDark = Color(0xFFACCBFA); const Color immichBrandColorDark = Color(0xFFACCBFA);
const Color whiteOpacity75 = Color.fromARGB((0.75 * 255) ~/ 1, 255, 255, 255); const Color whiteOpacity75 = Color.fromARGB((0.75 * 255) ~/ 1, 255, 255, 255);
const Color red400 = Color(0xFFEF5350); const Color red400 = Color(0xFFEF5350);
const Color grey200 = Color(0xFFEEEEEE);
final Map<ImmichColorPreset, ImmichTheme> _themePresetsMap = { final Map<ImmichColorPreset, ImmichTheme> _themePresetsMap = {
ImmichColorPreset.indigo: ImmichTheme( ImmichColorPreset.indigo: ImmichTheme(

View file

@ -17,6 +17,7 @@ import 'package:immich_mobile/pages/common/gallery_stacked_children.dart';
import 'package:immich_mobile/providers/app_settings.provider.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/asset_stack.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/show_controls.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/asset_viewer/video_player_value_provider.dart';
import 'package:immich_mobile/providers/haptic_feedback.provider.dart'; import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
@ -55,7 +56,6 @@ class GalleryViewerPage extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final totalAssets = useState(renderList.totalAssets); final totalAssets = useState(renderList.totalAssets);
final isZoomed = useState(false); final isZoomed = useState(false);
final isPlayingMotionVideo = useValueNotifier(false);
final stackIndex = useState(0); final stackIndex = useState(0);
final localPosition = useRef<Offset?>(null); final localPosition = useRef<Offset?>(null);
final currentIndex = useValueNotifier(initialIndex); final currentIndex = useValueNotifier(initialIndex);
@ -192,7 +192,7 @@ class GalleryViewerPage extends HookConsumerWidget {
}, },
onLongPressStart: asset.isMotionPhoto onLongPressStart: asset.isMotionPhoto
? (_, __, ___) { ? (_, __, ___) {
isPlayingMotionVideo.value = true; ref.read(isPlayingMotionVideoProvider.notifier).playing = true;
} }
: null, : null,
imageProvider: ImmichImage.imageProvider(asset: asset), imageProvider: ImmichImage.imageProvider(asset: asset),
@ -226,7 +226,6 @@ class GalleryViewerPage extends HookConsumerWidget {
child: NativeVideoViewerPage( child: NativeVideoViewerPage(
key: key, key: key,
asset: asset, asset: asset,
isPlayingMotionVideo: isPlayingMotionVideo,
image: Image( image: Image(
key: ValueKey(asset), key: ValueKey(asset),
image: ImmichImage.imageProvider( image: ImmichImage.imageProvider(
@ -245,7 +244,7 @@ class GalleryViewerPage extends HookConsumerWidget {
} }
PhotoViewGalleryPageOptions buildAsset(BuildContext context, int index) { PhotoViewGalleryPageOptions buildAsset(BuildContext context, int index) {
isPlayingMotionVideo.value = false; ref.read(isPlayingMotionVideoProvider.notifier).playing = false;
var newAsset = loadAsset(index); var newAsset = loadAsset(index);
final stackId = newAsset.stackId; final stackId = newAsset.stackId;
if (stackId != null && currentIndex.value == index) { if (stackId != null && currentIndex.value == index) {
@ -278,7 +277,7 @@ class GalleryViewerPage extends HookConsumerWidget {
return; return;
} }
if (asset.isImage && !isPlayingMotionVideo.value) { if (asset.isImage && !ref.read(isPlayingMotionVideoProvider)) {
isZoomed.value = state != PhotoViewScaleState.initial; isZoomed.value = state != PhotoViewScaleState.initial;
ref.read(showControlsProvider.notifier).show = ref.read(showControlsProvider.notifier).show =
!isZoomed.value; !isZoomed.value;
@ -324,7 +323,6 @@ class GalleryViewerPage extends HookConsumerWidget {
currentIndex.value = value; currentIndex.value = value;
stackIndex.value = 0; stackIndex.value = 0;
isPlayingMotionVideo.value = false;
ref.read(currentAssetProvider.notifier).set(newAsset); ref.read(currentAssetProvider.notifier).set(newAsset);
if (newAsset.isVideo || newAsset.isMotionPhoto) { if (newAsset.isVideo || newAsset.isMotionPhoto) {
@ -345,7 +343,6 @@ class GalleryViewerPage extends HookConsumerWidget {
child: GalleryAppBar( child: GalleryAppBar(
key: const ValueKey('app-bar'), key: const ValueKey('app-bar'),
showInfo: showInfo, showInfo: showInfo,
isPlayingMotionVideo: isPlayingMotionVideo,
), ),
), ),
Positioned( Positioned(

View file

@ -8,6 +8,7 @@ import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/video_player_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'; import 'package:immich_mobile/providers/asset_viewer/video_player_value_provider.dart';
import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/api.service.dart';
@ -26,15 +27,10 @@ class NativeVideoViewerPage extends HookConsumerWidget {
final bool showControls; final bool showControls;
final Widget image; final Widget image;
/// Whether to display the video part of the motion photo
/// TODO: this should probably be a provider
final ValueNotifier<bool>? isPlayingMotionVideo;
const NativeVideoViewerPage({ const NativeVideoViewerPage({
super.key, super.key,
required this.asset, required this.asset,
required this.image, required this.image,
this.isPlayingMotionVideo,
this.showControls = true, this.showControls = true,
}); });
@ -48,10 +44,7 @@ class NativeVideoViewerPage extends HookConsumerWidget {
final controller = useState<NativeVideoPlayerController?>(null); final controller = useState<NativeVideoPlayerController?>(null);
final lastVideoPosition = useRef(-1); final lastVideoPosition = useRef(-1);
final isBuffering = useRef(false); final isBuffering = useRef(false);
final showMotionVideo = useState(false);
useListenable(isPlayingMotionVideo);
final showMotionVideo =
isPlayingMotionVideo != null && isPlayingMotionVideo!.value;
// When a video is opened through the timeline, `isCurrent` will immediately be true. // When a video is opened through the timeline, `isCurrent` will immediately be true.
// When swiping from video A to video B, `isCurrent` will initially be true for video A and false for video B. // When swiping from video A to video B, `isCurrent` will initially be true for video A and false for video B.
@ -65,6 +58,25 @@ class NativeVideoViewerPage extends HookConsumerWidget {
final log = Logger('NativeVideoViewerPage'); final log = Logger('NativeVideoViewerPage');
ref.listen(isPlayingMotionVideoProvider, (_, value) async {
final videoController = controller.value;
if (!asset.isMotionPhoto || videoController == null || !context.mounted) {
return;
}
showMotionVideo.value = value;
try {
if (value) {
await videoController.seekTo(0);
await videoController.play();
} else {
await videoController.pause();
}
} catch (error) {
log.severe('Error toggling motion video: $error');
}
});
Future<VideoSource?> createSource() async { Future<VideoSource?> createSource() async {
if (!context.mounted) { if (!context.mounted) {
return null; return null;
@ -227,9 +239,7 @@ class NativeVideoViewerPage extends HookConsumerWidget {
ref.read(videoPlaybackValueProvider.notifier).value = videoPlayback; ref.read(videoPlaybackValueProvider.notifier).value = videoPlayback;
try { try {
if (asset.isVideo || if (asset.isVideo || showMotionVideo.value) {
isPlayingMotionVideo == null ||
isPlayingMotionVideo!.value) {
await videoController.play(); await videoController.play();
} }
await videoController.setVolume(0.9); await videoController.setVolume(0.9);
@ -307,24 +317,6 @@ class NativeVideoViewerPage extends HookConsumerWidget {
} }
} }
void onToggleMotionVideo() async {
final videoController = controller.value;
if (videoController == null || !context.mounted) {
return;
}
try {
if (isPlayingMotionVideo!.value) {
await videoController.seekTo(0);
await videoController.play();
} else {
await videoController.pause();
}
} catch (error) {
log.severe('Error toggling motion video: $error');
}
}
void removeListeners(NativeVideoPlayerController controller) { void removeListeners(NativeVideoPlayerController controller) {
controller.onPlaybackPositionChanged controller.onPlaybackPositionChanged
.removeListener(onPlaybackPositionChanged); .removeListener(onPlaybackPositionChanged);
@ -397,16 +389,8 @@ class NativeVideoViewerPage extends HookConsumerWidget {
() => isVisible.value = true, () => isVisible.value = true,
); );
if (isPlayingMotionVideo != null) {
isPlayingMotionVideo!.addListener(onToggleMotionVideo);
}
return () { return () {
timer?.cancel(); timer?.cancel();
if (isPlayingMotionVideo != null) {
isPlayingMotionVideo!.removeListener(onToggleMotionVideo);
}
final playerController = controller.value; final playerController = controller.value;
if (playerController == null) { if (playerController == null) {
return; return;
@ -430,7 +414,8 @@ class NativeVideoViewerPage extends HookConsumerWidget {
if (aspectRatio.value != null) if (aspectRatio.value != null)
Visibility.maintain( Visibility.maintain(
key: ValueKey(asset), key: ValueKey(asset),
visible: (asset.isVideo || showMotionVideo) && isVisible.value, visible:
(asset.isVideo || showMotionVideo.value) && isVisible.value,
child: Center( child: Center(
key: ValueKey(asset), key: ValueKey(asset),
child: AspectRatio( child: AspectRatio(

View file

@ -0,0 +1,23 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
/// Whether to display the video part of a motion photo
final isPlayingMotionVideoProvider =
StateNotifierProvider<IsPlayingMotionVideo, bool>((ref) {
return IsPlayingMotionVideo(ref);
});
class IsPlayingMotionVideo extends StateNotifier<bool> {
IsPlayingMotionVideo(this.ref) : super(false);
final Ref ref;
bool get playing => state;
set playing(bool value) {
state = value;
}
void toggle() {
state = !state;
}
}

View file

@ -1086,7 +1086,6 @@ class NativeVideoViewerRoute extends PageRouteInfo<NativeVideoViewerRouteArgs> {
Key? key, Key? key,
required Asset asset, required Asset asset,
required Widget placeholder, required Widget placeholder,
ValueNotifier<bool>? isPlayingMotionVideo,
bool showControls = true, bool showControls = true,
List<PageRouteInfo>? children, List<PageRouteInfo>? children,
}) : super( }) : super(
@ -1095,7 +1094,6 @@ class NativeVideoViewerRoute extends PageRouteInfo<NativeVideoViewerRouteArgs> {
key: key, key: key,
asset: asset, asset: asset,
placeholder: placeholder, placeholder: placeholder,
isPlayingMotionVideo: isPlayingMotionVideo,
showControls: showControls, showControls: showControls,
), ),
initialChildren: children, initialChildren: children,
@ -1111,7 +1109,6 @@ class NativeVideoViewerRoute extends PageRouteInfo<NativeVideoViewerRouteArgs> {
key: args.key, key: args.key,
asset: args.asset, asset: args.asset,
image: args.placeholder, image: args.placeholder,
isPlayingMotionVideo: args.isPlayingMotionVideo,
showControls: args.showControls, showControls: args.showControls,
); );
}, },
@ -1123,7 +1120,6 @@ class NativeVideoViewerRouteArgs {
this.key, this.key,
required this.asset, required this.asset,
required this.placeholder, required this.placeholder,
this.isPlayingMotionVideo,
this.showControls = true, this.showControls = true,
}); });
@ -1133,8 +1129,6 @@ class NativeVideoViewerRouteArgs {
final Widget placeholder; final Widget placeholder;
final ValueNotifier<bool>? isPlayingMotionVideo;
final bool showControls; final bool showControls;
@override @override

View file

@ -21,13 +21,8 @@ import 'package:immich_mobile/widgets/common/immich_toast.dart';
class GalleryAppBar extends ConsumerWidget { class GalleryAppBar extends ConsumerWidget {
final void Function() showInfo; final void Function() showInfo;
final ValueNotifier<bool> isPlayingMotionVideo;
const GalleryAppBar({ const GalleryAppBar({super.key, required this.showInfo});
super.key,
required this.showInfo,
required this.isPlayingMotionVideo,
});
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
@ -109,15 +104,12 @@ class GalleryAppBar extends ConsumerWidget {
child: TopControlAppBar( child: TopControlAppBar(
isOwner: isOwner, isOwner: isOwner,
isPartner: isPartner, isPartner: isPartner,
isPlayingMotionVideo: isPlayingMotionVideo.value,
asset: asset, asset: asset,
onMoreInfoPressed: showInfo, onMoreInfoPressed: showInfo,
onFavorite: toggleFavorite, onFavorite: toggleFavorite,
onRestorePressed: () => handleRestore(asset), onRestorePressed: () => handleRestore(asset),
onUploadPressed: asset.isLocal ? () => handleUpload(asset) : null, onUploadPressed: asset.isLocal ? () => handleUpload(asset) : null,
onDownloadPressed: asset.isLocal ? null : handleDownloadAsset, onDownloadPressed: asset.isLocal ? null : handleDownloadAsset,
onToggleMotionVideo: () =>
isPlayingMotionVideo.value = !isPlayingMotionVideo.value,
onAddToAlbumPressed: () => addToAlbum(asset), onAddToAlbumPressed: () => addToAlbum(asset),
onActivitiesPressed: handleActivities, onActivitiesPressed: handleActivities,
), ),

View file

@ -0,0 +1,22 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/immich_colors.dart';
import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart';
class MotionPhotoButton extends ConsumerWidget {
const MotionPhotoButton({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final isPlaying = ref.watch(isPlayingMotionVideoProvider);
return IconButton(
onPressed: () {
ref.read(isPlayingMotionVideoProvider.notifier).toggle();
},
icon: isPlaying
? const Icon(Icons.motion_photos_pause_outlined, color: grey200)
: const Icon(Icons.play_circle_outline_rounded, color: grey200),
);
}
}

View file

@ -5,6 +5,7 @@ import 'package:immich_mobile/providers/activity_statistics.provider.dart';
import 'package:immich_mobile/providers/album/current_album.provider.dart'; import 'package:immich_mobile/providers/album/current_album.provider.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/providers/asset.provider.dart'; import 'package:immich_mobile/providers/asset.provider.dart';
import 'package:immich_mobile/widgets/asset_viewer/motion_photo_button.dart';
class TopControlAppBar extends HookConsumerWidget { class TopControlAppBar extends HookConsumerWidget {
const TopControlAppBar({ const TopControlAppBar({
@ -14,8 +15,6 @@ class TopControlAppBar extends HookConsumerWidget {
required this.onDownloadPressed, required this.onDownloadPressed,
required this.onAddToAlbumPressed, required this.onAddToAlbumPressed,
required this.onRestorePressed, required this.onRestorePressed,
required this.onToggleMotionVideo,
required this.isPlayingMotionVideo,
required this.onFavorite, required this.onFavorite,
required this.onUploadPressed, required this.onUploadPressed,
required this.isOwner, required this.isOwner,
@ -27,12 +26,10 @@ class TopControlAppBar extends HookConsumerWidget {
final Function onMoreInfoPressed; final Function onMoreInfoPressed;
final VoidCallback? onUploadPressed; final VoidCallback? onUploadPressed;
final VoidCallback? onDownloadPressed; final VoidCallback? onDownloadPressed;
final VoidCallback onToggleMotionVideo;
final VoidCallback onAddToAlbumPressed; final VoidCallback onAddToAlbumPressed;
final VoidCallback onRestorePressed; final VoidCallback onRestorePressed;
final VoidCallback onActivitiesPressed; final VoidCallback onActivitiesPressed;
final Function(Asset) onFavorite; final Function(Asset) onFavorite;
final bool isPlayingMotionVideo;
final bool isOwner; final bool isOwner;
final bool isPartner; final bool isPartner;
@ -57,23 +54,6 @@ class TopControlAppBar extends HookConsumerWidget {
); );
} }
Widget buildLivePhotoButton() {
return IconButton(
onPressed: () {
onToggleMotionVideo();
},
icon: isPlayingMotionVideo
? Icon(
Icons.motion_photos_pause_outlined,
color: Colors.grey[200],
)
: Icon(
Icons.play_circle_outline_rounded,
color: Colors.grey[200],
),
);
}
Widget buildMoreInfoButton() { Widget buildMoreInfoButton() {
return IconButton( return IconButton(
onPressed: () { onPressed: () {
@ -175,13 +155,11 @@ class TopControlAppBar extends HookConsumerWidget {
foregroundColor: Colors.grey[100], foregroundColor: Colors.grey[100],
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
leading: buildBackButton(), leading: buildBackButton(),
actionsIconTheme: const IconThemeData( actionsIconTheme: const IconThemeData(size: iconSize),
size: iconSize,
),
shape: const Border(), shape: const Border(),
actions: [ actions: [
if (asset.isRemote && isOwner) buildFavoriteButton(a), if (asset.isRemote && isOwner) buildFavoriteButton(a),
if (asset.livePhotoVideoId != null) buildLivePhotoButton(), if (asset.livePhotoVideoId != null) const MotionPhotoButton(),
if (asset.isLocal && !asset.isRemote) buildUploadButton(), if (asset.isLocal && !asset.isRemote) buildUploadButton(),
if (asset.isRemote && !asset.isLocal && isOwner) buildDownloadButton(), if (asset.isRemote && !asset.isLocal && isOwner) buildDownloadButton(),
if (asset.isRemote && (isOwner || isPartner) && !asset.isTrashed) if (asset.isRemote && (isOwner || isPartner) && !asset.isTrashed)