Files

267 lines
7.2 KiB
Dart

// Author: fruit.com.cn
import 'package:flutter/material.dart';
import '../../../core/theme/app_colors.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<HomeFeedChannel> onChannelSelected;
final Future<bool> Function() onRefresh;
final VoidCallback onSearch;
final VoidCallback onMore;
@override
State<HomeFeedHeader> createState() => _HomeFeedHeaderState();
}
class _HomeFeedHeaderState extends State<HomeFeedHeader> {
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<void> _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: AppColors.onPrimary,
shadows: const [Shadow(blurRadius: 6, color: AppColors.mediaBlack)],
),
),
);
}
}
/// 刷新入口:点击重新加载 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: AppColors.onPrimary,
),
),
)
: Icon(
Icons.refresh_rounded,
size: glyphSize,
color: AppColors.onPrimary,
shadows: const [
Shadow(blurRadius: 6, color: AppColors.mediaBlack),
],
),
),
),
);
}
}
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 ? AppColors.onPrimary : AppColors.muted,
fontSize: fontSize,
fontWeight: selected ? FontWeight.w800 : FontWeight.w600,
shadows: const [
Shadow(blurRadius: 6, color: AppColors.mediaBlack),
],
),
),
const SizedBox(height: 4),
Container(
width: underlineWidth,
height: underlineHeight,
decoration: BoxDecoration(
color: selected ? AppColors.onPrimary : AppColors.transparent,
borderRadius: BorderRadius.circular(1),
),
),
],
),
);
}
String _keyName(HomeFeedChannel channel) => switch (channel) {
HomeFeedChannel.anime => 'anime',
HomeFeedChannel.live => 'real',
HomeFeedChannel.recommend => 'recommend',
};
}