mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-04 03:27:09 -05:00 
			
		
		
		
	Adds photo thumbnail to videos (#2880)
* Motion photos use placeholder image for more seamless loading * Fixes merge conflicts
This commit is contained in:
		
							parent
							
								
									48e4ea5231
								
							
						
					
					
						commit
						1b15b5414c
					
				@ -367,6 +367,26 @@ class GalleryViewerPage extends HookConsumerWidget {
 | 
				
			|||||||
      );
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ImageProvider imageProvider(Asset asset) {
 | 
				
			||||||
 | 
					      if (asset.isLocal) {
 | 
				
			||||||
 | 
					        return localImageProvider(asset);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        if (isLoadOriginal.value) {
 | 
				
			||||||
 | 
					          return originalImageProvider(asset);
 | 
				
			||||||
 | 
					        } else if (isLoadPreview.value) {
 | 
				
			||||||
 | 
					          return remoteThumbnailImageProvider(
 | 
				
			||||||
 | 
					            asset,
 | 
				
			||||||
 | 
					            api.ThumbnailFormat.JPEG,
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          return remoteThumbnailImageProvider(
 | 
				
			||||||
 | 
					            asset,
 | 
				
			||||||
 | 
					            api.ThumbnailFormat.WEBP,
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return Scaffold(
 | 
					    return Scaffold(
 | 
				
			||||||
      backgroundColor: Colors.black,
 | 
					      backgroundColor: Colors.black,
 | 
				
			||||||
      body: WillPopScope(
 | 
					      body: WillPopScope(
 | 
				
			||||||
@ -460,26 +480,9 @@ class GalleryViewerPage extends HookConsumerWidget {
 | 
				
			|||||||
                  : null,
 | 
					                  : null,
 | 
				
			||||||
              builder: (context, index) {
 | 
					              builder: (context, index) {
 | 
				
			||||||
                final asset = loadAsset(index);
 | 
					                final asset = loadAsset(index);
 | 
				
			||||||
 | 
					                final ImageProvider provider = imageProvider(asset);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (asset.isImage && !isPlayingMotionVideo.value) {
 | 
					                if (asset.isImage && !isPlayingMotionVideo.value) {
 | 
				
			||||||
                  // Show photo
 | 
					 | 
				
			||||||
                  final ImageProvider provider;
 | 
					 | 
				
			||||||
                  if (asset.isLocal) {
 | 
					 | 
				
			||||||
                    provider = localImageProvider(asset);
 | 
					 | 
				
			||||||
                  } else {
 | 
					 | 
				
			||||||
                    if (isLoadOriginal.value) {
 | 
					 | 
				
			||||||
                      provider = originalImageProvider(asset);
 | 
					 | 
				
			||||||
                    } else if (isLoadPreview.value) {
 | 
					 | 
				
			||||||
                      provider = remoteThumbnailImageProvider(
 | 
					 | 
				
			||||||
                        asset,
 | 
					 | 
				
			||||||
                        api.ThumbnailFormat.JPEG,
 | 
					 | 
				
			||||||
                      );
 | 
					 | 
				
			||||||
                    } else {
 | 
					 | 
				
			||||||
                      provider = remoteThumbnailImageProvider(
 | 
					 | 
				
			||||||
                        asset,
 | 
					 | 
				
			||||||
                        api.ThumbnailFormat.WEBP,
 | 
					 | 
				
			||||||
                      );
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                  }
 | 
					 | 
				
			||||||
                  return PhotoViewGalleryPageOptions(
 | 
					                  return PhotoViewGalleryPageOptions(
 | 
				
			||||||
                    onDragStart: (_, details, __) =>
 | 
					                    onDragStart: (_, details, __) =>
 | 
				
			||||||
                        localPosition = details.localPosition,
 | 
					                        localPosition = details.localPosition,
 | 
				
			||||||
@ -512,18 +515,23 @@ class GalleryViewerPage extends HookConsumerWidget {
 | 
				
			|||||||
                    maxScale: 1.0,
 | 
					                    maxScale: 1.0,
 | 
				
			||||||
                    minScale: 1.0,
 | 
					                    minScale: 1.0,
 | 
				
			||||||
                    basePosition: Alignment.bottomCenter,
 | 
					                    basePosition: Alignment.bottomCenter,
 | 
				
			||||||
                    child: SafeArea(
 | 
					                    child: VideoViewerPage(
 | 
				
			||||||
                      child: VideoViewerPage(
 | 
					                      onPlaying: () => isPlayingVideo.value = true,
 | 
				
			||||||
                        onPlaying: () => isPlayingVideo.value = true,
 | 
					                      onPaused: () => isPlayingVideo.value = false,
 | 
				
			||||||
                        onPaused: () => isPlayingVideo.value = false,
 | 
					                      asset: asset,
 | 
				
			||||||
                        asset: asset,
 | 
					                      isMotionVideo: isPlayingMotionVideo.value,
 | 
				
			||||||
                        isMotionVideo: isPlayingMotionVideo.value,
 | 
					                      placeholder: Image(
 | 
				
			||||||
                        onVideoEnded: () {
 | 
					                        image: provider,
 | 
				
			||||||
                          if (isPlayingMotionVideo.value) {
 | 
					                        fit: BoxFit.fitWidth,
 | 
				
			||||||
                            isPlayingMotionVideo.value = false;
 | 
					                        height: MediaQuery.of(context).size.height,
 | 
				
			||||||
                          }
 | 
					                        width: MediaQuery.of(context).size.width,
 | 
				
			||||||
                        },
 | 
					                        alignment: Alignment.center,
 | 
				
			||||||
                      ),
 | 
					                      ),
 | 
				
			||||||
 | 
					                      onVideoEnded: () {
 | 
				
			||||||
 | 
					                        if (isPlayingMotionVideo.value) {
 | 
				
			||||||
 | 
					                          isPlayingMotionVideo.value = false;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                      },
 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
                  );
 | 
					                  );
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
				
			|||||||
@ -15,6 +15,7 @@ import 'package:video_player/video_player.dart';
 | 
				
			|||||||
class VideoViewerPage extends HookConsumerWidget {
 | 
					class VideoViewerPage extends HookConsumerWidget {
 | 
				
			||||||
  final Asset asset;
 | 
					  final Asset asset;
 | 
				
			||||||
  final bool isMotionVideo;
 | 
					  final bool isMotionVideo;
 | 
				
			||||||
 | 
					  final Widget? placeholder;
 | 
				
			||||||
  final VoidCallback onVideoEnded;
 | 
					  final VoidCallback onVideoEnded;
 | 
				
			||||||
  final VoidCallback? onPlaying;
 | 
					  final VoidCallback? onPlaying;
 | 
				
			||||||
  final VoidCallback? onPaused;
 | 
					  final VoidCallback? onPaused;
 | 
				
			||||||
@ -26,6 +27,7 @@ class VideoViewerPage extends HookConsumerWidget {
 | 
				
			|||||||
    required this.onVideoEnded,
 | 
					    required this.onVideoEnded,
 | 
				
			||||||
    this.onPlaying,
 | 
					    this.onPlaying,
 | 
				
			||||||
    this.onPaused,
 | 
					    this.onPaused,
 | 
				
			||||||
 | 
					    this.placeholder,
 | 
				
			||||||
  }) : super(key: key);
 | 
					  }) : super(key: key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
@ -66,6 +68,7 @@ class VideoViewerPage extends HookConsumerWidget {
 | 
				
			|||||||
          onVideoEnded: onVideoEnded,
 | 
					          onVideoEnded: onVideoEnded,
 | 
				
			||||||
          onPaused: onPaused,
 | 
					          onPaused: onPaused,
 | 
				
			||||||
          onPlaying: onPlaying,
 | 
					          onPlaying: onPlaying,
 | 
				
			||||||
 | 
					          placeholder: placeholder,
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        if (downloadAssetStatus == DownloadAssetStatus.loading)
 | 
					        if (downloadAssetStatus == DownloadAssetStatus.loading)
 | 
				
			||||||
          const Center(
 | 
					          const Center(
 | 
				
			||||||
@ -95,6 +98,10 @@ class VideoPlayer extends StatefulWidget {
 | 
				
			|||||||
  final Function()? onPlaying;
 | 
					  final Function()? onPlaying;
 | 
				
			||||||
  final Function()? onPaused;
 | 
					  final Function()? onPaused;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// The placeholder to show while the video is loading
 | 
				
			||||||
 | 
					  /// usually, a thumbnail of the video
 | 
				
			||||||
 | 
					  final Widget? placeholder;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const VideoPlayer({
 | 
					  const VideoPlayer({
 | 
				
			||||||
    Key? key,
 | 
					    Key? key,
 | 
				
			||||||
    this.url,
 | 
					    this.url,
 | 
				
			||||||
@ -104,6 +111,7 @@ class VideoPlayer extends StatefulWidget {
 | 
				
			|||||||
    required this.isMotionVideo,
 | 
					    required this.isMotionVideo,
 | 
				
			||||||
    this.onPlaying,
 | 
					    this.onPlaying,
 | 
				
			||||||
    this.onPaused,
 | 
					    this.onPaused,
 | 
				
			||||||
 | 
					    this.placeholder,
 | 
				
			||||||
  }) : super(key: key);
 | 
					  }) : super(key: key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
@ -186,12 +194,18 @@ class _VideoPlayerState extends State<VideoPlayer> {
 | 
				
			|||||||
        ),
 | 
					        ),
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      return const Center(
 | 
					      return SizedBox(
 | 
				
			||||||
        child: SizedBox(
 | 
					        height: MediaQuery.of(context).size.height,
 | 
				
			||||||
          width: 75,
 | 
					        width: MediaQuery.of(context).size.width,
 | 
				
			||||||
          height: 75,
 | 
					        child: Center(
 | 
				
			||||||
          child: CircularProgressIndicator.adaptive(
 | 
					          child: Stack(
 | 
				
			||||||
            strokeWidth: 2,
 | 
					            children: [
 | 
				
			||||||
 | 
					              if (widget.placeholder != null)
 | 
				
			||||||
 | 
					                widget.placeholder!,
 | 
				
			||||||
 | 
					              const Center(
 | 
				
			||||||
 | 
					                child: ImmichLoadingIndicator(),
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user