import 'package:flutter/material.dart'; import 'package:forui/forui.dart'; import '../../../shared/widgets/app_loading_ring.dart'; import '../../../shared/widgets/auto_dismiss_menu.dart'; class HeroIconActionButton extends StatelessWidget { final IconData icon; final double height; final VoidCallback onPressed; final String? label; const HeroIconActionButton({ super.key, required this.icon, required this.height, required this.onPressed, this.label, }); @override Widget build(BuildContext context) { if (label != null) { return GestureDetector( onTap: onPressed, behavior: HitTestBehavior.opaque, child: SizedBox( height: height, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(icon, size: 24, color: Colors.white), const SizedBox(height: 4), Text( label!, style: TextStyle( fontSize: 11, color: Colors.white.withValues(alpha: 0.7), ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ], ), ), ); } return SizedBox( height: height, child: OutlinedButton( onPressed: onPressed, style: OutlinedButton.styleFrom( foregroundColor: Colors.white, side: BorderSide(color: Colors.white.withValues(alpha: 0.4)), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), padding: EdgeInsets.zero, ), child: Icon(icon, size: height * 0.44), ), ); } } final _heroPillBorder = StadiumBorder( side: BorderSide(color: Colors.white.withValues(alpha: 0.55)), ); const _heroDropdownMenuMaxHeight = 320.0; class HeroPillDropdownButton extends StatelessWidget { final IconData icon; final List menuChildren; final double height; const HeroPillDropdownButton({ super.key, required this.icon, required this.menuChildren, this.height = 52, }); @override Widget build(BuildContext context) { return FPopoverMenu( menuBuilder: autoDismissMenuBuilder, menuAnchor: Alignment.topCenter, childAnchor: Alignment.bottomCenter, maxHeight: _heroDropdownMenuMaxHeight, menu: [FItemGroup(children: menuChildren)], builder: (context, controller, child) => GestureDetector( onTap: controller.toggle, child: child, ), child: Material( color: Colors.transparent, shape: _heroPillBorder, clipBehavior: Clip.antiAlias, child: SizedBox( height: height, child: Padding( padding: const EdgeInsets.symmetric(vertical: 12), child: Icon(icon, size: 22, color: Colors.white), ), ), ), ); } } class DetailHeroActions extends StatelessWidget { final bool showReplay; final VoidCallback? onReplay; final bool isPlayed; final VoidCallback onTogglePlayed; final bool isFavorite; final VoidCallback onToggleFavorite; final List extraIconActions; final VoidCallback? onPlay; final String playLabel; final double? progressPercent; final String? trailingLabel; final bool isPlayLoading; final bool compact; const DetailHeroActions({ super.key, this.showReplay = false, this.onReplay, required this.isPlayed, required this.onTogglePlayed, required this.isFavorite, required this.onToggleFavorite, this.extraIconActions = const [], this.onPlay, this.playLabel = '播放', this.progressPercent, this.trailingLabel, this.isPlayLoading = false, required this.compact, }); @override Widget build(BuildContext context) { final btnH = compact ? 44.0 : 52.0; final iconRow = Row( children: [ if (showReplay) ...[ Expanded( child: HeroIconActionButton( icon: Icons.replay, height: btnH, label: compact ? '重播' : null, onPressed: onReplay ?? () {}, ), ), SizedBox(width: compact ? 0 : 10), ], Expanded( child: HeroIconActionButton( icon: isPlayed ? Icons.check_circle : Icons.check_circle_outline, height: btnH, label: compact ? '已看' : null, onPressed: onTogglePlayed, ), ), SizedBox(width: compact ? 0 : 10), Expanded( child: HeroIconActionButton( icon: isFavorite ? Icons.favorite : Icons.favorite_border, height: btnH, label: compact ? '收藏' : null, onPressed: onToggleFavorite, ), ), for (final extra in extraIconActions) ...[ SizedBox(width: compact ? 0 : 10), Expanded(child: extra), ], ], ); final playButton = onPlay != null ? HeroPlayButton( height: btnH, label: playLabel, progressPercent: progressPercent, trailingLabel: trailingLabel, isLoading: isPlayLoading, compact: compact, onPressed: onPlay, ) : null; return ConstrainedBox( constraints: BoxConstraints(maxWidth: compact ? double.infinity : 360), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: compact ? [ if (playButton != null) ...[ playButton, const SizedBox(height: 16), ], iconRow, ] : [ iconRow, if (playButton != null) ...[ const SizedBox(height: 12), playButton, ], ], ), ); } } class HeroPlayButton extends StatelessWidget { final double height; final String label; final IconData icon; final double? progressPercent; final String? trailingLabel; final bool isLoading; final String loadingLabel; final bool compact; final VoidCallback? onPressed; const HeroPlayButton({ super.key, required this.height, required this.label, this.icon = Icons.play_arrow, this.progressPercent, this.trailingLabel, this.isLoading = false, this.loadingLabel = '启动中', this.compact = false, required this.onPressed, }); @override Widget build(BuildContext context) { final radius = BorderRadius.circular(12); final hasProgress = progressPercent != null && progressPercent! > 0; final buttonChild = Row( mainAxisAlignment: MainAxisAlignment.center, children: [ isLoading ? const AppLoadingRing(size: 18, color: Colors.black54) : Icon(icon, size: compact ? 22 : 26), const SizedBox(width: 6), Text( isLoading ? loadingLabel : label, style: TextStyle( fontSize: compact ? 15 : 16, fontWeight: FontWeight.w600, ), ), if (trailingLabel != null && !isLoading) ...[ const SizedBox(width: 10), Text( trailingLabel!, style: TextStyle( fontSize: compact ? 13 : 14, color: Colors.black.withValues(alpha: 0.5), ), ), ], ], ); if (hasProgress) { final fraction = (progressPercent! / 100).clamp(0.0, 1.0); return ClipRRect( borderRadius: radius, child: SizedBox( height: height, child: Stack( children: [ Positioned.fill( child: DecoratedBox( decoration: BoxDecoration( gradient: LinearGradient( colors: const [Color(0xFFE3E3E3), Colors.white], stops: [fraction, fraction], ), ), ), ), Positioned.fill( child: FilledButton( onPressed: isLoading ? null : onPressed, style: FilledButton.styleFrom( backgroundColor: Colors.transparent, foregroundColor: Colors.black, overlayColor: Colors.black.withValues(alpha: 0.08), shadowColor: Colors.transparent, elevation: 0, shape: RoundedRectangleBorder(borderRadius: radius), ), child: buttonChild, ), ), ], ), ), ); } return SizedBox( height: height, child: FilledButton( onPressed: isLoading ? null : onPressed, style: FilledButton.styleFrom( backgroundColor: Colors.white, foregroundColor: Colors.black, shape: RoundedRectangleBorder(borderRadius: radius), ), child: buttonChild, ), ); } }