feat(recommend): 新增推荐频道页并恢复首页沉浸式信息流

- tab1 推荐页改为接口分类频道:频道导航可横滑,选中频道自动滚动到可视区,内容区左右滑动切换分类
- 精选频道:顶部固定高度 banner 轮播图 + 接口 tag 筛选的上新短剧横滑 + 其余短剧双列网格;其他分类三列网格
- 分类内容支持懒加载、下拉刷新与上拉分页加载
- 卡片角标文案与背景色取自接口 tag/tagColor,短剧标题统一白色
- 恢复 tab0 为沉浸式全屏视频首页及其三 inner-tab 顶导(搜索/礼物入口)
- l10n 新增原创出品、立即观看、上新、编辑精选、全N集等多语言文案
This commit is contained in:
Vicky authored and trae-solo-agent committed 2026-10-27 19:57:42 +08:00
1 parent a3f3481f4f
commit c027acc91f
22 files changed
+2018 -318

No files matched your search

+48 -20
View File
@@ -5,8 +5,10 @@ import 'package:flutter/material.dart';
import '../../../core/formatting/count_format.dart';
import '../../../core/theme/app_colors.dart';
const double _buttonSize = 44;
const double _iconSize = 32;
const double _defaultButtonSize = 44;
const double _defaultIconSize = 32;
const double _defaultCountFont = 12;
const double _defaultIconCountGap = 2;
const double _defaultItemGap = 24;
/// 互动栏单项:图标、计数与点击。
@@ -21,6 +23,7 @@ class InteractionRailItem {
this.countKey,
this.selected = false,
this.selectedColor = AppColors.blue,
this.mirrorIcon = false,
}) : assert((asset == null) != (icon == null));
final Key key;
@@ -32,6 +35,9 @@ class InteractionRailItem {
final Key? countKey;
final bool selected;
final Color selectedColor;
/// 水平镜像图标(分享弯箭头用 reply 镜像得到)。
final bool mirrorIcon;
}
/// 播放页与首页 feed 共用的右侧互动栏:收藏/评论/喜欢/分享。
@@ -40,10 +46,18 @@ class InteractionRail extends StatelessWidget {
super.key,
required this.items,
this.itemGap = _defaultItemGap,
this.buttonSize = _defaultButtonSize,
this.iconSize = _defaultIconSize,
this.countFontSize = _defaultCountFont,
this.iconCountGap = _defaultIconCountGap,
});
final List<InteractionRailItem> items;
final double itemGap;
final double buttonSize;
final double iconSize;
final double countFontSize;
final double iconCountGap;
@override
Widget build(BuildContext context) {
@@ -52,7 +66,13 @@ class InteractionRail extends StatelessWidget {
children: [
for (var index = 0; index < items.length; index++) ...[
if (index > 0) SizedBox(height: itemGap),
_RailButton(item: items[index]),
_RailButton(
item: items[index],
buttonSize: buttonSize,
iconSize: iconSize,
countFontSize: countFontSize,
iconCountGap: iconCountGap,
),
],
],
);
@@ -60,53 +80,61 @@ class InteractionRail extends StatelessWidget {
}
class _RailButton extends StatelessWidget {
const _RailButton({required this.item});
const _RailButton({
required this.item,
required this.buttonSize,
required this.iconSize,
required this.countFontSize,
required this.iconCountGap,
});
final InteractionRailItem item;
final double buttonSize;
final double iconSize;
final double countFontSize;
final double iconCountGap;
@override
Widget build(BuildContext context) {
final tint = item.selected ? item.selectedColor : AppColors.onPrimary;
return Column(
key: item.key,
mainAxisSize: MainAxisSize.min,
children: [
SizedBox.square(
dimension: _buttonSize,
dimension: buttonSize,
child: IconButton(
onPressed: item.onTap,
padding: EdgeInsets.zero,
icon: item.asset != null
? Image.asset(
item.asset!,
width: _iconSize,
height: _iconSize,
width: iconSize,
height: iconSize,
fit: BoxFit.contain,
color: item.selected
? item.selectedColor
: AppColors.onPrimary,
color: tint,
colorBlendMode: BlendMode.srcIn,
)
: Icon(
item.icon,
size: _iconSize,
color: item.selected
? item.selectedColor
: AppColors.onPrimary,
: Transform.flip(
flipX: item.mirrorIcon,
child: Icon(item.icon, size: iconSize, color: tint),
),
),
),
const SizedBox(height: 2),
SizedBox(height: iconCountGap),
Text(
item.count == null ? item.label : formatInteractionCount(item.count!),
key: item.countKey,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
style: TextStyle(
color: AppColors.onPrimary,
height: 1,
fontSize: 12,
fontSize: countFontSize,
fontWeight: FontWeight.w600,
shadows: [Shadow(blurRadius: 6, color: AppColors.mediaBlack)],
shadows: const [
Shadow(blurRadius: 6, color: AppColors.mediaBlack),
],
),
),
],