import 'package:flutter/material.dart'; import '../../../../core/theme/colors.dart'; import '../../../../domain/entities/track.dart'; /// Queue Track Tile Widget /// /// Displays a track in the queue with: /// - Track info (art, title, artist, duration) /// - Remove button /// - Drag handle /// - Visual indication for currently playing track class QueueTrackTile extends StatelessWidget { final Track track; final bool isPlaying; final int index; final VoidCallback? onTap; final VoidCallback? onRemove; final bool isDragging; const QueueTrackTile({ super.key, required this.track, this.isPlaying = false, required this.index, this.onTap, this.onRemove, this.isDragging = false, }); @override Widget build(BuildContext context) { return Container( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), decoration: BoxDecoration( color: isPlaying ? AppColors.cyan.withOpacity(0.1) : AppColors.surfaceVariant, borderRadius: BorderRadius.circular(12), border: Border.all( color: isPlaying ? AppColors.cyan.withOpacity(0.3) : AppColors.surfaceVariant, width: 1, ), boxShadow: isPlaying ? [ BoxShadow( color: AppColors.cyan.withOpacity(0.1), blurRadius: 10, spreadRadius: 1, ), ] : null, ), child: Material( color: Colors.transparent, child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(12), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), child: Row( children: [ // Drag handle _buildDragHandle(), const SizedBox(width: 8), // Track number or playing indicator _buildTrackIndicator(), const SizedBox(width: 12), // Album art _buildAlbumArt(), const SizedBox(width: 12), // Track info Expanded( child: _buildTrackInfo(), ), const SizedBox(width: 12), // Duration _buildDuration(), const SizedBox(width: 8), // Remove button if (onRemove != null) _buildRemoveButton(), ], ), ), ), ), ); } Widget _buildDragHandle() { return MouseRegion( cursor: SystemMouseCursors.grab, child: Container( padding: const EdgeInsets.symmetric(vertical: 8), child: Icon( Icons.drag_handle, color: AppColors.muted, size: 20, ), ), ); } Widget _buildTrackIndicator() { if (isPlaying) { return SizedBox( width: 20, child: _PlayingAnimation(), ); } return SizedBox( width: 20, child: Text( '${index + 1}', style: const TextStyle( color: AppColors.muted, fontSize: 14, fontWeight: FontWeight.w500, ), textAlign: TextAlign.center, ), ); } Widget _buildAlbumArt() { return Container( width: 48, height: 48, decoration: BoxDecoration( gradient: AppColors.accentGradient, borderRadius: BorderRadius.circular(8), boxShadow: isPlaying ? AppColors.violetGlow : null, ), child: track.imageUrl != null ? ClipRRect( borderRadius: BorderRadius.circular(8), child: Image.network( track.imageUrl!, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return const Icon( Icons.music_note, color: AppColors.onBackground, size: 24, ); }, ), ) : const Icon( Icons.music_note, color: AppColors.onBackground, size: 24, ), ); } Widget _buildTrackInfo() { return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( track.title, style: TextStyle( color: isPlaying ? AppColors.cyan : AppColors.onSurface, fontSize: 14, fontWeight: isPlaying ? FontWeight.w600 : FontWeight.w500, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 2), Text( track.artist?.name ?? 'Unknown Artist', style: const TextStyle( color: AppColors.muted, fontSize: 12, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ], ); } Widget _buildDuration() { return Text( track.formattedDuration, style: const TextStyle( color: AppColors.muted, fontSize: 12, fontWeight: FontWeight.w500, ), ); } Widget _buildRemoveButton() { return MouseRegion( cursor: SystemMouseCursors.click, child: GestureDetector( onTap: onRemove, child: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: AppColors.rouge.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: const Icon( Icons.close, color: AppColors.rouge, size: 18, ), ), ), ); } } /// Playing Animation Widget class _PlayingAnimation extends StatefulWidget { @override State<_PlayingAnimation> createState() => _PlayingAnimationState(); } class _PlayingAnimationState extends State<_PlayingAnimation> with SingleTickerProviderStateMixin { late AnimationController _controller; @override void initState() { super.initState(); _controller = AnimationController( duration: const Duration(milliseconds: 800), vsync: this, )..repeat(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: _controller, builder: (context, child) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: List.generate(3, (index) { final delay = index * 0.2; final animation = _controller .drive(CurveTween(curve: Curves.easeInOut)) .drive(Tween(begin: 0.3, end: 1.0)); return Transform.scale( scale: (animation.value - delay + 1) % 1 * 0.7 + 0.3, child: Container( width: 3, height: 12, margin: const EdgeInsets.symmetric(horizontal: 1), decoration: BoxDecoration( color: AppColors.cyan, borderRadius: BorderRadius.circular(2), ), ), ); }), ); }, ); } }