style(client): 对齐短剧列表字号与间距

This commit is contained in:
Zhou Weidong committed 2026-10-22 18:06:03 +08:00
1 parent d03dcf162e
commit d7a59e5a27
17 files changed
+576 -148

No files matched your search

+1
View File
@@ -79,6 +79,7 @@ abstract final class AppColors {
/// 次级文字与列表 chevron。
static const textPrimary = Color(0xFFFFFFFF);
static const textSecondary = Color(0xFF8E8E93);
static const textTertiary = Color(0xFF9E9E9E);
/// 原生开屏与冷启动过渡底色。
static const splashInk = ink;
+39 -9
View File
@@ -54,7 +54,9 @@ abstract final class AppDimens {
static const double progressRadius = 2;
static const double progressLineHeight = 3;
static const double searchHeight = 44;
static const double searchHeight = 40;
static const double searchRadius = 8;
static const double searchPlaceholderFont = 13;
static const double navDot = 3;
static const double glassBlur = 24;
@@ -75,23 +77,51 @@ abstract final class AppDimens {
static const double cardShadowY = 0;
/// 首页探索网格。
static const double homeGridGap = 8;
static const double homeCardRadius = 8;
static const double feedPageHorizontal = 14;
static const double homeGridGap = 6;
static const double homeGridRowGap = 18;
static const double homeCardRadius = 6;
static const double homeCardPadding = 8;
static const double homeCardTitle = 13;
static const double homeCardMeta = 10;
static const double homeCardBadge = 9;
static const double homeTagFont = 10;
static const double cardTitleFont = 15;
static const double cardTitleLineHeight = 1.3;
static const double cardViewsFont = 11;
static const double cardViewsIcon = 11;
static const double cardTagFont = 11;
static const double cardTagHeight = 18;
static const double cardTagRadius = 9;
static const double cardTagHorizontalPadding = 6;
static const double cardBadgeFont = 10;
static const double cardBadgeRadius = 3;
static const double cardBadgeHorizontalPadding = 6;
static const double cardBadgeVerticalPadding = 2;
static const double cardCoverTitleGap = 8;
static const double cardTitleTagGap = 6;
static const double cardCoverAspectRatio = 5 / 6;
static const double homeCardBodyExtent = 84;
static const double homeSectionFont = 14;
static const double homePrimaryTabFont = 16;
static const double homeSecondaryTagFont = 13;
static const double homeSectionIndicator = 2;
static const double homeIconSlot = 44;
static const double homeHeaderIcon = 22;
/// 推荐分区。
static const double recommendSectionTitle = 17;
static const double recommendSectionSubtitle = 11;
static const double recommendSectionMore = 12;
static const double recommendRowHeight = 252;
static const double recommendCardWidth = 122;
/// 全部短剧筛选与追剧双列。
static const double filterChipFont = 13;
static const double filterChipHeight = 30;
static const double filterChipRadius = 15;
static const double followingTabFont = 16;
static const double followingGridAspectRatio = 0.5;
/// 「我的」页中性视觉。
static const double mineCardRadius = 16;
static const double mineRowHeight = 56;
static const double minePageTitle = 21;
static const double minePageTitle = 17;
static const double mineSectionTitle = 15;
static const double mineAvatar = 56;
static const double mineUserName = 20;
+56 -8
View File
@@ -14,6 +14,7 @@ class FollowingGridCard extends StatelessWidget {
required this.title,
required this.posterUrl,
required this.viewCount,
required this.tags,
required this.subtitle,
required this.onTap,
this.progress,
@@ -25,6 +26,7 @@ class FollowingGridCard extends StatelessWidget {
final String title;
final String posterUrl;
final int viewCount;
final List<String> tags;
final String subtitle;
final VoidCallback onTap;
final double? progress;
@@ -40,7 +42,9 @@ class FollowingGridCard extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
AspectRatio(
key: Key('following-cover-$dramaId'),
aspectRatio: AppDimens.cardCoverAspectRatio,
child: Stack(
clipBehavior: Clip.none,
children: [
@@ -75,13 +79,14 @@ class FollowingGridCard extends StatelessWidget {
const Icon(
Icons.play_arrow_rounded,
color: AppColors.textPrimary,
size: AppDimens.homeCardMeta,
size: AppDimens.cardViewsIcon,
),
Text(
formatInteractionCount(viewCount),
key: Key('following-views-$dramaId'),
style: const TextStyle(
color: AppColors.textPrimary,
fontSize: AppDimens.homeCardMeta,
fontSize: AppDimens.cardViewsFont,
),
),
],
@@ -109,24 +114,30 @@ class FollowingGridCard extends StatelessWidget {
],
),
),
const SizedBox(height: AppDimens.spaceXs),
const SizedBox(height: AppDimens.cardCoverTitleGap),
Text(
title,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.textPrimary,
fontSize: AppDimens.homeCardTitle,
fontWeight: FontWeight.w600,
fontSize: AppDimens.cardTitleFont,
fontWeight: FontWeight.w500,
height: AppDimens.cardTitleLineHeight,
),
),
if (tags.isNotEmpty) ...[
const SizedBox(height: AppDimens.cardTitleTagGap),
_tags(),
],
const SizedBox(height: AppDimens.spaceXs),
Text(
subtitle,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.textSecondary,
fontSize: AppDimens.homeCardMeta,
color: AppColors.textTertiary,
fontSize: AppDimens.cardViewsFont,
),
),
if (progress != null) ...[
@@ -148,4 +159,41 @@ class FollowingGridCard extends StatelessWidget {
),
);
}
Widget _tags() {
final visible = tags.take(2).toList(growable: false);
return Row(
children: [
for (final (index, tag) in visible.indexed)
Flexible(
child: Padding(
padding: EdgeInsets.only(
right: index == visible.length - 1 ? 0 : AppDimens.spaceXs,
),
child: Container(
key: Key('following-tag-$dramaId-$index'),
height: AppDimens.cardTagHeight,
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(
horizontal: AppDimens.cardTagHorizontalPadding,
),
decoration: BoxDecoration(
color: AppColors.contentSurface,
borderRadius: BorderRadius.circular(AppDimens.cardTagRadius),
),
child: Text(
tag,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.textTertiary,
fontSize: AppDimens.cardTagFont,
),
),
),
),
),
],
);
}
}
+67 -46
View File
@@ -5,6 +5,7 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../core/data/models/drama.dart';
import '../../core/data/models/watch_record.dart';
import '../../core/data/repositories/catalog_repository.dart';
import '../../core/data/repositories/watch_history_repository.dart';
@@ -22,6 +23,8 @@ import '../../l10n/app_localizations.dart';
import '../player/open_player.dart';
import 'following_grid_card.dart';
final RegExp _tagSplitter = RegExp(r'[,/、·]');
enum FollowingTab { favorites, history, reserve }
class FollowingPage extends StatefulWidget {
@@ -117,51 +120,56 @@ class _FollowingPageState extends State<FollowingPage> {
return SizedBox(
key: const Key('following-header'),
height: 52,
child: Row(
children: [
for (final tab in tabs)
Expanded(
child: InkWell(
key: Key('following-tab-${tab.$3}'),
onTap: () => setState(() {
_tab = tab.$1;
_editing = false;
}),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
tab.$2,
maxLines: 1,
style: TextStyle(
color: _tab == tab.$1
? AppColors.textPrimary
: AppColors.textSecondary,
fontSize: 16,
fontWeight: _tab == tab.$1
? FontWeight.w700
: FontWeight.w500,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: AppDimens.feedPageHorizontal,
),
child: Row(
children: [
for (final tab in tabs)
Expanded(
child: InkWell(
key: Key('following-tab-${tab.$3}'),
onTap: () => setState(() {
_tab = tab.$1;
_editing = false;
}),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
tab.$2,
maxLines: 1,
style: TextStyle(
color: _tab == tab.$1
? AppColors.textPrimary
: AppColors.textTertiary,
fontSize: AppDimens.followingTabFont,
fontWeight: _tab == tab.$1
? FontWeight.w700
: FontWeight.w400,
),
),
),
const SizedBox(height: 3),
Container(
width: _tab == tab.$1 ? 20 : 0,
height: 2,
color: AppColors.textPrimary,
),
],
const SizedBox(height: 3),
Container(
width: _tab == tab.$1 ? 20 : 0,
height: 2,
color: AppColors.textPrimary,
),
],
),
),
),
IconButton(
key: const Key('following-edit'),
tooltip: _editing ? l10n.done : l10n.edit,
onPressed: () => setState(() => _editing = !_editing),
color: AppColors.textPrimary,
iconSize: 24,
icon: Icon(_editing ? Icons.check_rounded : Icons.edit_outlined),
),
IconButton(
key: const Key('following-edit'),
tooltip: _editing ? l10n.done : l10n.edit,
onPressed: () => setState(() => _editing = !_editing),
color: AppColors.textPrimary,
iconSize: 24,
icon: Icon(_editing ? Icons.check_rounded : Icons.edit_outlined),
),
],
],
),
),
);
}
@@ -233,6 +241,7 @@ class _FavoritesGrid extends StatelessWidget {
title: drama.title,
posterUrl: drama.posterUrl,
viewCount: drama.viewCount,
tags: _dramaTags(drama),
editing: editing,
subtitle: l10n.favoriteEpisodeProgress(
episode,
@@ -304,6 +313,7 @@ class _HistoryGrid extends StatelessWidget {
title: title,
posterUrl: record.displayCover,
viewCount: record.drama?.viewCount ?? 0,
tags: _dramaTags(record.drama),
progress: record.progress.fraction,
editing: editing,
subtitle: l10n.watchedToEpisode(record.episodeNumber),
@@ -322,6 +332,17 @@ class _HistoryGrid extends StatelessWidget {
}
}
List<String> _dramaTags(Drama? drama) {
if (drama == null) return const [];
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(2)
.toList(growable: false);
}
Future<void> _openHistory(
BuildContext context,
WatchRecordsController controller,
@@ -416,17 +437,17 @@ class _PagedGrid extends StatelessWidget {
else ...[
SliverPadding(
padding: const EdgeInsets.fromLTRB(
AppDimens.pageHorizontal,
AppDimens.feedPageHorizontal,
AppDimens.spaceMd,
AppDimens.pageHorizontal,
AppDimens.feedPageHorizontal,
AppDimens.spaceSm,
),
sliver: SliverGrid(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: AppDimens.spaceMd,
mainAxisSpacing: AppDimens.spaceMd,
childAspectRatio: 0.56,
crossAxisSpacing: AppDimens.homeGridGap,
mainAxisSpacing: AppDimens.homeGridRowGap,
childAspectRatio: AppDimens.followingGridAspectRatio,
),
delegate: SliverChildBuilderDelegate(
itemBuilder,
+19 -16
View File
@@ -10,7 +10,6 @@ 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 }
@@ -60,12 +59,12 @@ class HomeFeedCard extends StatelessWidget {
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.navSelected,
fontSize: AppDimens.homeCardTitle,
fontWeight: FontWeight.w600,
height: 1.25,
fontSize: AppDimens.cardTitleFont,
fontWeight: FontWeight.w500,
height: AppDimens.cardTitleLineHeight,
),
),
const SizedBox(height: AppDimens.spaceXs),
const SizedBox(height: AppDimens.cardTitleTagGap),
_tags(),
],
),
@@ -81,7 +80,7 @@ class HomeFeedCard extends StatelessWidget {
Widget _cover(AppLocalizations l10n) {
final badge = _badge();
return AspectRatio(
aspectRatio: _coverAspectRatio,
aspectRatio: AppDimens.cardCoverAspectRatio,
child: Stack(
fit: StackFit.expand,
children: [
@@ -128,18 +127,18 @@ class HomeFeedCard extends StatelessWidget {
return Container(
key: Key('home-feed-badge-${badge.name}-${drama.id}'),
padding: const EdgeInsets.symmetric(
horizontal: AppDimens.spaceSm,
vertical: 2,
horizontal: AppDimens.cardBadgeHorizontalPadding,
vertical: AppDimens.cardBadgeVerticalPadding,
),
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(AppDimens.chipRadius),
borderRadius: BorderRadius.circular(AppDimens.cardBadgeRadius),
),
child: Text(
label,
style: const TextStyle(
color: AppColors.navSelected,
fontSize: AppDimens.homeCardBadge,
fontSize: AppDimens.cardBadgeFont,
fontWeight: FontWeight.w700,
letterSpacing: 0,
),
@@ -160,7 +159,7 @@ class HomeFeedCard extends StatelessWidget {
children: [
const Icon(
Icons.play_arrow_rounded,
size: AppDimens.homeCardMeta,
size: AppDimens.cardViewsIcon,
color: AppColors.navSelected,
),
const SizedBox(width: 1),
@@ -168,7 +167,7 @@ class HomeFeedCard extends StatelessWidget {
formatInteractionCount(drama.viewCount),
style: const TextStyle(
color: AppColors.navSelected,
fontSize: AppDimens.homeCardMeta,
fontSize: AppDimens.cardViewsFont,
fontWeight: FontWeight.w600,
letterSpacing: 0,
),
@@ -191,18 +190,22 @@ class HomeFeedCard extends StatelessWidget {
),
child: Container(
key: Key('home-feed-tag-${drama.id}-$index'),
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2),
height: AppDimens.cardTagHeight,
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(
horizontal: AppDimens.cardTagHorizontalPadding,
),
decoration: BoxDecoration(
color: AppColors.contentSurface,
borderRadius: BorderRadius.circular(AppDimens.chipRadius),
borderRadius: BorderRadius.circular(AppDimens.cardTagRadius),
),
child: Text(
tag,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.textSecondary,
fontSize: AppDimens.homeTagFont,
color: AppColors.textTertiary,
fontSize: AppDimens.cardTagFont,
letterSpacing: 0,
),
),
+3 -3
View File
@@ -73,7 +73,7 @@ class _HomeFeedGridState extends State<HomeFeedGrid> {
builder: (context, constraints) {
final itemWidth =
(constraints.maxWidth -
AppDimens.pageHorizontal * 2 -
AppDimens.feedPageHorizontal * 2 -
AppDimens.homeGridGap * 2) /
3;
final itemExtent = itemWidth * 6 / 5 + AppDimens.homeCardBodyExtent;
@@ -117,7 +117,7 @@ class _HomeFeedGridState extends State<HomeFeedGrid> {
else
SliverPadding(
padding: const EdgeInsets.symmetric(
horizontal: AppDimens.pageHorizontal,
horizontal: AppDimens.feedPageHorizontal,
),
sliver: SliverGrid(
key: const Key('home-grid-cards'),
@@ -125,7 +125,7 @@ class _HomeFeedGridState extends State<HomeFeedGrid> {
SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: AppDimens.homeGridGap,
mainAxisSpacing: AppDimens.homeGridGap,
mainAxisSpacing: AppDimens.homeGridRowGap,
mainAxisExtent: itemExtent,
),
delegate: SliverChildBuilderDelegate(
+18 -16
View File
@@ -78,9 +78,9 @@ class HomeGridHeader extends StatelessWidget {
Widget _searchRow(AppLocalizations l10n) {
return Padding(
padding: const EdgeInsets.fromLTRB(
AppDimens.pageHorizontal,
AppDimens.feedPageHorizontal,
AppDimens.spaceSm,
AppDimens.pageHorizontal,
AppDimens.feedPageHorizontal,
AppDimens.spaceSm,
),
child: Row(
@@ -88,7 +88,7 @@ class HomeGridHeader extends StatelessWidget {
Expanded(
child: InkWell(
key: const Key('home-search-entry'),
borderRadius: BorderRadius.circular(AppDimens.inputRadius),
borderRadius: BorderRadius.circular(AppDimens.searchRadius),
onTap: callbacks.onSearch,
child: Container(
height: AppDimens.searchHeight,
@@ -97,7 +97,7 @@ class HomeGridHeader extends StatelessWidget {
),
decoration: BoxDecoration(
color: AppColors.contentSurface,
borderRadius: BorderRadius.circular(AppDimens.inputRadius),
borderRadius: BorderRadius.circular(AppDimens.searchRadius),
),
child: Row(
children: [
@@ -114,7 +114,7 @@ class HomeGridHeader extends StatelessWidget {
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.textSecondary,
fontSize: AppDimens.body,
fontSize: AppDimens.searchPlaceholderFont,
),
),
),
@@ -191,7 +191,7 @@ class HomeGridHeader extends StatelessWidget {
formatInteractionCount(badge),
style: const TextStyle(
color: AppColors.navSelected,
fontSize: AppDimens.homeCardBadge,
fontSize: AppDimens.cardBadgeFont,
fontWeight: FontWeight.w700,
),
),
@@ -206,7 +206,9 @@ class HomeGridHeader extends StatelessWidget {
return SingleChildScrollView(
key: const Key('home-primary-sections'),
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: AppDimens.pageHorizontal),
padding: const EdgeInsets.symmetric(
horizontal: AppDimens.feedPageHorizontal,
),
child: Row(
children: [
_primaryItem(
@@ -264,9 +266,9 @@ class HomeGridHeader extends StatelessWidget {
style: TextStyle(
color: selected
? AppColors.navSelected
: AppColors.textSecondary,
fontSize: AppDimens.homeSectionFont,
fontWeight: selected ? FontWeight.w700 : FontWeight.w500,
: AppColors.textTertiary,
fontSize: AppDimens.homePrimaryTabFont,
fontWeight: selected ? FontWeight.w700 : FontWeight.w400,
letterSpacing: 0,
),
),
@@ -288,9 +290,9 @@ class HomeGridHeader extends StatelessWidget {
Widget _tagRow(AppLocalizations l10n) {
return Padding(
padding: const EdgeInsets.fromLTRB(
AppDimens.pageHorizontal,
AppDimens.feedPageHorizontal,
AppDimens.spaceMd,
AppDimens.pageHorizontal,
AppDimens.feedPageHorizontal,
AppDimens.spaceSm,
),
child: Row(
@@ -347,15 +349,15 @@ class HomeGridHeader extends StatelessWidget {
vertical: AppDimens.spaceSm,
),
decoration: BoxDecoration(
color: selected ? AppColors.contentChip : AppColors.contentSurface,
color: selected ? AppColors.contentSurface : AppColors.transparent,
borderRadius: BorderRadius.circular(AppDimens.pillRadius),
),
child: Text(
tag.label,
style: TextStyle(
color: selected ? AppColors.navSelected : AppColors.textSecondary,
fontSize: AppDimens.homeSectionFont - 2,
fontWeight: selected ? FontWeight.w600 : FontWeight.w500,
color: selected ? AppColors.navSelected : AppColors.textTertiary,
fontSize: AppDimens.homeSecondaryTagFont,
fontWeight: selected ? FontWeight.w700 : FontWeight.w400,
letterSpacing: 0,
),
),
+4 -4
View File
@@ -134,7 +134,7 @@ class _RecommendPageState extends State<RecommendPage> {
height: 56,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: AppDimens.pageHorizontal,
horizontal: AppDimens.feedPageHorizontal,
),
child: Row(
children: [
@@ -189,9 +189,9 @@ class _RecommendPageState extends State<RecommendPage> {
style: TextStyle(
color: selected
? AppColors.textPrimary
: AppColors.textSecondary,
fontSize: AppDimens.listTitle,
fontWeight: selected ? FontWeight.w700 : FontWeight.w500,
: AppColors.textTertiary,
fontSize: AppDimens.homePrimaryTabFont,
fontWeight: selected ? FontWeight.w700 : FontWeight.w400,
),
),
const SizedBox(height: 3),
@@ -47,7 +47,7 @@ class _AllDramasFilterGridState extends State<AllDramasFilterGrid> {
builder: (context, constraints) {
final itemWidth =
(constraints.maxWidth -
AppDimens.pageHorizontal * 2 -
AppDimens.feedPageHorizontal * 2 -
AppDimens.homeGridGap * 2) /
3;
final itemExtent = itemWidth * 6 / 5 + AppDimens.homeCardBodyExtent;
@@ -77,14 +77,14 @@ class _AllDramasFilterGridState extends State<AllDramasFilterGrid> {
else
SliverPadding(
padding: const EdgeInsets.symmetric(
horizontal: AppDimens.pageHorizontal,
horizontal: AppDimens.feedPageHorizontal,
),
sliver: SliverGrid(
key: const Key('all-dramas-grid'),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: AppDimens.homeGridGap,
mainAxisSpacing: AppDimens.homeGridGap,
mainAxisSpacing: AppDimens.homeGridRowGap,
mainAxisExtent: itemExtent,
),
delegate: SliverChildBuilderDelegate(
@@ -122,9 +122,9 @@ class _AllDramasFilterGridState extends State<AllDramasFilterGrid> {
final genres = _availableGenres(l10n);
return Padding(
padding: const EdgeInsets.fromLTRB(
AppDimens.pageHorizontal,
AppDimens.feedPageHorizontal,
AppDimens.spaceSm,
AppDimens.pageHorizontal,
AppDimens.feedPageHorizontal,
AppDimens.spaceMd,
),
child: Column(
@@ -207,21 +207,21 @@ class _AllDramasFilterGridState extends State<AllDramasFilterGrid> {
return InkWell(
key: key,
onTap: onTap,
borderRadius: BorderRadius.circular(AppDimens.pillRadius),
borderRadius: BorderRadius.circular(AppDimens.filterChipRadius),
child: Container(
height: 32,
height: AppDimens.filterChipHeight,
padding: const EdgeInsets.symmetric(horizontal: AppDimens.spaceMd),
alignment: Alignment.center,
decoration: BoxDecoration(
color: selected ? AppColors.contentChip : AppColors.contentSurface,
borderRadius: BorderRadius.circular(AppDimens.pillRadius),
color: selected ? AppColors.contentSurface : AppColors.transparent,
borderRadius: BorderRadius.circular(AppDimens.filterChipRadius),
),
child: Text(
label,
style: TextStyle(
color: selected ? AppColors.textPrimary : AppColors.textSecondary,
fontSize: AppDimens.caption,
fontWeight: selected ? FontWeight.w700 : FontWeight.w500,
color: selected ? AppColors.textPrimary : AppColors.textTertiary,
fontSize: AppDimens.filterChipFont,
fontWeight: selected ? FontWeight.w700 : FontWeight.w400,
),
),
),
@@ -10,6 +10,8 @@ import '../../../core/widgets/remote_image.dart';
import '../../../l10n/app_localizations.dart';
import '../../home/widgets/home_drama_classification.dart';
final RegExp _tagSplitter = RegExp(r'[,/、·]');
class RecommendSectionCard extends StatelessWidget {
const RecommendSectionCard({
super.key,
@@ -26,7 +28,7 @@ class RecommendSectionCard extends StatelessWidget {
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return SizedBox(
width: 122,
width: AppDimens.recommendCardWidth,
child: InkWell(
key: Key('recommend-card-$sectionKey-${drama.id}'),
borderRadius: BorderRadius.circular(AppDimens.homeCardRadius),
@@ -38,7 +40,7 @@ class RecommendSectionCard extends StatelessWidget {
ClipRRect(
borderRadius: BorderRadius.circular(AppDimens.homeCardRadius),
child: AspectRatio(
aspectRatio: 3 / 4,
aspectRatio: AppDimens.cardCoverAspectRatio,
child: Stack(
fit: StackFit.expand,
children: [
@@ -55,20 +57,20 @@ class RecommendSectionCard extends StatelessWidget {
child: Container(
key: Key('recommend-hot-$sectionKey-${drama.id}'),
padding: const EdgeInsets.symmetric(
horizontal: 6,
vertical: 2,
horizontal: AppDimens.cardBadgeHorizontalPadding,
vertical: AppDimens.cardBadgeVerticalPadding,
),
decoration: BoxDecoration(
color: AppColors.badgeHot,
borderRadius: BorderRadius.circular(
AppDimens.chipRadius,
AppDimens.cardBadgeRadius,
),
),
child: Text(
l10n.homeBadgeHot,
style: const TextStyle(
color: AppColors.textPrimary,
fontSize: AppDimens.homeCardBadge,
fontSize: AppDimens.cardBadgeFont,
fontWeight: FontWeight.w700,
),
),
@@ -78,19 +80,20 @@ class RecommendSectionCard extends StatelessWidget {
),
),
),
const SizedBox(height: AppDimens.spaceXs),
const SizedBox(height: AppDimens.cardCoverTitleGap),
Row(
key: Key('recommend-views-$sectionKey-${drama.id}'),
children: [
const Icon(
Icons.play_arrow_rounded,
color: AppColors.textSecondary,
size: AppDimens.homeCardMeta,
color: AppColors.textPrimary,
size: AppDimens.cardViewsIcon,
),
Text(
formatInteractionCount(drama.viewCount),
style: const TextStyle(
color: AppColors.textSecondary,
fontSize: AppDimens.homeCardMeta,
color: AppColors.textPrimary,
fontSize: AppDimens.cardViewsFont,
),
),
],
@@ -103,14 +106,65 @@ class RecommendSectionCard extends StatelessWidget {
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.textPrimary,
fontSize: AppDimens.homeCardTitle,
fontWeight: FontWeight.w600,
height: 1.25,
fontSize: AppDimens.cardTitleFont,
fontWeight: FontWeight.w500,
height: AppDimens.cardTitleLineHeight,
),
),
if (_cardTags().isNotEmpty) ...[
const SizedBox(height: AppDimens.cardTitleTagGap),
_tags(),
],
],
),
),
);
}
Widget _tags() {
final tags = _cardTags();
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('recommend-tag-$sectionKey-${drama.id}-$index'),
height: AppDimens.cardTagHeight,
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(
horizontal: AppDimens.cardTagHorizontalPadding,
),
decoration: BoxDecoration(
color: AppColors.contentSurface,
borderRadius: BorderRadius.circular(AppDimens.cardTagRadius),
),
child: Text(
tag,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.textTertiary,
fontSize: AppDimens.cardTagFont,
),
),
),
),
),
],
);
}
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(2)
.toList(growable: false);
}
}
@@ -132,7 +132,7 @@ class RecommendSectionsView extends StatelessWidget {
children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: AppDimens.pageHorizontal,
horizontal: AppDimens.feedPageHorizontal,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -145,7 +145,7 @@ class RecommendSectionsView extends StatelessWidget {
title,
style: const TextStyle(
color: AppColors.textPrimary,
fontSize: AppDimens.sectionTitle,
fontSize: AppDimens.recommendSectionTitle,
fontWeight: FontWeight.w700,
),
),
@@ -154,7 +154,7 @@ class RecommendSectionsView extends StatelessWidget {
subtitle,
style: const TextStyle(
color: AppColors.textSecondary,
fontSize: AppDimens.cardViews,
fontSize: AppDimens.recommendSectionSubtitle,
),
),
],
@@ -173,7 +173,8 @@ class RecommendSectionsView extends StatelessWidget {
Text(
l10n.more,
style: const TextStyle(
fontSize: AppDimens.caption,
color: AppColors.textSecondary,
fontSize: AppDimens.recommendSectionMore,
fontWeight: FontWeight.w500,
),
),
@@ -186,12 +187,12 @@ class RecommendSectionsView extends StatelessWidget {
),
const SizedBox(height: AppDimens.spaceMd),
SizedBox(
height: 218,
height: AppDimens.recommendRowHeight,
child: ListView.separated(
key: Key('recommend-row-$key'),
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(
horizontal: AppDimens.pageHorizontal,
horizontal: AppDimens.feedPageHorizontal,
),
itemCount: dramas.length,
separatorBuilder: (_, _) =>