1import 'package:flutter/material.dart'; 2import 'package:video_player/video_player.dart'; 3 4import '../constants/app_colors.dart'; 5 6/// Minimal video controls showing only a scrubber/progress bar 7/// 8/// Always visible at the bottom of the video, positioned above 9/// the Android navigation bar using SafeArea. 10class MinimalVideoControls extends StatefulWidget { 11 const MinimalVideoControls({ 12 required this.controller, 13 super.key, 14 }); 15 16 final VideoPlayerController controller; 17 18 @override 19 State<MinimalVideoControls> createState() => _MinimalVideoControlsState(); 20} 21 22class _MinimalVideoControlsState extends State<MinimalVideoControls> { 23 double _sliderValue = 0; 24 bool _isUserDragging = false; 25 26 @override 27 void initState() { 28 super.initState(); 29 widget.controller.addListener(_updateSlider); 30 } 31 32 @override 33 void dispose() { 34 widget.controller.removeListener(_updateSlider); 35 super.dispose(); 36 } 37 38 void _updateSlider() { 39 if (!_isUserDragging && mounted) { 40 final position = 41 widget.controller.value.position.inMilliseconds.toDouble(); 42 final duration = 43 widget.controller.value.duration.inMilliseconds.toDouble(); 44 45 if (duration > 0) { 46 setState(() { 47 _sliderValue = position / duration; 48 }); 49 } 50 } 51 } 52 53 void _onSliderChanged(double value) { 54 setState(() { 55 _sliderValue = value; 56 }); 57 } 58 59 void _onSliderChangeStart(double value) { 60 _isUserDragging = true; 61 } 62 63 void _onSliderChangeEnd(double value) { 64 _isUserDragging = false; 65 final duration = widget.controller.value.duration; 66 final position = duration * value; 67 widget.controller.seekTo(position); 68 } 69 70 String _formatDuration(Duration duration) { 71 final minutes = duration.inMinutes; 72 final seconds = duration.inSeconds % 60; 73 return '${minutes.toString().padLeft(1, '0')}:' 74 '${seconds.toString().padLeft(2, '0')}'; 75 } 76 77 @override 78 Widget build(BuildContext context) { 79 final position = widget.controller.value.position; 80 final duration = widget.controller.value.duration; 81 82 return Container( 83 padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), 84 decoration: BoxDecoration( 85 gradient: LinearGradient( 86 begin: Alignment.bottomCenter, 87 end: Alignment.topCenter, 88 colors: [ 89 Colors.black.withValues(alpha: 0.7), 90 Colors.black.withValues(alpha: 0), 91 ], 92 ), 93 ), 94 child: Column( 95 mainAxisSize: MainAxisSize.min, 96 children: [ 97 // Scrubber slider 98 SliderTheme( 99 data: SliderThemeData( 100 trackHeight: 3, 101 thumbShape: 102 const RoundSliderThumbShape(enabledThumbRadius: 6), 103 overlayShape: 104 const RoundSliderOverlayShape(overlayRadius: 12), 105 activeTrackColor: AppColors.primary, 106 inactiveTrackColor: Colors.white.withValues(alpha: 0.3), 107 thumbColor: AppColors.primary, 108 overlayColor: AppColors.primary.withValues(alpha: 0.3), 109 ), 110 child: Slider( 111 value: _sliderValue.clamp(0, 1.0), 112 onChanged: _onSliderChanged, 113 onChangeStart: _onSliderChangeStart, 114 onChangeEnd: _onSliderChangeEnd, 115 ), 116 ), 117 // Time labels 118 Padding( 119 padding: const EdgeInsets.symmetric(horizontal: 8), 120 child: Row( 121 mainAxisAlignment: MainAxisAlignment.spaceBetween, 122 children: [ 123 Text( 124 _formatDuration(position), 125 style: const TextStyle( 126 color: Colors.white, 127 fontSize: 12, 128 ), 129 ), 130 Text( 131 _formatDuration(duration), 132 style: TextStyle( 133 color: Colors.white.withValues(alpha: 0.7), 134 fontSize: 12, 135 ), 136 ), 137 ], 138 ), 139 ), 140 ], 141 ), 142 ); 143 } 144}