// Author: fruit.com.cn part of 'player_overlay.dart'; /// 顶部按钮:直接压在画面上,不加底色,靠文字/图标阴影保证可读性。 class _TopButton extends StatelessWidget { const _TopButton({ super.key, required this.tooltip, required this.icon, required this.onPressed, this.iconSize = 22, }); final String tooltip; final IconData icon; final VoidCallback onPressed; final double iconSize; @override Widget build(BuildContext context) { return SizedBox.square( dimension: _topButtonSize, child: IconButton( tooltip: tooltip, padding: EdgeInsets.zero, style: IconButton.styleFrom( backgroundColor: AppColors.transparent, foregroundColor: AppColors.onPrimary, ), onPressed: onPressed, icon: Icon(icon, size: iconSize, shadows: _playerTextShadows), ), ); } } class _SpeedEntry extends StatelessWidget { const _SpeedEntry({super.key, required this.speed, required this.onTap}); final double speed; final VoidCallback onTap; @override Widget build(BuildContext context) { return Material( color: AppColors.transparent, borderRadius: BorderRadius.circular(999), child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(999), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 7), child: Row( mainAxisSize: MainAxisSize.min, children: [ const Icon( Icons.speed_rounded, color: AppColors.onPrimary, size: 15, shadows: _playerTextShadows, ), const SizedBox(width: 4), Text( '${speed}x', style: const TextStyle( color: AppColors.onPrimary, fontSize: 12, fontWeight: FontWeight.w700, shadows: _playerTextShadows, ), ), ], ), ), ), ); } } class _BottomIconButton extends StatelessWidget { const _BottomIconButton({ super.key, required this.tooltip, required this.icon, required this.onPressed, this.height = 40, }); final String tooltip; final IconData icon; final VoidCallback onPressed; /// 与左侧选集条等高,两个控件底部齐平。 final double height; @override Widget build(BuildContext context) { return SizedBox( width: 40, height: height, child: IconButton( tooltip: tooltip, padding: EdgeInsets.zero, style: IconButton.styleFrom( backgroundColor: AppColors.transparent, foregroundColor: AppColors.onPrimary, ), onPressed: onPressed, icon: Icon(icon, size: 24, shadows: _playerTextShadows), ), ); } } class _EpisodeSynopsis extends StatefulWidget { const _EpisodeSynopsis({ super.key, required this.l10n, required this.drama, required this.synopsis, required this.authorNote, }); final AppLocalizations l10n; final Drama drama; final String synopsis; final String authorNote; @override State<_EpisodeSynopsis> createState() => _EpisodeSynopsisState(); } class _EpisodeSynopsisState extends State<_EpisodeSynopsis> { String? _fullSynopsis; int _detailRequest = 0; bool _detailRequested = false; static const int _authorNoteLines = 2; static const double _authorNoteGap = 4; static const double _authorNoteFont = 10; String get _synopsis => _fullSynopsis ?? widget.synopsis; @override void didChangeDependencies() { super.didChangeDependencies(); if (_detailRequested || widget.drama.detailLoaded) return; _detailRequested = true; unawaited(_loadFullSynopsis()); } @override void didUpdateWidget(covariant _EpisodeSynopsis oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.drama.id == widget.drama.id && oldWidget.synopsis == widget.synopsis) { return; } _fullSynopsis = null; _detailRequested = !widget.drama.detailLoaded; if (_detailRequested) unawaited(_loadFullSynopsis()); } Future _loadFullSynopsis() async { if (widget.drama.detailLoaded) return; final request = ++_detailRequest; final CatalogController catalog; try { catalog = context.read(); } catch (_) { return; } try { final detail = await catalog.requireDetail(widget.drama); if (!mounted || request != _detailRequest) return; final full = localizedDramaSynopsis(widget.l10n, detail); if (full == widget.synopsis) return; setState(() => _fullSynopsis = full); } catch (_) { // 详情失败时继续用列表里的简介。 } } @override Widget build(BuildContext context) { return ExpandableSynopsis( key: ValueKey(widget.drama.id), keyPrefix: 'player', synopsis: _synopsis, expandedFooter: widget.authorNote.isEmpty ? null : _authorNote(), ); } Widget _authorNote() => Padding( padding: const EdgeInsets.only(top: _authorNoteGap), child: Text( widget.authorNote, key: const Key('player-author-note'), maxLines: _authorNoteLines, overflow: TextOverflow.ellipsis, style: const TextStyle( color: AppColors.playerMuted, fontSize: _authorNoteFont, shadows: _playerTextShadows, ), ), ); }