// 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'; const double _badgeSize = 24; const double _badgeCornerRadius = 10; const double _badgeFontSize = 14; const double _titleFontSize = 16; const double _metaLineGap = 2; const double _lineHeight = 1.25; const int _synopsisMaxLines = 3; const double _metaFontSize = 12; const double _tagFontSize = 10; const double _flameSize = 13; const double _flameGap = 2; const double _badgeMutedAlpha = 0.6; const double _badgeTextHeight = 1; /// 榜单行:左侧 3:4 封面带名次角标,右侧标题/分类/简介/底行元信息。 class RankingDramaRow extends StatelessWidget { const RankingDramaRow({ super.key, required this.rank, required this.drama, required this.onTap, }); final int rank; final Drama drama; final VoidCallback onTap; @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; return SizedBox( height: AppDimens.rankingCardCoverHeight, child: Material( color: AppColors.transparent, child: InkWell( key: Key('ranking-row-${rank - 1}'), onTap: onTap, child: Row( children: [ _cover(), const SizedBox(width: AppDimens.spaceMd), Expanded(child: _info(l10n)), ], ), ), ), ); } String get _category { final values = {}; for (final value in [drama.genre, drama.category]) { for (final part in value.split(RegExp(r'[,/、·]'))) { final tag = part.trim(); if (tag.isNotEmpty) values.add(tag); } } return values.join(' · '); } Color get _badgeColor => switch (rank) { 1 => AppColors.brandRed, 2 => AppColors.homeCrownGold, 3 => AppColors.bronze, _ => AppColors.ink.withValues(alpha: _badgeMutedAlpha), }; Widget _badge() { return Container( key: Key('ranking-top-$rank'), width: _badgeSize, height: _badgeSize, alignment: Alignment.center, decoration: BoxDecoration( color: _badgeColor, borderRadius: const BorderRadius.only( topLeft: Radius.circular(AppDimens.rankingCoverRadius), bottomRight: Radius.circular(_badgeCornerRadius), ), ), child: Text( '$rank', maxLines: 1, style: const TextStyle( color: AppColors.textPrimary, fontSize: _badgeFontSize, fontWeight: FontWeight.w700, height: _badgeTextHeight, ), ), ); } Widget _cover() { return ClipRRect( borderRadius: BorderRadius.circular(AppDimens.rankingCoverRadius), child: SizedBox( key: Key('ranking-row-cover-$rank'), width: AppDimens.rankingCardCoverWidth, height: AppDimens.rankingCardCoverHeight, child: Stack( fit: StackFit.expand, children: [ RemoteImage( drama.posterUrl, fit: BoxFit.cover, fallbackWidget: const ColoredBox(color: AppColors.raised), ), Positioned(top: 0, left: 0, child: _badge()), ], ), ), ); } Widget _info(AppLocalizations l10n) { final category = _category; final synopsis = drama.synopsis.trim(); return SizedBox( height: AppDimens.rankingCardCoverHeight, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( drama.title, key: Key('ranking-title-$rank'), maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( color: AppColors.textPrimary, fontSize: _titleFontSize, fontWeight: FontWeight.w600, height: _lineHeight, ), ), if (category.isNotEmpty) _metaText(category), if (synopsis.isNotEmpty) _metaText(synopsis, maxLines: _synopsisMaxLines), const SizedBox(height: _metaLineGap), _bottomMeta(l10n), ], ), ); } Widget _metaText(String text, {int maxLines = 1}) { return Padding( padding: const EdgeInsets.only(top: _metaLineGap), child: Text( text, maxLines: maxLines, overflow: TextOverflow.ellipsis, style: const TextStyle( color: AppColors.textTertiary, fontSize: _metaFontSize, height: _lineHeight, ), ), ); } Widget _bottomMeta(AppLocalizations l10n) { final tag = drama.tag.trim(); final metas = [ if (drama.viewCount > 0) l10n.heatLabel(formatPlayCount(drama.viewCount)), if (drama.likeCount != null) l10n.likesLabel(formatInteractionCount(drama.likeCount!)), ]; if (tag.isEmpty && metas.isEmpty) return const SizedBox.shrink(); return Wrap( crossAxisAlignment: WrapCrossAlignment.center, spacing: AppDimens.spaceSm, runSpacing: _metaLineGap, children: [ if (tag.isNotEmpty) _tagText(tag), if (metas.isNotEmpty) Row( mainAxisSize: MainAxisSize.min, children: [ const Icon( Icons.local_fire_department_rounded, color: AppColors.actionRed, size: _flameSize, ), const SizedBox(width: _flameGap), Flexible( child: Text( metas.join(' · '), maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( color: AppColors.textTertiary, fontSize: _metaFontSize, height: _lineHeight, ), ), ), ], ), ], ); } Widget _tagText(String tag) { return Text( tag, key: Key('ranking-tag-$rank'), maxLines: 1, style: TextStyle( color: drama.tagRibbonColor(AppColors.brandRed), fontSize: _tagFontSize, fontWeight: FontWeight.w600, height: _lineHeight, ), ); } }