// Author: fruit.com.cn import 'package:flutter/material.dart'; import '../../../core/data/models/drama.dart'; import '../../../core/formatting/count_format.dart'; import '../../../core/theme/app_colors.dart'; import '../../../core/theme/app_dimens.dart'; import '../../../core/widgets/remote_image.dart'; import '../../../l10n/app_localizations.dart'; import 'home_drama_classification.dart'; const double _coverAspectRatio = 5 / 6; final RegExp _tagSplitter = RegExp(r'[,/、·]'); enum HomeDramaBadge { hot, newRelease, anime } class HomeFeedCard extends StatelessWidget { const HomeFeedCard({ super.key, required this.drama, required this.onTap, this.now, }); final Drama drama; final VoidCallback onTap; final DateTime? now; @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; return Container( key: Key('home-feed-card-${drama.id}'), decoration: BoxDecoration( color: AppColors.contentCard, borderRadius: BorderRadius.circular(AppDimens.homeCardRadius), border: Border.all(color: AppColors.contentBorder), ), child: Material( color: AppColors.transparent, borderRadius: BorderRadius.circular(AppDimens.homeCardRadius), clipBehavior: Clip.antiAlias, child: InkWell( onTap: onTap, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _cover(l10n), Expanded( child: Padding( padding: const EdgeInsets.all(AppDimens.homeCardPadding), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( drama.title, key: Key('home-feed-title-${drama.id}'), maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle( color: AppColors.navSelected, fontSize: AppDimens.homeCardTitle, fontWeight: FontWeight.w600, height: 1.25, ), ), const SizedBox(height: AppDimens.spaceXs), _tags(), ], ), ), ), ], ), ), ), ); } Widget _cover(AppLocalizations l10n) { final badge = _badge(); return AspectRatio( aspectRatio: _coverAspectRatio, child: Stack( fit: StackFit.expand, children: [ RemoteImage( drama.posterUrl, key: Key('home-feed-cover-${drama.id}'), fit: BoxFit.cover, showFallbackIcon: true, fallbackColor: AppColors.contentSurface, ), if (badge != null) Positioned( top: AppDimens.spaceXs, left: AppDimens.spaceXs, child: _badgeChip(l10n, badge), ), Positioned( left: AppDimens.spaceXs, bottom: AppDimens.spaceXs, child: _views(), ), ], ), ); } HomeDramaBadge? _badge() { if (drama.dramaType.toUpperCase() == 'ANIME') { return HomeDramaBadge.anime; } if (isNewDrama(drama, now: now)) return HomeDramaBadge.newRelease; if (isHotDrama(drama)) { return HomeDramaBadge.hot; } return null; } Widget _badgeChip(AppLocalizations l10n, HomeDramaBadge badge) { final (label, color) = switch (badge) { HomeDramaBadge.hot => (l10n.homeBadgeHot, AppColors.badgeHot), HomeDramaBadge.newRelease => (l10n.homeBadgeNew, AppColors.badgeNew), HomeDramaBadge.anime => (l10n.homeBadgeAnime, AppColors.badgeAnime), }; return Container( key: Key('home-feed-badge-${badge.name}-${drama.id}'), padding: const EdgeInsets.symmetric( horizontal: AppDimens.spaceSm, vertical: 2, ), decoration: BoxDecoration( color: color, borderRadius: BorderRadius.circular(AppDimens.chipRadius), ), child: Text( label, style: const TextStyle( color: AppColors.navSelected, fontSize: AppDimens.homeCardBadge, fontWeight: FontWeight.w700, letterSpacing: 0, ), ), ); } Widget _views() { return Container( key: Key('home-feed-views-${drama.id}'), padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2), decoration: BoxDecoration( color: AppColors.playerOverlayPill, borderRadius: BorderRadius.circular(AppDimens.pillRadius), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ const Icon( Icons.play_arrow_rounded, size: AppDimens.homeCardMeta, color: AppColors.navSelected, ), const SizedBox(width: 1), Text( formatInteractionCount(drama.viewCount), style: const TextStyle( color: AppColors.navSelected, fontSize: AppDimens.homeCardMeta, fontWeight: FontWeight.w600, letterSpacing: 0, ), ), ], ), ); } Widget _tags() { final tags = _cardTags(); if (tags.isEmpty) return const SizedBox.shrink(); return Row( children: [ for (final (index, tag) in tags.indexed) Flexible( child: Padding( padding: EdgeInsets.only( right: index == tags.length - 1 ? 0 : AppDimens.spaceXs, ), child: Container( key: Key('home-feed-tag-${drama.id}-$index'), padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2), decoration: BoxDecoration( color: AppColors.contentSurface, borderRadius: BorderRadius.circular(AppDimens.chipRadius), ), child: Text( tag, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( color: AppColors.textSecondary, fontSize: AppDimens.homeTagFont, letterSpacing: 0, ), ), ), ), ), ], ); } List _cardTags() { final seen = {}; return [drama.genre, drama.category] .expand((value) => value.split(_tagSplitter)) .map((value) => value.trim()) .where((value) => value.isNotEmpty && seen.add(value)) .take(2) .toList(growable: false); } }