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

@@ -22,11 +22,13 @@ part 'home_feed_overlay_parts.dart';
const double _progressHeight = 2;
const double _progressHitHeight = 32;
const double _pageHorizontal = 14;
const double _railRight = 8;
const double _pageHorizontal = 20;
const double _railRight = 14;
const double _railGap = 12;
const double _bottomInfoInset = 32;
const double _railBottomOffset = 60;
// 观看条高 44;2pt 进度线视觉上沿距观看条顶约 7pt(线中心距进度条盒底 8)。
// 信息面板底边取 44+16=60,使简介↔进度线、进度线↔观看条两侧间距均为 7pt。
const double _bottomInfoInset = homeFeedWatchAllHeight + 16;
const double _railBottomOffset = 40;
const Duration _overlayFadeDuration = Duration(milliseconds: 120);
const List<Shadow> _boostTextShadows = [
Shadow(color: AppColors.shadow, blurRadius: 5, offset: Offset(0, 1)),
@@ -52,6 +54,7 @@ class HomeFeedOverlay extends StatefulWidget {
required this.onDetail,
required this.onOpenFullPlayer,
this.onEpisodes,
this.onOpenSearch,
required this.onTogglePlay,
required this.onBoostStart,
required this.onBoostEnd,
@@ -92,6 +95,9 @@ class HomeFeedOverlay extends StatefulWidget {
final VoidCallback onDetail;
final VoidCallback onOpenFullPlayer;
final VoidCallback? onEpisodes;
/// 底部热搜胶囊点击(降级版:跳搜索页)。
final VoidCallback? onOpenSearch;
final VoidCallback onTogglePlay;
final VoidCallback onBoostStart;
final VoidCallback onBoostEnd;
@@ -173,7 +179,7 @@ class _HomeFeedOverlayState extends State<HomeFeedOverlay> {
Positioned(
left: 0,
right: 0,
bottom: 20,
bottom: homeFeedWatchAllHeight + 12,
child: AnimatedOpacity(
duration: _overlayFadeDuration,
opacity: widget.boosting ? 1 : 0,
@@ -184,9 +190,9 @@ class _HomeFeedOverlayState extends State<HomeFeedOverlay> {
),
),
Positioned(
left: 0,
right: 0,
bottom: 0,
left: _pageHorizontal,
right: _pageHorizontal,
bottom: homeFeedWatchAllHeight,
child: _FeedProgressBar(
controller: widget.controller,
locked: widget.locked,
@@ -203,6 +209,24 @@ class _HomeFeedOverlayState extends State<HomeFeedOverlay> {
onSeek: widget.onSeek,
),
),
Positioned(
left: 0,
right: 0,
bottom: 0,
child: AnimatedOpacity(
duration: _overlayFadeDuration,
opacity: widget.visible && !_scrubbing && !widget.boosting
? 1
: 0,
child: IgnorePointer(
ignoring: !widget.visible || _scrubbing || widget.boosting,
child: HomeFeedWatchAllBar(
drama: widget.drama,
onTap: widget.onOpenFullPlayer,
),
),
),
),
],
),
);
@@ -273,7 +297,7 @@ class _HomeFeedOverlayState extends State<HomeFeedOverlay> {
onSave: widget.onSave,
onLike: widget.onLike,
onComments: widget.onComments,
onEpisodes: widget.onEpisodes ?? widget.onOpenFullPlayer,
onShare: widget.onShare,
);
}
@@ -283,6 +307,7 @@ class _HomeFeedOverlayState extends State<HomeFeedOverlay> {
episode: widget.episode,
onDetail: widget.onDetail,
onOpenFullPlayer: widget.onOpenFullPlayer,
onOpenSearch: widget.onOpenSearch,
);
}
}
@@ -6,7 +6,6 @@ const double _thumbSize = 6;
const double _thumbActiveSize = 14;
const double _trackActiveHeight = 6;
const double _lineCenterFromBottom = 8;
const Duration _trackResizeDuration = Duration(milliseconds: 120);
const double _timeBubbleWidth = 96;
const double _timeBubbleMargin = 12;
const Key _inactiveTrackKey = Key('home-feed-progress-inactive');
@@ -371,10 +370,8 @@ class _FeedProgressBarState extends State<_FeedProgressBar> {
children: [
Align(
alignment: Alignment.centerLeft,
child: AnimatedContainer(
child: Container(
key: const Key('home-feed-progress-track'),
duration: _trackResizeDuration,
curve: Curves.easeOut,
width: width,
height: trackHeight,
color: AppColors.border,
@@ -382,10 +379,8 @@ class _FeedProgressBarState extends State<_FeedProgressBar> {
),
Align(
alignment: Alignment.centerLeft,
child: AnimatedContainer(
child: Container(
key: const Key('home-feed-progress-played'),
duration: dragging ? Duration.zero : _trackResizeDuration,
curve: Curves.easeOut,
width: playedWidth,
height: trackHeight,
color: AppColors.blue,
@@ -401,10 +396,8 @@ class _FeedProgressBarState extends State<_FeedProgressBar> {
child: SizedBox.square(
dimension: _thumbActiveSize,
child: Center(
child: AnimatedContainer(
child: Container(
key: const Key('home-feed-progress-thumb'),
duration: _trackResizeDuration,
curve: Curves.easeOut,
width: size,
height: size,
decoration: const BoxDecoration(
@@ -1,8 +1,7 @@
// Author: fruit.com.cn
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import '../../../core/data/models/drama.dart';
import '../../../core/formatting/localized_content.dart';
@@ -11,15 +10,22 @@ import '../../../l10n/app_localizations.dart';
import '../../home/widgets/home_drama_classification.dart';
import 'interaction_rail.dart';
const double _seriesFont = 19;
const double _tagFont = 13;
const double _synopsisFont = 13;
const double _seriesFont = 21;
const double _tagFont = 14;
const double _synopsisFont = 15;
const double _watchAllHeight = 44;
const List<Shadow> _textShadows = [
Shadow(color: AppColors.shadow, blurRadius: 6, offset: Offset(0, 1)),
];
final RegExp _tagSplitter = RegExp(r'[,/、·]');
/// 通栏「观看完整剧集」半透明白底条高度,overlay 用它安排底部避让。
const double homeFeedWatchAllHeight = _watchAllHeight;
/// 播放量紧凑展示(跟随语言):zh 20万 / en 200K / id 200 rb。
String _compactViewers(int count, Locale locale) =>
NumberFormat.compact(locale: locale.languageCode).format(count);
class HomeFeedInfoPanel extends StatefulWidget {
const HomeFeedInfoPanel({
super.key,
@@ -27,6 +33,7 @@ class HomeFeedInfoPanel extends StatefulWidget {
required this.episode,
required this.onDetail,
required this.onOpenFullPlayer,
this.onOpenSearch,
});
final Drama drama;
@@ -34,6 +41,9 @@ class HomeFeedInfoPanel extends StatefulWidget {
final VoidCallback onDetail;
final VoidCallback onOpenFullPlayer;
/// 热搜胶囊点击;为空或播放量为 0 时不显示胶囊。
final VoidCallback? onOpenSearch;
@override
State<HomeFeedInfoPanel> createState() => _HomeFeedInfoPanelState();
}
@@ -48,9 +58,22 @@ class _HomeFeedInfoPanelState extends State<HomeFeedInfoPanel> {
final synopsis = localizedDramaSynopsis(l10n, widget.drama).trim();
final genres = _genreValues(Localizations.localeOf(context).languageCode);
return Column(
key: const Key('home-feed-info-panel'),
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (widget.onOpenSearch != null && widget.drama.viewCount > 0) ...[
_HotSearchPill(
label: l10n.feedWatchingCount(
_compactViewers(
widget.drama.viewCount,
Localizations.localeOf(context),
),
),
onTap: widget.onOpenSearch!,
),
const SizedBox(height: 8),
],
GestureDetector(
key: const Key('home-feed-series-name'),
behavior: HitTestBehavior.opaque,
@@ -67,12 +90,12 @@ class _HomeFeedInfoPanelState extends State<HomeFeedInfoPanel> {
style: const TextStyle(
color: AppColors.textPrimary,
fontSize: _seriesFont,
fontWeight: FontWeight.w600,
fontWeight: FontWeight.w700,
shadows: _textShadows,
),
),
),
const SizedBox(width: 4),
const SizedBox(width: 2),
const Icon(
Icons.chevron_right_rounded,
size: 20,
@@ -81,119 +104,96 @@ class _HomeFeedInfoPanelState extends State<HomeFeedInfoPanel> {
],
),
),
const SizedBox(height: 8),
const SizedBox(height: 6),
_tagMetaRow(l10n, genres),
if (synopsis.isNotEmpty) ...[
const SizedBox(height: 8),
_synopsis(l10n, synopsis),
],
const SizedBox(height: 12),
_watchAll(context, l10n),
],
);
}
Widget _tagMetaRow(AppLocalizations l10n, List<String> genres) {
return Row(
final chips = <Widget>[
if (isHotDrama(widget.drama))
_TagChip(
key: const Key('home-feed-hot-badge'),
label: l10n.homeBadgeHot,
filled: true,
),
for (final genre in genres)
_TagChip(key: Key('home-feed-tag-$genre'), label: genre),
];
return SizedBox(
key: const Key('home-feed-tags'),
children: [
if (isHotDrama(widget.drama)) ...[
Container(
key: const Key('home-feed-hot-badge'),
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: AppColors.error,
borderRadius: BorderRadius.circular(3),
),
child: Text(
l10n.homeBadgeHot,
style: const TextStyle(
color: AppColors.textPrimary,
fontSize: 10,
fontWeight: FontWeight.w600,
),
),
),
const SizedBox(width: 8),
],
Flexible(
fit: FlexFit.loose,
child: Text(
genres.join(' '),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.overlayLabel,
fontSize: _tagFont,
),
),
),
const SizedBox(width: 8),
Flexible(
child: Text(
l10n.favoriteEpisodeProgress(
widget.episode.number,
widget.drama.episodeCount,
),
key: const Key('home-feed-episode-progress'),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.overlayLabel,
fontSize: _tagFont,
),
),
),
],
height: 25,
child: ListView.separated(
scrollDirection: Axis.horizontal,
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: chips.length,
separatorBuilder: (_, _) => const SizedBox(width: 8),
itemBuilder: (_, index) => chips[index],
),
);
}
Widget _synopsis(AppLocalizations l10n, String synopsis) {
const style = TextStyle(
color: AppColors.overlaySecondary,
final prefix = l10n.feedEpisodePrefix(widget.episode.number);
final baseStyle = const TextStyle(
color: AppColors.textPrimary,
fontSize: _synopsisFont,
height: 1.35,
height: 1.3,
shadows: _textShadows,
);
final span = TextSpan(
children: [
TextSpan(
text: prefix,
style: baseStyle.copyWith(fontWeight: FontWeight.w600),
),
TextSpan(text: synopsis, style: baseStyle),
],
);
if (_synopsisExpanded) {
return _expandedSynopsis(span, baseStyle, l10n);
}
return LayoutBuilder(
builder: (context, constraints) {
final toggle = _synopsisToggle(
key: const Key('home-feed-synopsis-more'),
label: l10n.expandSynopsis,
);
final painter = TextPainter(
text: TextSpan(text: synopsis, style: style),
maxLines: 2,
text: span,
maxLines: 1,
textDirection: Directionality.of(context),
textScaler: MediaQuery.textScalerOf(context),
)..layout(maxWidth: constraints.maxWidth);
final overflows = painter.didExceedMaxLines;
if (!overflows) {
return Text(
synopsis,
if (!painter.didExceedMaxLines) {
return Text.rich(
span,
key: const Key('home-feed-synopsis'),
style: style,
maxLines: 1,
style: baseStyle,
);
}
if (_synopsisExpanded) {
return _expandedSynopsis(constraints.maxWidth, synopsis, style, l10n);
}
return Stack(
// 设计稿:单行省略号 + 行内固定「展开」。
return Row(
key: const Key('home-feed-synopsis-collapsed'),
children: [
Padding(
padding: const EdgeInsets.only(right: 40),
child: Text(
synopsis,
Expanded(
child: Text.rich(
span,
key: const Key('home-feed-synopsis'),
maxLines: 2,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: style,
),
),
Positioned(
right: 0,
bottom: 0,
child: _synopsisToggle(
key: const Key('home-feed-synopsis-more'),
label: l10n.expandSynopsis,
style: baseStyle,
),
),
const SizedBox(width: 2),
toggle,
],
);
},
@@ -201,26 +201,28 @@ class _HomeFeedInfoPanelState extends State<HomeFeedInfoPanel> {
}
Widget _expandedSynopsis(
double maxWidth,
String synopsis,
InlineSpan span,
TextStyle style,
AppLocalizations l10n,
) {
return SizedBox(
width: maxWidth,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(synopsis, key: const Key('home-feed-synopsis'), style: style),
Align(
alignment: Alignment.centerRight,
child: _synopsisToggle(
key: const Key('home-feed-synopsis-more'),
label: l10n.showLess,
),
return Column(
key: const Key('home-feed-synopsis-expanded'),
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text.rich(
span,
key: const Key('home-feed-synopsis'),
style: style,
),
const SizedBox(height: 2),
Align(
alignment: Alignment.centerRight,
child: _synopsisToggle(
key: const Key('home-feed-synopsis-more'),
label: l10n.showLess,
),
],
),
),
],
);
}
@@ -234,54 +236,12 @@ class _HomeFeedInfoPanelState extends State<HomeFeedInfoPanel> {
color: AppColors.textPrimary,
fontSize: _synopsisFont,
fontWeight: FontWeight.w600,
shadows: _textShadows,
),
),
);
}
Widget _watchAll(BuildContext context, AppLocalizations l10n) {
final screenWidth = MediaQuery.sizeOf(context).width;
return LayoutBuilder(
builder: (context, constraints) {
final buttonWidth = math.min(screenWidth * 0.7, constraints.maxWidth);
return Align(
alignment: Alignment.centerLeft,
child: SizedBox(
width: buttonWidth,
height: _watchAllHeight,
child: DecoratedBox(
key: const Key('home-feed-full-series-bg'),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [AppColors.actionRed, AppColors.blue],
),
borderRadius: BorderRadius.circular(8),
),
child: Material(
color: AppColors.transparent,
child: InkWell(
key: const Key('home-feed-full-series'),
onTap: widget.onOpenFullPlayer,
borderRadius: BorderRadius.circular(8),
child: Center(
child: Text(
l10n.homeWatchAll,
style: const TextStyle(
color: AppColors.textPrimary,
fontSize: 15,
fontWeight: FontWeight.w600,
),
),
),
),
),
),
),
);
},
);
}
List<String> _genreValues(String languageCode) {
final localized = widget.drama.localizedTypeLabels(languageCode);
if (localized.isNotEmpty) return localized.take(3).toList(growable: false);
@@ -297,6 +257,147 @@ class _HomeFeedInfoPanelState extends State<HomeFeedInfoPanel> {
}
}
/// 热搜榜降级胶囊:🔍 共 xxx 人在看 ›(暂无榜单接口,点击跳搜索页)。
class _HotSearchPill extends StatelessWidget {
const _HotSearchPill({required this.label, required this.onTap});
final String label;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return GestureDetector(
key: const Key('home-feed-hot-search-pill'),
behavior: HitTestBehavior.opaque,
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration(
color: AppColors.textPrimary.withValues(alpha: 0.14),
borderRadius: BorderRadius.circular(6),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
Icons.search_rounded,
size: 15,
color: AppColors.textPrimary,
),
const SizedBox(width: 5),
Text(
label,
maxLines: 1,
style: const TextStyle(
color: AppColors.textPrimary,
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
const SizedBox(width: 2),
const Icon(
Icons.chevron_right_rounded,
size: 16,
color: AppColors.textPrimary,
),
],
),
),
);
}
}
/// 热剧红胶囊 / 类型深色半透明填充胶囊。
class _TagChip extends StatelessWidget {
const _TagChip({super.key, required this.label, this.filled = false});
final String label;
final bool filled;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 2),
alignment: Alignment.center,
decoration: BoxDecoration(
color: filled
? AppColors.brandRed
: AppColors.textPrimary.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(4),
),
child: Text(
label,
maxLines: 1,
style: const TextStyle(
color: AppColors.textPrimary,
fontSize: _tagFont,
fontWeight: FontWeight.w500,
height: 1.2,
shadows: _textShadows,
),
),
);
}
}
/// 底部通栏条:🎬 观看完整剧集 全 N 集。
class HomeFeedWatchAllBar extends StatelessWidget {
const HomeFeedWatchAllBar({
super.key,
required this.drama,
required this.onTap,
});
final Drama drama;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Material(
key: const Key('home-feed-full-series-bg'),
color: AppColors.textPrimary.withValues(alpha: 0.15),
child: InkWell(
key: const Key('home-feed-full-series'),
onTap: onTap,
child: SizedBox(
height: _watchAllHeight,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Row(
children: [
const Icon(
Icons.movie_rounded,
size: 22,
color: AppColors.onPrimary,
),
const SizedBox(width: 10),
Expanded(
child: Text(
l10n.watchFullSeries('${drama.episodeCount}'),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.textPrimary,
fontSize: 15,
fontWeight: FontWeight.w500,
),
),
),
const Icon(
Icons.chevron_right_rounded,
size: 22,
color: AppColors.textPrimary,
),
],
),
),
),
),
);
}
}
class HomeFeedActionRail extends StatelessWidget {
const HomeFeedActionRail({
super.key,
@@ -305,7 +406,7 @@ class HomeFeedActionRail extends StatelessWidget {
required this.onSave,
required this.onLike,
required this.onComments,
required this.onEpisodes,
required this.onShare,
this.favoriteCount,
this.likeCount,
this.commentCount,
@@ -319,13 +420,17 @@ class HomeFeedActionRail extends StatelessWidget {
final VoidCallback onSave;
final VoidCallback onLike;
final VoidCallback onComments;
final VoidCallback onEpisodes;
final VoidCallback onShare;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return InteractionRail(
itemGap: 14,
itemGap: 18,
buttonSize: 48,
iconSize: 36,
countFontSize: 15,
iconCountGap: 6,
items: [
InteractionRailItem(
key: const Key('rail-save'),
@@ -334,18 +439,9 @@ class HomeFeedActionRail extends StatelessWidget {
count: favoriteCount,
countKey: const Key('rail-save-count'),
selected: saved,
selectedColor: AppColors.brandRed,
onTap: onSave,
),
InteractionRailItem(
key: const Key('rail-like'),
asset: 'assets/icon/rail/rail_like.png',
label: l10n.like,
count: likeCount,
countKey: const Key('rail-like-count'),
selected: liked,
selectedColor: AppColors.doubleTapHeartRed,
onTap: onLike,
),
InteractionRailItem(
key: const Key('rail-comments'),
asset: 'assets/icon/rail/rail_comment.png',
@@ -355,11 +451,22 @@ class HomeFeedActionRail extends StatelessWidget {
onTap: onComments,
),
InteractionRailItem(
key: const Key('rail-episodes'),
icon: Icons.layers_rounded,
label: l10n.episodes,
countKey: const Key('rail-episodes-label'),
onTap: onEpisodes,
key: const Key('rail-like'),
asset: 'assets/icon/rail/rail_like.png',
label: l10n.like,
count: likeCount,
countKey: const Key('rail-like-count'),
selected: liked,
selectedColor: AppColors.brandRed,
onTap: onLike,
),
InteractionRailItem(
key: const Key('rail-share'),
icon: Icons.reply_rounded,
mirrorIcon: true,
label: l10n.share,
countKey: const Key('rail-share-label'),
onTap: onShare,
),
],
);
+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),
],
),
),
],