import 'package:flutter/material.dart'; import '../models/models.dart'; import '../theme/app_theme.dart'; import '../theme/app_spacing.dart'; import 'shimmer_loading.dart'; class BookCard extends StatefulWidget { final Book book; final VoidCallback onTap; const BookCard({super.key, required this.book, required this.onTap}); @override State createState() => _BookCardState(); } class _BookCardState extends State { bool _isHovered = false; @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; final textTheme = Theme.of(context).textTheme; final disableAnimations = MediaQuery.of(context).disableAnimations; return GestureDetector( onTap: widget.onTap, child: MouseRegion( onEnter: (_) => setState(() => _isHovered = true), onExit: (_) => setState(() => _isHovered = false), child: AnimatedScale( duration: disableAnimations ? Duration.zero : const Duration(milliseconds: 200), scale: _isHovered ? 1.02 : 1.0, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ AspectRatio( aspectRatio: 2 / 3, child: Hero( tag: 'book-cover-${widget.book.id}', child: Container( decoration: BoxDecoration( color: colorScheme.surface, borderRadius: BorderRadius.circular( AppSpacing.radiusMedium, ), boxShadow: AppTheme.shadowMd, ), child: Stack( children: [ ClipRRect( borderRadius: BorderRadius.circular( AppSpacing.radiusMedium, ), child: widget.book.coverUrl != null ? Image.network( widget.book.coverUrl!, fit: BoxFit.cover, width: double.infinity, height: double.infinity, loadingBuilder: (context, child, loadingProgress) { if (loadingProgress == null) return child; return ShimmerLoading( borderRadius: AppSpacing.radiusMedium, height: double.infinity, ); }, errorBuilder: (_, e, st) => _placeholder(colorScheme), ) : _placeholder(colorScheme), ), if (widget.book.isFavorite) Positioned( top: AppSpacing.sm, right: AppSpacing.sm, child: Container( padding: const EdgeInsets.all(AppSpacing.xs), decoration: BoxDecoration( color: colorScheme.error, borderRadius: BorderRadius.circular( AppSpacing.radiusPill, ), boxShadow: AppTheme.shadowSm, ), child: const Icon( Icons.favorite, color: Colors.white, size: 18, ), ), ), if (widget.book.status == 'done') Positioned( top: AppSpacing.sm, left: AppSpacing.sm, child: Container( padding: const EdgeInsets.symmetric( horizontal: AppSpacing.sm, vertical: AppSpacing.xs, ), decoration: BoxDecoration( color: colorScheme.secondary, borderRadius: BorderRadius.circular( AppSpacing.radiusSmall, ), ), child: Text( 'DONE', style: textTheme.labelSmall?.copyWith( color: colorScheme.onSecondary, fontWeight: FontWeight.bold, ), ), ), ), if (widget.book.status == 'reading' && widget.book.progress != null) Positioned( bottom: 0, left: 0, right: 0, child: ClipRRect( borderRadius: const BorderRadius.only( bottomLeft: Radius.circular( AppSpacing.radiusMedium, ), bottomRight: Radius.circular( AppSpacing.radiusMedium, ), ), child: LinearProgressIndicator( value: widget.book.progress! / 100, minHeight: 4, backgroundColor: colorScheme.surfaceContainerHighest, valueColor: AlwaysStoppedAnimation( colorScheme.primary, ), ), ), ), ], ), ), ), ), const SizedBox(height: AppSpacing.sm), Text( widget.book.title, maxLines: 1, overflow: TextOverflow.ellipsis, style: textTheme.titleSmall, ), const SizedBox(height: AppSpacing.xs), Text( widget.book.author, maxLines: 1, overflow: TextOverflow.ellipsis, style: textTheme.bodySmall?.copyWith( color: colorScheme.onSurface.withValues(alpha: 0.7), ), ), ], ), ), ), ); } Widget _placeholder(ColorScheme colorScheme) { return Container( color: colorScheme.surfaceContainerHighest, child: Center( child: Icon( Icons.book, color: colorScheme.primary.withValues(alpha: 0.3), size: 48, ), ), ); } }