feat(recommend): 上新板块改按后端语义码判断,顶部轮播改为无缝循环
flutter-ci / test (push) Failing after 30m39s

Drama 新增 tagCode,推荐页只看 NEW_ARRIVAL 这个码决定上新横滑,
不再依赖可变的标签文案;分类网格高度改为复用卡片的统一常量。
头部轮播改用虚拟无限页码,首尾相接且能反向滑,自动播放不再倒滑回第一张。
This commit is contained in:
Vicky authored and trae-solo-agent committed 2026-10-29 15:44:33 +08:00
1 parent c311985601
commit 3590cd0c55
5 files changed
+235 -73

No files matched your search

@@ -138,8 +138,7 @@ class _RecommendDiscoverPageState extends State<RecommendDiscoverPage>
}
}
Future<void> _openDrama(Drama drama) =>
playDramaFromCatalog(context, drama);
Future<void> _openDrama(Drama drama) => playDramaFromCatalog(context, drama);
@override
Widget build(BuildContext context) {
@@ -163,9 +162,7 @@ class _RecommendDiscoverPageState extends State<RecommendDiscoverPage>
Positioned.fill(
child: categoryList.isEmpty
? const Center(
child: CircularProgressIndicator(
color: AppColors.brandRed,
),
child: CircularProgressIndicator(color: AppColors.brandRed),
)
: PageView.builder(
key: const Key('recommend-channel-pager'),
@@ -184,7 +181,8 @@ class _RecommendDiscoverPageState extends State<RecommendDiscoverPage>
carouselActive: widget.active && index == selectedIndex,
mediaTop: mediaTop,
onRequest: _ensureCategory,
onRefresh: () => _refreshCategory(category.id, index == 0),
onRefresh: () =>
_refreshCategory(category.id, index == 0),
onOpenDrama: _openDrama,
);
},
@@ -294,11 +292,7 @@ class _ChannelFeedState extends State<_ChannelFeed>
List<Drama> newArrivals = const [];
List<Drama> rest = dramas;
if (widget.isFeatured && dramas.isNotEmpty) {
final lang = Localizations.localeOf(context).languageCode;
final badge = AppLocalizations.of(context)!.newArrivalBadge;
newArrivals = dramas
.where((drama) => _isNewArrivalTag(drama, lang, badge))
.toList(growable: false);
newArrivals = dramas.where(isNewArrivalTag).toList(growable: false);
if (newArrivals.isNotEmpty) {
final newIds = newArrivals.map((drama) => drama.id).toSet();
rest = dramas
@@ -326,7 +320,9 @@ class _ChannelFeedState extends State<_ChannelFeed>
hasScrollBody: false,
child: Padding(
padding: EdgeInsets.only(
top: showCarousel ? 0 : widget.mediaTop + RecommendChannelNav.height,
top: showCarousel
? 0
: widget.mediaTop + RecommendChannelNav.height,
),
child: Center(child: _placeholder(slot.loading)),
),
@@ -334,12 +330,11 @@ class _ChannelFeedState extends State<_ChannelFeed>
);
} else if (widget.isFeatured) {
// 无轮播图时内容需避开顶部悬浮导航。
final contentTop =
showCarousel ? 24.0 : widget.mediaTop + RecommendChannelNav.height + 16;
final contentTop = showCarousel
? 24.0
: widget.mediaTop + RecommendChannelNav.height + 16;
if (newArrivals.isNotEmpty) {
sliverList.add(
SliverToBoxAdapter(child: SizedBox(height: contentTop)),
);
sliverList.add(SliverToBoxAdapter(child: SizedBox(height: contentTop)));
sliverList.add(
SliverToBoxAdapter(
child: HomeNewArrivalRow(
@@ -348,9 +343,7 @@ class _ChannelFeedState extends State<_ChannelFeed>
),
),
);
sliverList.add(
const SliverToBoxAdapter(child: SizedBox(height: 28)),
);
sliverList.add(const SliverToBoxAdapter(child: SizedBox(height: 28)));
}
sliverList.add(
_gridSliver(
@@ -363,7 +356,9 @@ class _ChannelFeedState extends State<_ChannelFeed>
sliverList.add(
_gridSliver(
dramas,
topPadding: showCarousel ? 20 : widget.mediaTop + RecommendChannelNav.height + 16,
topPadding: showCarousel
? 20
: widget.mediaTop + RecommendChannelNav.height + 16,
crossAxisCount: 3,
),
);
@@ -427,7 +422,6 @@ class _ChannelFeedState extends State<_ChannelFeed>
const pageSide = 14.0;
const cardGap = 9.0;
const rowGap = 16.0;
const cardBodyExtent = 44.0; // 封面下间距 + 剧名行 + 集数行
// 两列时卡更大,卡间距略放宽。
final spacing = crossAxisCount == 2 ? 12.0 : cardGap;
return SliverPadding(
@@ -437,7 +431,7 @@ class _ChannelFeedState extends State<_ChannelFeed>
final itemWidth =
(constraints.crossAxisExtent - spacing * (crossAxisCount - 1)) /
crossAxisCount;
final itemExtent = itemWidth * 157 / 110 + cardBodyExtent;
final itemExtent = itemWidth * 157 / 110 + homePosterCardBodyExtent;
return SliverGrid(
key: Key('recommend-category-grid-${widget.categoryId}'),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
@@ -446,17 +440,14 @@ class _ChannelFeedState extends State<_ChannelFeed>
mainAxisSpacing: rowGap,
mainAxisExtent: itemExtent,
),
delegate: SliverChildBuilderDelegate(
(context, index) {
final drama = dramas[index];
return HomePosterCard(
key: Key('recommend-category-card-${drama.id}'),
drama: drama,
onTap: () => widget.onOpenDrama(drama),
);
},
childCount: dramas.length,
),
delegate: SliverChildBuilderDelegate((context, index) {
final drama = dramas[index];
return HomePosterCard(
key: Key('recommend-category-card-${drama.id}'),
drama: drama,
onTap: () => widget.onOpenDrama(drama),
);
}, childCount: dramas.length),
);
},
),
@@ -464,14 +455,11 @@ class _ChannelFeedState extends State<_ChannelFeed>
}
}
/// 判定接口下发的短剧标签是否为「上新」。
/// 「上新」标签语义码,对应后台 content_tag.code。
const String newArrivalTagCode = 'NEW_ARRIVAL';
/// 判定短剧是否属于「上新」板块。
///
/// 完全以后端返回的 `tag` / `tagLocales`(经 [Drama.localizedTag] 本地化)
/// 为准;兼容「上新 / NEW / BARU」三种文案,并兼容「本周上新」这类带前后缀的写法。
bool _isNewArrivalTag(Drama drama, String lang, String localizedBadge) {
final tag = drama.localizedTag(lang).trim();
if (tag.isEmpty) return false;
if (tag == localizedBadge) return true;
final lower = tag.toLowerCase();
return tag.contains('上新') || lower == 'new' || lower == 'baru';
}
/// 只看语义码:码由后台标签管理配置,文案怎么改、切什么语言都不影响。
bool isNewArrivalTag(Drama drama) =>
drama.tagCode.trim().toUpperCase() == newArrivalTagCode;