Files
ReelShort/lib/pages/home/widgets/home_feed_card.dart
T

195 lines
5.9 KiB
Dart

// 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/drama_genre_ribbon.dart';
import '../../../core/widgets/remote_image.dart';
import '../../../l10n/app_localizations.dart';
import 'home_drama_classification.dart';
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 Material(
key: Key('home-feed-card-${drama.id}'),
color: AppColors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(AppDimens.cardCoverRadius),
onTap: onTap,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
_cover(l10n),
Expanded(
child: Padding(
padding: const EdgeInsets.only(
top: AppDimens.cardCoverTitleGap,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
drama.title,
key: Key('home-feed-title-${drama.id}'),
maxLines: 2,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.left,
style: const TextStyle(
color: AppColors.navSelected,
fontSize: AppDimens.cardTitleFont,
fontWeight: FontWeight.w500,
height: AppDimens.cardTitleLineHeight,
),
),
],
),
),
),
],
),
),
);
}
Widget _cover(AppLocalizations l10n) {
final badge = _badge();
return ClipRRect(
key: Key('home-feed-cover-clip-${drama.id}'),
borderRadius: BorderRadius.circular(AppDimens.cardCoverRadius),
child: AspectRatio(
aspectRatio: AppDimens.cardCoverAspectRatio,
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(
top: 0,
right: 0,
child: DramaGenreRibbon(tags: _cardTags(), keyPrefix: drama.id),
),
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.cardBadgeHorizontalPadding,
vertical: AppDimens.cardBadgeVerticalPadding,
),
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(AppDimens.cardBadgeRadius),
),
child: Text(
label,
style: const TextStyle(
color: AppColors.navSelected,
fontSize: AppDimens.cardBadgeFont,
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.auto_awesome_rounded,
size: AppDimens.cardViewsIcon,
color: AppColors.navSelected,
),
const SizedBox(width: 1),
Text(
formatPlayCount(drama.viewCount),
style: const TextStyle(
color: AppColors.navSelected,
fontSize: AppDimens.cardViewsFont,
fontWeight: FontWeight.w600,
letterSpacing: 0,
),
),
],
),
);
}
List<String> _cardTags() {
final seen = <String>{};
return [drama.genre, drama.category]
.expand((value) => value.split(_tagSplitter))
.map((value) => value.trim())
.where((value) => value.isNotEmpty && seen.add(value))
.take(3)
.toList(growable: false);
}
}