mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-10-09 02:54:14 +00:00
122 lines
3.6 KiB
Dart
122 lines
3.6 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:twonly/src/utils/misc.dart';
|
|
import 'package:video_player/video_player.dart';
|
|
|
|
class VideoPlayerHelper extends StatefulWidget {
|
|
const VideoPlayerHelper({
|
|
required this.controller,
|
|
this.onDoubleTap,
|
|
super.key,
|
|
});
|
|
|
|
final VideoPlayerController controller;
|
|
final VoidCallback? onDoubleTap;
|
|
|
|
@override
|
|
State<VideoPlayerHelper> createState() => _VideoPlayerHelperState();
|
|
}
|
|
|
|
class _VideoPlayerHelperState extends State<VideoPlayerHelper>
|
|
with SingleTickerProviderStateMixin {
|
|
late final AnimationController _iconAnim;
|
|
late final Animation<double> _opacity;
|
|
late final Animation<double> _scale;
|
|
|
|
bool _isPaused = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_iconAnim = AnimationController(
|
|
vsync: this,
|
|
duration: const Duration(milliseconds: 800),
|
|
);
|
|
|
|
// Opacity: flash in quickly, hold, fade out
|
|
_opacity = TweenSequence<double>([
|
|
TweenSequenceItem(tween: Tween(begin: 0, end: 1), weight: 15),
|
|
TweenSequenceItem(tween: ConstantTween(1), weight: 30),
|
|
TweenSequenceItem(tween: Tween(begin: 1, end: 0), weight: 55),
|
|
]).animate(_iconAnim);
|
|
|
|
// Scale: pop in slightly over-sized then settle
|
|
_scale = TweenSequence<double>([
|
|
TweenSequenceItem(tween: Tween(begin: 0.6, end: 1.15), weight: 20),
|
|
TweenSequenceItem(tween: Tween(begin: 1.15, end: 1), weight: 15),
|
|
TweenSequenceItem(tween: ConstantTween(1), weight: 65),
|
|
]).animate(CurvedAnimation(parent: _iconAnim, curve: Curves.easeOut));
|
|
|
|
widget.controller.addListener(_onControllerUpdate);
|
|
}
|
|
|
|
void _onControllerUpdate() {
|
|
final paused = !widget.controller.value.isPlaying;
|
|
if (paused != _isPaused && mounted) {
|
|
setState(() => _isPaused = paused);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
widget.controller.removeListener(_onControllerUpdate);
|
|
_iconAnim.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
void _togglePlayPause() {
|
|
if (widget.controller.value.isPlaying) {
|
|
widget.controller.pause();
|
|
} else {
|
|
widget.controller.play();
|
|
}
|
|
_iconAnim.forward(from: 0);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return GestureDetector(
|
|
onTap: _togglePlayPause,
|
|
onDoubleTap: widget.onDoubleTap,
|
|
child: Stack(
|
|
alignment: Alignment.center,
|
|
children: [
|
|
VideoPlayer(widget.controller),
|
|
AnimatedBuilder(
|
|
animation: _iconAnim,
|
|
builder: (context, _) {
|
|
// While paused and the flash has finished, show a dim persistent icon
|
|
final opacity = _iconAnim.isAnimating
|
|
? _opacity.value
|
|
: (_isPaused ? 0.1 : 0.0);
|
|
final scale = _iconAnim.isAnimating ? _scale.value : 1.0;
|
|
|
|
if (opacity == 0.0) return const SizedBox.shrink();
|
|
|
|
return Opacity(
|
|
opacity: opacity,
|
|
child: Transform.scale(
|
|
scale: scale,
|
|
child: Container(
|
|
width: 64,
|
|
height: 64,
|
|
decoration: BoxDecoration(
|
|
color: context.appColor(AppColor.mediaScrim),
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: Icon(
|
|
_isPaused
|
|
? Icons.pause_rounded
|
|
: Icons.play_arrow_rounded,
|
|
color: context.appColor(AppColor.mediaForeground),
|
|
size: 36,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|