// Author: fruit.com.cn part of '../discover_view.dart'; class _HomeDramaGrid extends StatelessWidget { const _HomeDramaGrid({ required this.l10n, required this.categories, required this.dramas, required this.searching, required this.onOpen, }); final AppLocalizations l10n; final CategoryController categories; final List dramas; final bool searching; final ValueChanged onOpen; @override Widget build(BuildContext context) { if (categories.loading && categories.feed.sections.isEmpty) { return const SliverFillRemaining( hasScrollBody: false, child: Center(child: CircularProgressIndicator(strokeWidth: 2)), ); } if (categories.error != null) { return _fill( EmptyState( key: const Key('home-categories-error'), icon: Icons.cloud_off_rounded, title: l10n.categoriesError, subtitle: l10n.emptyHint, actionLabel: l10n.retry, onAction: categories.load, ), ); } if (dramas.isEmpty) { final missingCategory = categories.selectedSectionMissing; return _fill( EmptyState( key: const Key('home-all-dramas-empty'), icon: searching ? Icons.search_off_rounded : missingCategory ? Icons.grid_view_rounded : Icons.movie_filter_outlined, title: searching ? l10n.noSearchResults : missingCategory ? l10n.noCategoryDramas : l10n.noSearchResults, subtitle: l10n.emptyHint, ), ); } return SliverMainAxisGroup( slivers: [ _dramaGrid(dramas), const SliverToBoxAdapter(child: SizedBox(height: 24)), ], ); } Widget _dramaGrid(List items) => SliverPadding( key: const Key('home-all-dramas-section'), padding: const EdgeInsets.symmetric(horizontal: AppDimens.pageHorizontal), sliver: SliverGrid( key: const Key('home-all-dramas-grid'), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, mainAxisSpacing: 14, crossAxisSpacing: 12, childAspectRatio: 0.55, ), delegate: SliverChildBuilderDelegate( (context, index) => _HomeDramaCard( l10n: l10n, drama: items[index], onTap: () => onOpen(items[index]), ), childCount: items.length, ), ), ); Widget _fill(Widget child) { return SliverFillRemaining(hasScrollBody: false, child: child); } } class _HomeDramaCard extends StatelessWidget { const _HomeDramaCard({ required this.l10n, required this.drama, required this.onTap, }); final AppLocalizations l10n; final Drama drama; final VoidCallback onTap; @override Widget build(BuildContext context) { final genre = drama.genre.isEmpty ? drama.category : drama.genre; return InkWell( key: Key('home-drama-card-${drama.id}'), onTap: onTap, borderRadius: BorderRadius.circular(AppDimens.cardRadius), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ AspectRatio( key: Key('home-drama-cover-${drama.id}'), aspectRatio: 3 / 4.2, child: ClipRRect( borderRadius: BorderRadius.circular(AppDimens.cardRadius), child: Stack( fit: StackFit.expand, children: [ RemoteImage( drama.posterUrl, fit: BoxFit.cover, showFallbackIcon: true, ), const DecoratedBox( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Color(0x33000000), Color(0x00000000), Color(0xB3000000), ], stops: [0, 0.42, 1], ), ), ), Positioned( top: 8, left: 8, child: Wrap( spacing: 4, runSpacing: 4, children: [ if (drama.isHot) _badge(l10n.hot, AppColors.blue), if (drama.freeEpisodes > 0) _badge(l10n.free, AppColors.success), ], ), ), if (drama.viewCount > 0) Positioned( right: 8, bottom: 8, child: DecoratedBox( decoration: BoxDecoration( color: Colors.black.withValues(alpha: 0.42), borderRadius: BorderRadius.circular(7), ), child: Padding( padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 2, ), child: Text( _compactCount(drama.viewCount), key: Key('home-drama-views-${drama.id}'), style: const TextStyle( color: AppColors.cream, fontSize: 10, fontWeight: FontWeight.w600, ), ), ), ), ), ], ), ), ), const SizedBox(height: 8), Text( drama.title, key: Key('home-drama-title-${drama.id}'), maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle( color: AppColors.onSurface, fontSize: 13, fontWeight: FontWeight.w600, height: 1.2, ), ), const SizedBox(height: 3), Text( genre.isEmpty ? l10n.videoPending : genre, key: Key('home-drama-genre-${drama.id}'), maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( color: AppColors.subtle, fontSize: 11, fontWeight: FontWeight.w400, height: 1.2, ), ), ], ), ); } Widget _badge(String label, Color color) { return DecoratedBox( decoration: BoxDecoration( color: color, borderRadius: BorderRadius.circular(999), ), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 2), child: Text( label, style: const TextStyle( color: AppColors.onPrimary, fontSize: 9, fontWeight: FontWeight.w800, letterSpacing: 0.3, height: 1.1, ), ), ), ); } } String _compactCount(int value) { if (value >= 1000000) { final scaled = value / 1000000; return '${scaled.toStringAsFixed(scaled >= 10 ? 0 : 1)}M'; } if (value >= 1000) { final scaled = value / 1000; return '${scaled.toStringAsFixed(scaled >= 10 ? 0 : 1)}K'; } return '$value'; }