// Author: fruit.com.cn part of 'home_feed_overlay.dart'; const double _thumbSize = 6; const double _thumbActiveSize = 14; const double _trackActiveHeight = 6; const double _lineCenterFromBottom = 8; /// 整集条底部的进度线:20px 手势区内可点按/拖动 seek。 class _FeedProgressBar extends StatefulWidget { const _FeedProgressBar({ required this.controller, required this.height, required this.hitHeight, this.onScrubStart, this.onSeek, }); final ValueListenable? controller; final double height; final double hitHeight; final VoidCallback? onScrubStart; final ValueChanged? onSeek; @override State<_FeedProgressBar> createState() => _FeedProgressBarState(); } class _FeedProgressBarState extends State<_FeedProgressBar> { double? _preview; @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { final width = constraints.maxWidth; final source = widget.controller; final track = source == null ? _track(0, width) : ValueListenableBuilder( valueListenable: source, builder: (context, value, _) { final total = value.duration.inMilliseconds; final played = total <= 0 ? 0.0 : (value.position.inMilliseconds / total) .clamp(0.0, 1.0) .toDouble(); return _track(_preview ?? played, width); }, ); return GestureDetector( key: const Key('home-feed-progress'), behavior: HitTestBehavior.opaque, onHorizontalDragStart: (details) { widget.onScrubStart?.call(); _update(details.localPosition.dx / width); }, onHorizontalDragUpdate: (details) => _update(details.localPosition.dx / width), onHorizontalDragEnd: (_) => _commit(_preview), onHorizontalDragCancel: () => setState(() => _preview = null), child: track, ); }, ); } void _update(double fraction) { setState(() => _preview = fraction.clamp(0.0, 1.0).toDouble()); } void _commit(double? fraction) { if (fraction == null) return; final total = widget.controller?.value.duration.inMilliseconds ?? 0; if (total > 0) { widget.onSeek?.call( Duration(milliseconds: (total * fraction.clamp(0.0, 1.0)).round()), ); } setState(() => _preview = null); } Widget _track(double fraction, double width) { final dragging = _preview != null; final trackHeight = dragging ? _trackActiveHeight : widget.height; final size = dragging ? _thumbActiveSize : _thumbSize; final left = (width * fraction - size / 2) .clamp(0.0, width - size) .toDouble(); final lineBottom = _lineCenterFromBottom - trackHeight / 2; final thumbBottom = _lineCenterFromBottom - size / 2; return SizedBox( height: widget.hitHeight, child: Stack( clipBehavior: Clip.none, children: [ Positioned( left: 0, right: 0, bottom: lineBottom, child: Container( key: const Key('home-feed-progress-track'), height: trackHeight, color: Colors.white24, ), ), Positioned( left: 0, bottom: lineBottom, child: Container( width: width * fraction, height: trackHeight, color: AppColors.blue, ), ), Positioned( left: left, bottom: thumbBottom, child: Container( key: const Key('home-feed-progress-thumb'), width: size, height: size, decoration: const BoxDecoration( color: Colors.white, shape: BoxShape.circle, ), ), ), ], ), ); } }