// 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) { return _fill( EmptyState( key: const Key('home-all-dramas-empty'), icon: searching ? Icons.search_off_rounded : Icons.movie_filter_outlined, title: searching ? l10n.noSearchResults : categories.selectedSectionMissing ? l10n.noCategoryDramas : l10n.noSearchResults, subtitle: l10n.emptyHint, ), ); } return SliverMainAxisGroup( slivers: [ SliverToBoxAdapter(child: _masonry(dramas)), const SliverToBoxAdapter(child: SizedBox(height: 24)), ], ); } Widget _masonry(List items) { return Padding( key: const Key('home-all-dramas-section'), padding: const EdgeInsets.symmetric(horizontal: 14), child: Row( key: const Key('home-all-dramas-grid'), crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded(child: _column(items, 0)), const SizedBox(width: 10), Expanded(child: _column(items, 1)), ], ), ); } Widget _column(List items, int start) { final children = []; for (var index = start; index < items.length; index += 2) { children.add( _HomeDramaCard( l10n: l10n, drama: items[index], onTap: () => onOpen(items[index]), ), ); if (index + 2 < items.length) { children.add(const SizedBox(height: 10)); } } return Column(children: children); } Widget _fill(Widget child) => 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; final free = drama.freeEpisodes > 0 || drama.episodes.any((episode) => episode.isFree); return InkWell( key: Key('home-drama-card-${drama.id}'), onTap: onTap, borderRadius: BorderRadius.circular(10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ClipRRect( key: Key('home-drama-cover-${drama.id}'), borderRadius: BorderRadius.circular(10), child: Stack( children: [ _DiscoverCoverImage(path: drama.posterUrl), if (free) Positioned( top: 6, left: 6, child: Container( padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 2, ), decoration: BoxDecoration( color: AppColors.blue, borderRadius: BorderRadius.circular(6), ), child: Text( l10n.free, style: const TextStyle( color: AppColors.onPrimary, fontSize: 8, fontWeight: FontWeight.w800, ), ), ), ), ], ), ), const SizedBox(height: 5), Text( drama.title, key: Key('home-drama-title-${drama.id}'), maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( color: AppColors.onSurface, fontSize: 11, fontWeight: FontWeight.w800, ), ), const SizedBox(height: 2), Text( genre.isEmpty ? l10n.videoPending : genre, key: Key('home-drama-genre-${drama.id}'), maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(color: Color(0xFFBBBBBB), fontSize: 9), ), ], ), ); } } class _DiscoverCoverImage extends StatelessWidget { const _DiscoverCoverImage({required this.path}); final String path; @override Widget build(BuildContext context) { final url = AppConfig.mediaUrl(path); if (url.isEmpty) return _placeholder(); return Image.network( url, width: double.infinity, fit: BoxFit.fitWidth, loadingBuilder: (context, child, progress) { if (progress == null) return child; return _placeholder(); }, errorBuilder: (_, _, _) => _placeholder(), ); } Widget _placeholder() { return AspectRatio( aspectRatio: 3 / 4, child: ColoredBox( color: AppColors.card, child: const Center( child: Icon( Icons.image_not_supported_outlined, color: AppColors.muted, size: 32, ), ), ), ); } }