// 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, }); final String tooltip; final IconData icon; final VoidCallback onPressed; @override Widget build(BuildContext context) { return SizedBox.square( dimension: _topButtonSize, child: IconButton( tooltip: tooltip, padding: EdgeInsets.zero, style: IconButton.styleFrom( backgroundColor: const Color(0x80000000), foregroundColor: AppColors.onPrimary, ), onPressed: onPressed, icon: Icon(icon, size: 18), ), ); } } 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: const Color(0x80000000), 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, }); final String tooltip; final IconData icon; final VoidCallback onPressed; @override Widget build(BuildContext context) { return SizedBox.square( dimension: 40, child: IconButton( tooltip: tooltip, padding: EdgeInsets.zero, style: IconButton.styleFrom( backgroundColor: AppColors.playerOverlayPill, foregroundColor: AppColors.onPrimary, ), onPressed: onPressed, icon: Icon(icon, size: 22), ), ); } } class _Rail extends StatelessWidget { const _Rail({ required this.l10n, required this.liked, required this.saved, required this.likeCount, required this.favoriteCount, required this.commentCount, required this.onLike, required this.onComments, required this.onSave, required this.onShare, }); final AppLocalizations l10n; final bool liked; final bool saved; final int? likeCount; final int? favoriteCount; final int? commentCount; final VoidCallback onLike; final VoidCallback onComments; final VoidCallback onSave; final VoidCallback onShare; @override Widget build(BuildContext context) { return Column( key: const Key('player-interaction-rail'), children: [ _RailButton( key: const Key('player-rail-save'), icon: saved ? Icons.star_rounded : Icons.star_border_rounded, label: favoriteCount == null ? l10n.favorite : formatInteractionCount(favoriteCount!), active: saved, onTap: onSave, ), const SizedBox(height: _railGap), _RailButton( key: const Key('player-rail-comments'), icon: Icons.chat_bubble_outline_rounded, label: commentCount == null ? l10n.comments : formatInteractionCount(commentCount!), onTap: onComments, ), const SizedBox(height: _railGap), _RailButton( key: const Key('player-rail-like'), icon: liked ? Icons.favorite_rounded : Icons.favorite_border_rounded, label: likeCount == null ? l10n.like : formatInteractionCount(likeCount!), active: liked, onTap: onLike, ), const SizedBox(height: _railGap), _RailButton( key: const Key('player-rail-share'), icon: Icons.ios_share_rounded, label: l10n.share, onTap: onShare, ), ], ); } } class _RailButton extends StatelessWidget { const _RailButton({ super.key, required this.icon, required this.label, required this.onTap, this.active = false, }); final IconData icon; final String label; final VoidCallback onTap; final bool active; @override Widget build(BuildContext context) { return Column( children: [ SizedBox.square( dimension: _railButtonSize, child: IconButton( onPressed: onTap, padding: EdgeInsets.zero, style: IconButton.styleFrom( backgroundColor: const Color(0x80000000), foregroundColor: active ? AppColors.blue : AppColors.onPrimary, ), icon: Icon(icon, size: 24), ), ), Text( label, maxLines: 1, style: const TextStyle( color: AppColors.onPrimary, fontSize: 11, height: 1, fontWeight: FontWeight.w600, ), ), ], ); } } class _EpisodeSynopsis extends StatefulWidget { const _EpisodeSynopsis({ super.key, required this.l10n, required this.episodeNumber, required this.synopsis, required this.authorNote, }); final AppLocalizations l10n; final int episodeNumber; final String synopsis; final String authorNote; @override State<_EpisodeSynopsis> createState() => _EpisodeSynopsisState(); } class _EpisodeSynopsisState extends State<_EpisodeSynopsis> { bool _expanded = false; static const TextStyle _labelStyle = TextStyle( color: AppColors.onPrimary, fontSize: 12, fontWeight: FontWeight.w700, shadows: _playerTextShadows, ); static const TextStyle _synopsisStyle = TextStyle( color: AppColors.playerMuted, fontSize: 12, height: 1.35, shadows: _playerTextShadows, ); @override Widget build(BuildContext context) { return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.baseline, textBaseline: TextBaseline.alphabetic, children: [ Text( widget.l10n.episodeLabel(widget.episodeNumber), key: const Key('player-episode-label'), maxLines: 1, overflow: TextOverflow.ellipsis, style: _labelStyle, ), const Padding( padding: EdgeInsets.symmetric(horizontal: 6), child: Text( '|', maxLines: 1, style: TextStyle( color: Colors.white54, fontSize: 12, shadows: _playerTextShadows, ), ), ), Flexible( child: Text( widget.synopsis, key: const Key('player-synopsis'), maxLines: _expanded ? null : 1, overflow: _expanded ? TextOverflow.clip : TextOverflow.ellipsis, style: _synopsisStyle, ), ), if (!_expanded) _toggle(widget.l10n.expandSynopsis), ], ), if (_expanded) ...[ if (widget.authorNote.isNotEmpty) ...[ const SizedBox(height: 4), Text( widget.authorNote, key: const Key('player-author-note'), maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle( color: AppColors.playerMuted, fontSize: 10, shadows: _playerTextShadows, ), ), ], _toggle(widget.l10n.showLess), ], ], ); } Widget _toggle(String label) { return InkWell( key: const Key('player-synopsis-toggle'), onTap: () => setState(() => _expanded = !_expanded), borderRadius: BorderRadius.circular(4), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1), child: Text( label, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( color: AppColors.blue, fontSize: 12, fontWeight: FontWeight.w700, shadows: _playerTextShadows, ), ), ), ); } }