1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2024-12-29 15:11:58 +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 whiteOpacity75 = Color.fromARGB((0.75 * 255) ~/ 1, 255, 255, 255);
const Color red400 = Color(0xFFEF5350);
const Color grey200 = Color(0xFFEEEEEE);
final Map<ImmichColorPreset, ImmichTheme> _themePresetsMap = {
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/asset_viewer/asset_stack.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/video_player_value_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) {
final totalAssets = useState(renderList.totalAssets);
final isZoomed = useState(false);
final isPlayingMotionVideo = useValueNotifier(false);
final stackIndex = useState(0);
final localPosition = useRef<Offset?>(null);
final currentIndex = useValueNotifier(initialIndex);
@ -192,7 +192,7 @@ class GalleryViewerPage extends HookConsumerWidget {
},
onLongPressStart: asset.isMotionPhoto
? (_, __, ___) {
isPlayingMotionVideo.value = true;
ref.read(isPlayingMotionVideoProvider.notifier).playing = true;
}
: null,
imageProvider: ImmichImage.imageProvider(asset: asset),
@ -226,7 +226,6 @@ class GalleryViewerPage extends HookConsumerWidget {
child: NativeVideoViewerPage(
key: key,
asset: asset,
isPlayingMotionVideo: isPlayingMotionVideo,
image: Image(
key: ValueKey(asset),
image: ImmichImage.imageProvider(
@ -245,7 +244,7 @@ class GalleryViewerPage extends HookConsumerWidget {
}
PhotoViewGalleryPageOptions buildAsset(BuildContext context, int index) {
isPlayingMotionVideo.value = false;
ref.read(isPlayingMotionVideoProvider.notifier).playing = false;
var newAsset = loadAsset(index);
final stackId = newAsset.stackId;
if (stackId != null && currentIndex.value == index) {
@ -278,7 +277,7 @@ class GalleryViewerPage extends HookConsumerWidget {
return;
}
if (asset.isImage && !isPlayingMotionVideo.value) {
if (asset.isImage && !ref.read(isPlayingMotionVideoProvider)) {
isZoomed.value = state != PhotoViewScaleState.initial;
ref.read(showControlsProvider.notifier).show =
!isZoomed.value;
@ -324,7 +323,6 @@ class GalleryViewerPage extends HookConsumerWidget {
currentIndex.value = value;
stackIndex.value = 0;
isPlayingMotionVideo.value = false;
ref.read(currentAssetProvider.notifier).set(newAsset);
if (newAsset.isVideo || newAsset.isMotionPhoto) {
@ -345,7 +343,6 @@ class GalleryViewerPage extends HookConsumerWidget {
child: GalleryAppBar(
key: const ValueKey('app-bar'),
showInfo: showInfo,
isPlayingMotionVideo: isPlayingMotionVideo,
),
),
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/providers/app_settings.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_value_provider.dart';
import 'package:immich_mobile/services/api.service.dart';
@ -26,15 +27,10 @@ class NativeVideoViewerPage extends HookConsumerWidget {
final bool showControls;
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({
super.key,
required this.asset,
required this.image,
this.isPlayingMotionVideo,
this.showControls = true,
});
@ -48,10 +44,7 @@ class NativeVideoViewerPage extends HookConsumerWidget {
final controller = useState<NativeVideoPlayerController?>(null);
final lastVideoPosition = useRef(-1);
final isBuffering = useRef(false);
useListenable(isPlayingMotionVideo);
final showMotionVideo =
isPlayingMotionVideo != null && isPlayingMotionVideo!.value;
final showMotionVideo = useState(false);
// 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.
@ -65,6 +58,25 @@ class NativeVideoViewerPage extends HookConsumerWidget {
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 {
if (!context.mounted) {
return null;
@ -227,9 +239,7 @@ class NativeVideoViewerPage extends HookConsumerWidget {
ref.read(videoPlaybackValueProvider.notifier).value = videoPlayback;
try {
if (asset.isVideo ||
isPlayingMotionVideo == null ||
isPlayingMotionVideo!.value) {
if (asset.isVideo || showMotionVideo.value) {
await videoController.play();
}
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) {
controller.onPlaybackPositionChanged
.removeListener(onPlaybackPositionChanged);
@ -397,16 +389,8 @@ class NativeVideoViewerPage extends HookConsumerWidget {
() => isVisible.value = true,
);
if (isPlayingMotionVideo != null) {
isPlayingMotionVideo!.addListener(onToggleMotionVideo);
}
return () {
timer?.cancel();
if (isPlayingMotionVideo != null) {
isPlayingMotionVideo!.removeListener(onToggleMotionVideo);
}
final playerController = controller.value;
if (playerController == null) {
return;
@ -430,7 +414,8 @@ class NativeVideoViewerPage extends HookConsumerWidget {
if (aspectRatio.value != null)
Visibility.maintain(
key: ValueKey(asset),
visible: (asset.isVideo || showMotionVideo) && isVisible.value,
visible:
(asset.isVideo || showMotionVideo.value) && isVisible.value,
child: Center(
key: ValueKey(asset),
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,
required Asset asset,
required Widget placeholder,
ValueNotifier<bool>? isPlayingMotionVideo,
bool showControls = true,
List<PageRouteInfo>? children,
}) : super(
@ -1095,7 +1094,6 @@ class NativeVideoViewerRoute extends PageRouteInfo<NativeVideoViewerRouteArgs> {
key: key,
asset: asset,
placeholder: placeholder,
isPlayingMotionVideo: isPlayingMotionVideo,
showControls: showControls,
),
initialChildren: children,
@ -1111,7 +1109,6 @@ class NativeVideoViewerRoute extends PageRouteInfo<NativeVideoViewerRouteArgs> {
key: args.key,
asset: args.asset,
image: args.placeholder,
isPlayingMotionVideo: args.isPlayingMotionVideo,
showControls: args.showControls,
);
},
@ -1123,7 +1120,6 @@ class NativeVideoViewerRouteArgs {
this.key,
required this.asset,
required this.placeholder,
this.isPlayingMotionVideo,
this.showControls = true,
});
@ -1133,8 +1129,6 @@ class NativeVideoViewerRouteArgs {
final Widget placeholder;
final ValueNotifier<bool>? isPlayingMotionVideo;
final bool showControls;
@override

View file

@ -21,13 +21,8 @@ import 'package:immich_mobile/widgets/common/immich_toast.dart';
class GalleryAppBar extends ConsumerWidget {
final void Function() showInfo;
final ValueNotifier<bool> isPlayingMotionVideo;
const GalleryAppBar({
super.key,
required this.showInfo,
required this.isPlayingMotionVideo,
});
const GalleryAppBar({super.key, required this.showInfo});
@override
Widget build(BuildContext context, WidgetRef ref) {
@ -109,15 +104,12 @@ class GalleryAppBar extends ConsumerWidget {
child: TopControlAppBar(
isOwner: isOwner,
isPartner: isPartner,
isPlayingMotionVideo: isPlayingMotionVideo.value,
asset: asset,
onMoreInfoPressed: showInfo,
onFavorite: toggleFavorite,
onRestorePressed: () => handleRestore(asset),
onUploadPressed: asset.isLocal ? () => handleUpload(asset) : null,
onDownloadPressed: asset.isLocal ? null : handleDownloadAsset,
onToggleMotionVideo: () =>
isPlayingMotionVideo.value = !isPlayingMotionVideo.value,
onAddToAlbumPressed: () => addToAlbum(asset),
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/entities/asset.entity.dart';
import 'package:immich_mobile/providers/asset.provider.dart';
import 'package:immich_mobile/widgets/asset_viewer/motion_photo_button.dart';
class TopControlAppBar extends HookConsumerWidget {
const TopControlAppBar({
@ -14,8 +15,6 @@ class TopControlAppBar extends HookConsumerWidget {
required this.onDownloadPressed,
required this.onAddToAlbumPressed,
required this.onRestorePressed,
required this.onToggleMotionVideo,
required this.isPlayingMotionVideo,
required this.onFavorite,
required this.onUploadPressed,
required this.isOwner,
@ -27,12 +26,10 @@ class TopControlAppBar extends HookConsumerWidget {
final Function onMoreInfoPressed;
final VoidCallback? onUploadPressed;
final VoidCallback? onDownloadPressed;
final VoidCallback onToggleMotionVideo;
final VoidCallback onAddToAlbumPressed;
final VoidCallback onRestorePressed;
final VoidCallback onActivitiesPressed;
final Function(Asset) onFavorite;
final bool isPlayingMotionVideo;
final bool isOwner;
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() {
return IconButton(
onPressed: () {
@ -175,13 +155,11 @@ class TopControlAppBar extends HookConsumerWidget {
foregroundColor: Colors.grey[100],
backgroundColor: Colors.transparent,
leading: buildBackButton(),
actionsIconTheme: const IconThemeData(
size: iconSize,
),
actionsIconTheme: const IconThemeData(size: iconSize),
shape: const Border(),
actions: [
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.isRemote && !asset.isLocal && isOwner) buildDownloadButton(),
if (asset.isRemote && (isOwner || isPartner) && !asset.isTrashed)