import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import '../file_viewer/media_player.dart'; import 'gallery_cache.dart'; import 'gallery_item.dart'; /// A video in the gallery: thumbnail and play button; the file is only /// downloaded on play. Leaving the page tears the player down. class GalleryVideoPage extends StatefulWidget { final GalleryItem item; final bool isActive; const GalleryVideoPage({ required this.item, required this.isActive, super.key, }); @override State createState() => _GalleryVideoPageState(); } class _GalleryVideoPageState extends State { String? _path; bool _playing = false; bool _failed = false; ValueListenable? _progress; @override void initState() { super.initState(); GalleryOriginals.instance.addListener(_onFlightsChanged); _progress = GalleryOriginals.instance.progressOf(widget.item); } @override void didUpdateWidget(covariant GalleryVideoPage oldWidget) { super.didUpdateWidget(oldWidget); if (!widget.isActive && _playing) _playing = false; } @override void dispose() { GalleryOriginals.instance.removeListener(_onFlightsChanged); super.dispose(); } void _onFlightsChanged() { final progress = GalleryOriginals.instance.progressOf(widget.item); if (progress != _progress && mounted) setState(() => _progress = progress); } Future _play() async { setState(() => _failed = false); try { final file = await GalleryOriginals.instance.cached(widget.item) ?? await GalleryOriginals.instance.load(widget.item); if (!mounted) return; setState(() { _path = file.path; _playing = widget.isActive; }); } on Object { if (mounted) setState(() => _failed = true); } } @override Widget build(BuildContext context) { final path = _path; if (_playing && path != null) { return MediaPlayer( key: ValueKey(path), path: path, isAudio: false, autoPlay: true, ); } final progress = _progress; return Stack( fit: StackFit.expand, children: [ Image( image: galleryPreviewProvider(context, widget.item), fit: BoxFit.contain, gaplessPlayback: true, errorBuilder: (_, _, _) => const Center(child: Icon(Icons.movie_outlined, size: 96)), ), Center( child: progress != null ? ValueListenableBuilder( valueListenable: progress, builder: (context, value, _) => _PlayCircle( child: CircularProgressIndicator( value: value, color: Colors.white, strokeWidth: 3, semanticsLabel: 'Video wird geladen', ), ), ) : _PlayCircle( onTap: () => unawaited(_play()), child: const Icon( Icons.play_arrow, color: Colors.white, size: 40, semanticLabel: 'Abspielen', ), ), ), if (_failed) Positioned( left: 24, right: 24, bottom: 32, child: SafeArea( child: Text( 'Video konnte nicht geladen werden. Tippe erneut auf Abspielen.', textAlign: TextAlign.center, style: Theme.of(context).textTheme.bodyMedium, ), ), ), ], ); } } class _PlayCircle extends StatelessWidget { final Widget child; final VoidCallback? onTap; const _PlayCircle({required this.child, this.onTap}); @override Widget build(BuildContext context) => Material( color: Colors.black54, shape: const CircleBorder(), clipBehavior: Clip.antiAlias, child: InkWell( onTap: onTap, child: SizedBox( width: 72, height: 72, child: Center(child: SizedBox(width: 40, height: 40, child: child)), ), ), ); }