// Author: fruit.com.cn part of '../discover_view.dart'; class _HomeDramaGrid extends StatelessWidget { const _HomeDramaGrid({ required this.l10n, required this.categories, required this.onOpen, }); final AppLocalizations l10n; final CategoryController categories; 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, ), ); } final dramas = categories.visibleDramas; if (dramas.isEmpty) { final missingCategory = categories.selectedSectionMissing; return _fill( EmptyState( key: const Key('home-all-dramas-empty'), icon: missingCategory ? Icons.grid_view_rounded : Icons.search_off_rounded, title: missingCategory ? l10n.noCategoryDramas : l10n.noSearchResults, subtitle: l10n.emptyHint, ), ); } return SliverMainAxisGroup( slivers: [ _dramaGrid(dramas), const SliverToBoxAdapter(child: SizedBox(height: 24)), ], ); } Widget _dramaGrid(List dramas) => SliverPadding( key: const Key('home-all-dramas-section'), padding: const EdgeInsets.symmetric(horizontal: 16), sliver: SliverGrid( key: const Key('home-all-dramas-grid'), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, mainAxisSpacing: 14, crossAxisSpacing: 10, childAspectRatio: 2 / 3, ), delegate: SliverChildBuilderDelegate( (context, index) => _HomeDramaCard( l10n: l10n, drama: dramas[index], onTap: () => onOpen(dramas[index]), ), childCount: dramas.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(14), child: AspectRatio( key: Key('home-drama-cover-${drama.id}'), aspectRatio: 2 / 3, child: ClipRRect( borderRadius: BorderRadius.circular(14), 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(0x00000000), Color(0xE6100C0A)], stops: [0.42, 1], ), ), ), if (drama.isHot) Positioned( top: 8, left: 8, child: DecoratedBox( decoration: BoxDecoration( color: AppColors.blue, borderRadius: BorderRadius.circular(6), ), child: Padding( padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 2, ), child: Text( l10n.hot, style: const TextStyle( color: AppColors.onPrimary, fontSize: 10, fontWeight: FontWeight.w800, height: 1.1, ), ), ), ), ), Positioned( left: 10, right: 10, bottom: 10, child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( drama.title, key: Key('home-drama-title-${drama.id}'), maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle( color: AppColors.cream, fontSize: 13, fontWeight: FontWeight.w800, height: 1.15, ), ), const SizedBox(height: 4), Text( genre.isEmpty ? l10n.videoPending : genre, key: Key('home-drama-genre-${drama.id}'), maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( color: Color(0xE6FFFFFF), fontSize: 11, fontWeight: FontWeight.w400, height: 1.2, ), ), ], ), ), ], ), ), ), ); } }