Files
ReelShort/lib/pages/recommend/widgets/recommend_top_nav.dart

197 lines
6.1 KiB
Dart

// Author: fruit.com.cn
import 'package:flutter/material.dart';
import '../../../core/theme/app_colors.dart';
import '../../../core/theme/app_dimens.dart';
import '../../../core/widgets/animated_nav_label.dart';
import '../../../l10n/app_localizations.dart';
import '../recommend_types.dart';
import 'search_gift_glyphs.dart';
const double _navHeight = 56;
const double _tabGap = 20;
const double _tabFont = 18;
const double _maxNavigationTextScale = 1.3;
const double _searchIconSize = 26;
const double _giftIconSize = 26;
const double _underlineWidth = 26;
const double _underlineHeight = 3;
class RecommendTopNav extends StatelessWidget {
const RecommendTopNav({
super.key,
required this.selected,
required this.onSelected,
required this.onSearch,
this.onVideo = false,
this.onGift,
this.pagePosition,
});
static const double height = _navHeight;
final RecommendInnerTab selected;
final ValueChanged<RecommendInnerTab> onSelected;
final VoidCallback onSearch;
final bool onVideo;
/// 首页横向页面的连续位置,用于文字和下划线跟手过渡。
final double? pagePosition;
/// 右上角礼包入口;为空时不显示。
final VoidCallback? onGift;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final items = [
(RecommendInnerTab.feed, l10n.recommendTopTab, 'recommend'),
(RecommendInnerTab.playlists, l10n.playlistTopTab, 'playlist'),
(RecommendInnerTab.allDramas, l10n.allDramasTopTab, 'all'),
];
final shadows = onVideo
? const [Shadow(color: AppColors.mediaBlack, blurRadius: 6)]
: null;
return MediaQuery.withClampedTextScaling(
maxScaleFactor: _maxNavigationTextScale,
child: SizedBox(
key: const Key('recommend-top-nav'),
height: height,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.only(left: 20, right: 8),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
for (var index = 0; index < items.length; index++) ...[
if (index > 0) const SizedBox(width: _tabGap),
_tab(items[index], shadows, context),
],
],
),
),
),
SizedBox.square(
dimension: AppDimens.tapTarget,
child: IconButton(
key: const Key('recommend-search'),
tooltip: l10n.search,
onPressed: onSearch,
padding: EdgeInsets.zero,
constraints: const BoxConstraints.tightFor(
width: AppDimens.tapTarget,
height: AppDimens.tapTarget,
),
iconSize: _searchIconSize,
color: AppColors.textPrimary,
icon: SearchGlyph(
size: _searchIconSize,
color: AppColors.textPrimary,
),
),
),
if (onGift != null)
SizedBox.square(
dimension: AppDimens.tapTarget,
child: IconButton(
key: const Key('recommend-gift'),
tooltip: l10n.wallet,
onPressed: onGift,
padding: EdgeInsets.zero,
constraints: const BoxConstraints.tightFor(
width: AppDimens.tapTarget,
height: AppDimens.tapTarget,
),
iconSize: _giftIconSize,
icon: GiftGlyph(size: _giftIconSize),
),
),
const SizedBox(width: 8),
],
),
),
);
}
Widget _tab(
(RecommendInnerTab, String, String) item,
List<Shadow>? shadows,
BuildContext context,
) {
final (tab, label, code) = item;
final isSelected = selected == tab;
final position = pagePosition;
final progress = position == null
? null
: (1 - (position - tab.index).abs()).clamp(0.0, 1.0);
return GestureDetector(
key: Key('recommend-tab-$code'),
behavior: HitTestBehavior.opaque,
onTap: () => onSelected(tab),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 补偿文字下方的 4pt 间距 + 3pt 下划线,让文字视觉中心与右侧图标对齐。
const SizedBox(height: 4 + _underlineHeight),
AnimatedNavLabel(
label: label,
selected: isSelected,
selectionProgress: progress,
inactiveColor: AppColors.homeTabInactive,
style: TextStyle(
color: AppColors.textPrimary,
fontSize: _tabFont,
fontWeight: FontWeight.w400,
letterSpacing: 0,
height: 1.0,
shadows: shadows,
),
),
const SizedBox(height: 4),
_underline(
code,
progress,
isSelected,
MediaQuery.disableAnimationsOf(context),
),
],
),
);
}
Widget _underline(
String code,
double? progress,
bool isSelected,
bool reduceMotion,
) {
if (progress != null) return _line(code, progress);
final target = isSelected ? 1.0 : 0.0;
return TweenAnimationBuilder<double>(
tween: Tween(begin: target, end: target),
duration: reduceMotion
? Duration.zero
: AppDimens.homePrimaryTabAnimationDuration,
curve: Curves.easeOut,
builder: (_, value, _) => _line(code, value),
);
}
Widget _line(String code, double progress) => Container(
key: Key('recommend-tab-$code-underline'),
width: _underlineWidth,
height: _underlineHeight,
decoration: BoxDecoration(
color: progress == 0
? AppColors.transparent
: AppColors.brandRed.withValues(alpha: progress),
borderRadius: BorderRadius.circular(_underlineHeight / 2),
),
);
}