import 'dart:ui' show ImageFilter; import 'package:flutter/material.dart'; import 'package:forui/forui.dart'; import '../../../shared/widgets/app_loading_ring.dart'; import '../../../shared/widgets/smart_image.dart'; import '../../../shared/widgets/top_drag_area.dart'; import '../../../shared/widgets/window_controls.dart'; class PlayerLoadingBackdrop extends StatelessWidget { const PlayerLoadingBackdrop({super.key, this.backdropUrl}); final String? backdropUrl; @override Widget build(BuildContext context) { final url = backdropUrl; if (url == null || url.isEmpty) { return const Positioned.fill(child: ColoredBox(color: Colors.black)); } return Positioned.fill( child: Stack( fit: StackFit.expand, children: [ const ColoredBox(color: Colors.black), RepaintBoundary( child: Transform.scale( scale: 1.08, child: ImageFiltered( imageFilter: ImageFilter.blur(sigmaX: 24, sigmaY: 24), child: SmartImage( url: url, fit: BoxFit.cover, borderRadius: 0, fallbackIcon: null, memCacheWidth: 480, placeholder: const ColoredBox(color: Colors.black), ), ), ), ), const ColoredBox(color: Colors.black54), ], ), ); } } class PlayerLoadingIndicator extends StatelessWidget { const PlayerLoadingIndicator({super.key}); @override Widget build(BuildContext context) { return const Center(child: AppLoadingRing(size: 44, color: Colors.white)); } } class PlayerStatusTopBar extends StatelessWidget { const PlayerStatusTopBar({ super.key, required this.title, required this.onBack, required this.topInset, required this.leftInset, required this.showWindowControls, this.enableWindowDrag = false, }); final String title; final VoidCallback onBack; final double topInset; final double leftInset; final bool showWindowControls; final bool enableWindowDrag; @override Widget build(BuildContext context) { final trimmed = title.trim(); final shown = trimmed.isEmpty ? '播放器' : trimmed; final Widget bar = SafeArea( bottom: false, child: Row( children: [ IconButton( onPressed: onBack, icon: const Icon(Icons.arrow_back, color: Colors.white), tooltip: '返回', ), const SizedBox(width: 4), Expanded( child: Text( shown, style: const TextStyle(color: Colors.white, fontSize: 15), overflow: TextOverflow.ellipsis, ), ), if (showWindowControls) const WindowControls.forDarkSurface(), ], ), ); return Positioned( top: topInset, left: leftInset, right: 12, child: enableWindowDrag ? TopDragArea(child: bar) : bar, ); } } class PlayerErrorCard extends StatelessWidget { const PlayerErrorCard({ super.key, required this.message, required this.onBack, required this.onRetry, }); final String message; final VoidCallback onBack; final VoidCallback onRetry; @override Widget build(BuildContext context) { return Center( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 32), child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 480), child: Material( color: const Color(0xFF1C1F26), borderRadius: BorderRadius.circular(16), elevation: 8, shadowColor: Colors.black, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.fromLTRB(24, 20, 12, 0), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Container( width: 36, height: 36, decoration: BoxDecoration( color: const Color( 0xFFFFB020, ).withValues(alpha: 0.14), borderRadius: BorderRadius.circular(8), ), child: const Icon( Icons.error_outline, color: Color(0xFFFFC46B), size: 22, ), ), const SizedBox(width: 14), const Expanded( child: Text( '无法播放此媒体', style: TextStyle( color: Colors.white, fontSize: 17, fontWeight: FontWeight.w600, ), ), ), IconButton( onPressed: onBack, icon: const Icon(Icons.close, color: Colors.white70), tooltip: '关闭播放器', ), ], ), ), Padding( padding: const EdgeInsets.fromLTRB(24, 12, 24, 0), child: ConstrainedBox( constraints: const BoxConstraints(maxHeight: 140), child: SingleChildScrollView( child: SelectableText( message, style: const TextStyle( color: Colors.white70, fontSize: 13, height: 1.45, ), ), ), ), ), const FDivider( style: .delta( color: Colors.white12, padding: .value(EdgeInsets.only(top: 20)), ), ), Padding( padding: const EdgeInsets.fromLTRB(16, 8, 16, 12), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton.icon( onPressed: onBack, icon: const Icon(Icons.arrow_back, size: 18), label: const Text('返回详情'), style: TextButton.styleFrom( foregroundColor: Colors.white70, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(6), ), ), ), const SizedBox(width: 10), FilledButton.icon( onPressed: onRetry, icon: const Icon(Icons.refresh, size: 18), label: const Text('重试'), style: FilledButton.styleFrom( backgroundColor: Colors.white, foregroundColor: Colors.black, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(6), ), ), ), ], ), ), ], ), ), ), ), ); } }