// Author: fruit.com.cn import 'package:flutter/material.dart'; import '../../home/home_feed_channel.dart'; import '../../../l10n/app_localizations.dart'; const double _topIconSize = 36; const double _topIconGlyph = 22; const double _tabGap = 22; const double _tabFontSize = 15; const double _underlineWidth = 18; const double _underlineHeight = 2; /// 首页 feed 固定顶栏:刷新 / 漫剧·真人剧·推荐 / 搜索。 class HomeFeedHeader extends StatefulWidget { const HomeFeedHeader({ super.key, required this.l10n, required this.channel, required this.onChannelSelected, required this.onRefresh, required this.onSearch, required this.onMore, }); final AppLocalizations l10n; final HomeFeedChannel channel; final ValueChanged onChannelSelected; final Future Function() onRefresh; final VoidCallback onSearch; final VoidCallback onMore; @override State createState() => _HomeFeedHeaderState(); } class _HomeFeedHeaderState extends State { bool _refreshing = false; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.fromLTRB(8, 6, 8, 4), child: Row( children: [ _RefreshButton( tooltip: widget.l10n.retry, dimension: _topIconSize, glyphSize: _topIconGlyph, refreshing: _refreshing, onTap: _refresh, onLongPress: widget.onMore, ), Expanded( child: Center( child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: [ for ( var index = 0; index < HomeFeedChannel.values.length; index++ ) _tabSlot(HomeFeedChannel.values[index], index), ], ), ), ), ), _TopIconButton( key: const Key('home-feed-search'), tooltip: widget.l10n.search, icon: Icons.search_rounded, dimension: _topIconSize, glyphSize: _topIconGlyph, onPressed: widget.onSearch, ), ], ), ); } Widget _tabSlot(HomeFeedChannel channel, int index) { final item = _FeedTabItem( channel: channel, label: _label(channel), selected: widget.channel == channel, fontSize: _tabFontSize, underlineWidth: _underlineWidth, underlineHeight: _underlineHeight, onTap: () => widget.onChannelSelected(channel), ); if (index == 0) return item; return Padding( padding: const EdgeInsets.only(left: _tabGap), child: item, ); } Future _refresh() async { if (_refreshing) return; setState(() => _refreshing = true); await widget.onRefresh(); if (mounted) setState(() => _refreshing = false); } String _label(HomeFeedChannel channel) => switch (channel) { HomeFeedChannel.anime => widget.l10n.feedTabAnime, HomeFeedChannel.live => widget.l10n.feedTabReal, HomeFeedChannel.recommend => widget.l10n.feedTabForYou, }; } class _TopIconButton extends StatelessWidget { const _TopIconButton({ super.key, required this.tooltip, required this.icon, required this.dimension, required this.glyphSize, required this.onPressed, }); final String tooltip; final IconData icon; final double dimension; final double glyphSize; final VoidCallback onPressed; @override Widget build(BuildContext context) { return SizedBox.square( dimension: dimension, child: IconButton( tooltip: tooltip, onPressed: onPressed, padding: EdgeInsets.zero, icon: Icon( icon, size: glyphSize, color: Colors.white, shadows: const [Shadow(blurRadius: 6, color: Colors.black)], ), ), ); } } /// 刷新入口:点击重新加载 feed,长按打开更多(清屏等)。 class _RefreshButton extends StatelessWidget { const _RefreshButton({ required this.tooltip, required this.dimension, required this.glyphSize, required this.refreshing, required this.onTap, required this.onLongPress, }); final String tooltip; final double dimension; final double glyphSize; final bool refreshing; final VoidCallback onTap; final VoidCallback onLongPress; @override Widget build(BuildContext context) { return Tooltip( message: tooltip, child: GestureDetector( key: const Key('home-feed-refresh'), behavior: HitTestBehavior.opaque, onTap: onTap, onLongPress: onLongPress, child: SizedBox.square( dimension: dimension, child: refreshing ? const Center( child: SizedBox.square( dimension: 18, child: CircularProgressIndicator( key: Key('home-feed-refreshing'), strokeWidth: 2, color: Colors.white, ), ), ) : Icon( Icons.refresh_rounded, size: glyphSize, color: Colors.white, shadows: const [Shadow(blurRadius: 6, color: Colors.black)], ), ), ), ); } } class _FeedTabItem extends StatelessWidget { const _FeedTabItem({ required this.channel, required this.label, required this.selected, required this.fontSize, required this.underlineWidth, required this.underlineHeight, required this.onTap, }); final HomeFeedChannel channel; final String label; final bool selected; final double fontSize; final double underlineWidth; final double underlineHeight; final VoidCallback onTap; @override Widget build(BuildContext context) { return GestureDetector( key: Key('home-feed-tab-${_keyName(channel)}'), behavior: HitTestBehavior.opaque, onTap: onTap, child: Column( mainAxisSize: MainAxisSize.min, children: [ Text( label, maxLines: 1, style: TextStyle( color: selected ? Colors.white : Colors.white70, fontSize: fontSize, fontWeight: selected ? FontWeight.w800 : FontWeight.w600, shadows: const [Shadow(blurRadius: 6, color: Colors.black)], ), ), const SizedBox(height: 4), Container( width: underlineWidth, height: underlineHeight, decoration: BoxDecoration( color: selected ? Colors.white : Colors.transparent, borderRadius: BorderRadius.circular(1), ), ), ], ), ); } String _keyName(HomeFeedChannel channel) => switch (channel) { HomeFeedChannel.anime => 'anime', HomeFeedChannel.live => 'real', HomeFeedChannel.recommend => 'recommend', }; }