mirror of
https://github.com/immich-app/immich.git
synced 2024-12-28 22:51:59 +00:00
Videos play in memories now
This commit is contained in:
parent
b0ff859cd6
commit
f14295e107
3 changed files with 108 additions and 55 deletions
|
@ -21,40 +21,45 @@ class VideoViewerPage extends HookConsumerWidget {
|
|||
final Asset asset;
|
||||
final bool isMotionVideo;
|
||||
final Widget? placeholder;
|
||||
final VoidCallback onVideoEnded;
|
||||
final VoidCallback? onVideoEnded;
|
||||
final VoidCallback? onPlaying;
|
||||
final VoidCallback? onPaused;
|
||||
final Duration hideControlsTimer;
|
||||
final bool showControls;
|
||||
final bool showDownloadingIndicator;
|
||||
|
||||
const VideoViewerPage({
|
||||
super.key,
|
||||
required this.asset,
|
||||
required this.isMotionVideo,
|
||||
required this.onVideoEnded,
|
||||
this.isMotionVideo = false,
|
||||
this.onVideoEnded,
|
||||
this.onPlaying,
|
||||
this.onPaused,
|
||||
this.placeholder,
|
||||
this.showControls = true,
|
||||
this.hideControlsTimer = const Duration(seconds: 5),
|
||||
this.showDownloadingIndicator = true,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
if (asset.isLocal && asset.livePhotoVideoId == null) {
|
||||
final AsyncValue<File> videoFile = ref.watch(_fileFamily(asset.local!));
|
||||
return videoFile.when(
|
||||
data: (data) => VideoPlayer(
|
||||
file: data,
|
||||
isMotionVideo: false,
|
||||
onVideoEnded: () {},
|
||||
),
|
||||
error: (error, stackTrace) => Icon(
|
||||
Icons.image_not_supported_outlined,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
loading: () => const Center(
|
||||
child: SizedBox(
|
||||
width: 75,
|
||||
height: 75,
|
||||
child: CircularProgressIndicator.adaptive(),
|
||||
return AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: videoFile.when(
|
||||
data: (data) => VideoPlayer(
|
||||
file: data,
|
||||
isMotionVideo: false,
|
||||
onVideoEnded: () {},
|
||||
),
|
||||
error: (error, stackTrace) => Icon(
|
||||
Icons.image_not_supported_outlined,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
loading: () => showDownloadingIndicator
|
||||
? const Center(child: ImmichLoadingIndicator())
|
||||
: Container(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -74,15 +79,24 @@ class VideoViewerPage extends HookConsumerWidget {
|
|||
onPaused: onPaused,
|
||||
onPlaying: onPlaying,
|
||||
placeholder: placeholder,
|
||||
hideControlsTimer: hideControlsTimer,
|
||||
showControls: showControls,
|
||||
showDownloadingIndicator: showDownloadingIndicator,
|
||||
),
|
||||
if (downloadAssetStatus == DownloadAssetStatus.loading)
|
||||
SizedBox(
|
||||
AnimatedOpacity(
|
||||
duration: const Duration(milliseconds: 400),
|
||||
opacity: (downloadAssetStatus == DownloadAssetStatus.loading &&
|
||||
showDownloadingIndicator)
|
||||
? 1.0
|
||||
: 0.0,
|
||||
child: SizedBox(
|
||||
height: context.height,
|
||||
width: context.width,
|
||||
child: const Center(
|
||||
child: ImmichLoadingIndicator(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -102,7 +116,9 @@ class VideoPlayer extends StatefulWidget {
|
|||
final String? jwtToken;
|
||||
final File? file;
|
||||
final bool isMotionVideo;
|
||||
final VoidCallback onVideoEnded;
|
||||
final VoidCallback? onVideoEnded;
|
||||
final Duration hideControlsTimer;
|
||||
final bool showControls;
|
||||
|
||||
final Function()? onPlaying;
|
||||
final Function()? onPaused;
|
||||
|
@ -111,16 +127,23 @@ class VideoPlayer extends StatefulWidget {
|
|||
/// usually, a thumbnail of the video
|
||||
final Widget? placeholder;
|
||||
|
||||
final bool showDownloadingIndicator;
|
||||
|
||||
const VideoPlayer({
|
||||
super.key,
|
||||
this.url,
|
||||
this.jwtToken,
|
||||
this.file,
|
||||
required this.onVideoEnded,
|
||||
this.onVideoEnded,
|
||||
required this.isMotionVideo,
|
||||
this.onPlaying,
|
||||
this.onPaused,
|
||||
this.placeholder,
|
||||
this.hideControlsTimer = const Duration(
|
||||
seconds: 5,
|
||||
),
|
||||
this.showControls = true,
|
||||
this.showDownloadingIndicator = true,
|
||||
});
|
||||
|
||||
@override
|
||||
|
@ -149,7 +172,7 @@ class _VideoPlayerState extends State<VideoPlayer> {
|
|||
if (videoPlayerController.value.position ==
|
||||
videoPlayerController.value.duration) {
|
||||
WakelockPlus.disable();
|
||||
widget.onVideoEnded();
|
||||
widget.onVideoEnded?.call();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -184,9 +207,9 @@ class _VideoPlayerState extends State<VideoPlayer> {
|
|||
autoInitialize: true,
|
||||
allowFullScreen: false,
|
||||
allowedScreenSleep: false,
|
||||
showControls: !widget.isMotionVideo,
|
||||
showControls: widget.showControls && !widget.isMotionVideo,
|
||||
customControls: const VideoPlayerControls(),
|
||||
hideControlsTimer: const Duration(seconds: 5),
|
||||
hideControlsTimer: widget.hideControlsTimer,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -214,9 +237,10 @@ class _VideoPlayerState extends State<VideoPlayer> {
|
|||
child: Stack(
|
||||
children: [
|
||||
if (widget.placeholder != null) widget.placeholder!,
|
||||
const Center(
|
||||
child: ImmichLoadingIndicator(),
|
||||
),
|
||||
if (widget.showDownloadingIndicator)
|
||||
const Center(
|
||||
child: ImmichLoadingIndicator(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:ui';
|
|||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/views/video_viewer_page.dart';
|
||||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/models/store.dart';
|
||||
import 'package:immich_mobile/shared/ui/immich_image.dart';
|
||||
|
@ -11,15 +12,15 @@ import 'package:openapi/api.dart';
|
|||
|
||||
class MemoryCard extends StatelessWidget {
|
||||
final Asset asset;
|
||||
final void Function() onTap;
|
||||
final String title;
|
||||
final bool showTitle;
|
||||
final Function()? onVideoEnded;
|
||||
|
||||
const MemoryCard({
|
||||
required this.asset,
|
||||
required this.onTap,
|
||||
required this.title,
|
||||
required this.showTitle,
|
||||
this.onVideoEnded,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
@ -59,24 +60,23 @@ class MemoryCard extends StatelessWidget {
|
|||
child: Container(color: Colors.black.withOpacity(0.2)),
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: onTap,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
// Determine the fit using the aspect ratio
|
||||
BoxFit fit = BoxFit.fitWidth;
|
||||
if (asset.width != null && asset.height != null) {
|
||||
final aspectRatio = asset.width! / asset.height!;
|
||||
final phoneAspectRatio =
|
||||
constraints.maxWidth / constraints.maxHeight;
|
||||
// Look for a 25% difference in either direction
|
||||
if (phoneAspectRatio * .75 < aspectRatio &&
|
||||
phoneAspectRatio * 1.25 > aspectRatio) {
|
||||
// Cover to look nice if we have nearly the same aspect ratio
|
||||
fit = BoxFit.cover;
|
||||
}
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
// Determine the fit using the aspect ratio
|
||||
BoxFit fit = BoxFit.fitWidth;
|
||||
if (asset.width != null && asset.height != null) {
|
||||
final aspectRatio = asset.height! / asset.width!;
|
||||
final phoneAspectRatio =
|
||||
constraints.maxWidth / constraints.maxHeight;
|
||||
// Look for a 25% difference in either direction
|
||||
if (phoneAspectRatio * .75 < aspectRatio &&
|
||||
phoneAspectRatio * 1.25 > aspectRatio) {
|
||||
// Cover to look nice if we have nearly the same aspect ratio
|
||||
fit = BoxFit.cover;
|
||||
}
|
||||
}
|
||||
|
||||
if (asset.isImage) {
|
||||
return Hero(
|
||||
tag: 'memory-${asset.id}',
|
||||
child: ImmichImage(
|
||||
|
@ -88,8 +88,25 @@ class MemoryCard extends StatelessWidget {
|
|||
preferredLocalAssetSize: 2048,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
} else {
|
||||
return Hero(
|
||||
tag: 'memory-${asset.id}',
|
||||
child: VideoViewerPage(
|
||||
asset: asset,
|
||||
showDownloadingIndicator: false,
|
||||
placeholder: ImmichImage(
|
||||
asset,
|
||||
fit: fit,
|
||||
type: ThumbnailFormat.JPEG,
|
||||
preferredLocalAssetSize: 2048,
|
||||
),
|
||||
hideControlsTimer: const Duration(seconds: 2),
|
||||
onVideoEnded: onVideoEnded,
|
||||
showControls: false,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
if (showTitle)
|
||||
Positioned(
|
||||
|
|
|
@ -245,13 +245,25 @@ class MemoryPage extends HookConsumerWidget {
|
|||
itemCount: memories[mIndex].assets.length,
|
||||
itemBuilder: (context, index) {
|
||||
final asset = memories[mIndex].assets[index];
|
||||
return Container(
|
||||
color: Colors.black,
|
||||
child: MemoryCard(
|
||||
asset: asset,
|
||||
onTap: () => toNextAsset(index),
|
||||
title: memories[mIndex].title,
|
||||
showTitle: index == 0,
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () {
|
||||
toNextAsset(index);
|
||||
},
|
||||
child: Container(
|
||||
color: Colors.black,
|
||||
child: MemoryCard(
|
||||
asset: asset,
|
||||
title: memories[mIndex].title,
|
||||
showTitle: index == 0,
|
||||
onVideoEnded: () {
|
||||
// If this is a live photo, don't go to
|
||||
// next asset
|
||||
if (asset.livePhotoVideoId == null) {
|
||||
toNextAsset(index);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue