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:
parent
20b1572b8e
commit
128f19efa5
8 changed files with 78 additions and 86 deletions
|
@ -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(
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
|
|
22
mobile/lib/widgets/asset_viewer/motion_photo_button.dart
Normal file
22
mobile/lib/widgets/asset_viewer/motion_photo_button.dart
Normal 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),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue