Files
ReelShort/lib/pages/home/widgets/home_grid_header.dart
T

366 lines
11 KiB
Dart

// Author: fruit.com.cn
import 'package:flutter/material.dart';
import '../../../core/formatting/count_format.dart';
import '../../../core/theme/app_colors.dart';
import '../../../core/theme/app_dimens.dart';
import '../../../l10n/app_localizations.dart';
class HomeGridCategoryShortcut {
const HomeGridCategoryShortcut({
required this.label,
required this.categoryName,
});
final String label;
final String categoryName;
}
class HomeGridTagOption {
const HomeGridTagOption({required this.id, required this.label});
final String id;
final String label;
}
class HomeGridCallbacks {
const HomeGridCallbacks({
required this.onSearch,
required this.onOpenVip,
required this.onOpenWallet,
required this.onOpenNewReleases,
required this.onOpenCategories,
required this.onOpenRanking,
required this.onOpenFilter,
});
final VoidCallback onSearch;
final VoidCallback onOpenVip;
final VoidCallback onOpenWallet;
final VoidCallback onOpenNewReleases;
final VoidCallback onOpenCategories;
final VoidCallback onOpenRanking;
final VoidCallback onOpenFilter;
}
class HomeGridHeader extends StatelessWidget {
const HomeGridHeader({
super.key,
required this.callbacks,
required this.categories,
required this.tags,
required this.selectedCategory,
required this.selectedTag,
required this.onCategorySelected,
required this.onTagSelected,
this.walletBadge,
});
final HomeGridCallbacks callbacks;
final List<HomeGridCategoryShortcut> categories;
final List<HomeGridTagOption> tags;
final String? selectedCategory;
final String? selectedTag;
final ValueChanged<String?> onCategorySelected;
final ValueChanged<String?> onTagSelected;
final int? walletBadge;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Column(
key: const Key('home-grid-header'),
children: [_searchRow(l10n), _primaryRow(l10n), _tagRow(l10n)],
);
}
Widget _searchRow(AppLocalizations l10n) {
return Padding(
padding: const EdgeInsets.fromLTRB(
AppDimens.pageHorizontal,
AppDimens.spaceSm,
AppDimens.pageHorizontal,
AppDimens.spaceSm,
),
child: Row(
children: [
Expanded(
child: InkWell(
key: const Key('home-search-entry'),
borderRadius: BorderRadius.circular(AppDimens.inputRadius),
onTap: callbacks.onSearch,
child: Container(
height: AppDimens.searchHeight,
padding: const EdgeInsets.symmetric(
horizontal: AppDimens.spaceMd,
),
decoration: BoxDecoration(
color: AppColors.contentSurface,
borderRadius: BorderRadius.circular(AppDimens.inputRadius),
),
child: Row(
children: [
const Icon(
Icons.search_rounded,
size: AppDimens.homeHeaderIcon,
color: AppColors.textSecondary,
),
const SizedBox(width: AppDimens.spaceSm),
Expanded(
child: Text(
l10n.homeSearchHint,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.textSecondary,
fontSize: AppDimens.body,
),
),
),
],
),
),
),
),
const SizedBox(width: AppDimens.spaceSm),
_headerAction(
key: const Key('home-vip-entry'),
icon: Icons.workspace_premium_outlined,
tooltip: l10n.vipMembership,
onTap: callbacks.onOpenVip,
),
const SizedBox(width: AppDimens.spaceXs),
_headerAction(
key: const Key('home-wallet-entry'),
icon: Icons.card_giftcard_rounded,
tooltip: l10n.wallet,
onTap: callbacks.onOpenWallet,
badge: walletBadge,
),
],
),
);
}
Widget _headerAction({
required Key key,
required IconData icon,
required String tooltip,
required VoidCallback onTap,
int? badge,
}) {
return SizedBox.square(
dimension: AppDimens.homeIconSlot,
child: Stack(
clipBehavior: Clip.none,
children: [
Positioned.fill(
child: Tooltip(
message: tooltip,
child: Material(
color: AppColors.contentSurface,
borderRadius: BorderRadius.circular(AppDimens.inputRadius),
child: InkWell(
key: key,
borderRadius: BorderRadius.circular(AppDimens.inputRadius),
onTap: onTap,
child: Icon(
icon,
size: AppDimens.homeHeaderIcon,
color: AppColors.navSelected,
),
),
),
),
),
if (badge != null && badge > 0)
Positioned(
key: const Key('home-wallet-badge'),
top: -5,
right: -6,
child: Container(
constraints: const BoxConstraints(minWidth: 20, minHeight: 16),
padding: const EdgeInsets.symmetric(horizontal: 5),
alignment: Alignment.center,
decoration: BoxDecoration(
color: AppColors.badgeHot,
borderRadius: BorderRadius.circular(AppDimens.pillRadius),
),
child: Text(
formatInteractionCount(badge),
style: const TextStyle(
color: AppColors.navSelected,
fontSize: AppDimens.homeCardBadge,
fontWeight: FontWeight.w700,
),
),
),
),
],
),
);
}
Widget _primaryRow(AppLocalizations l10n) {
return SingleChildScrollView(
key: const Key('home-primary-sections'),
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: AppDimens.pageHorizontal),
child: Row(
children: [
_primaryItem(
key: const Key('home-section-featured'),
label: l10n.homeSectionFeatured,
selected: selectedCategory == null,
onTap: () => onCategorySelected(null),
),
_primaryItem(
key: const Key('home-section-new'),
label: l10n.homeSectionNew,
selected: false,
onTap: callbacks.onOpenNewReleases,
),
_primaryItem(
key: const Key('home-section-categories'),
label: l10n.homeSectionCategories,
selected: false,
onTap: callbacks.onOpenCategories,
),
_primaryItem(
key: const Key('home-section-ranking'),
label: l10n.homeSectionRanking,
selected: false,
onTap: callbacks.onOpenRanking,
),
for (final category in categories)
_primaryItem(
key: Key('home-section-category-${category.categoryName}'),
label: category.label,
selected: selectedCategory == category.categoryName,
onTap: () => onCategorySelected(category.categoryName),
),
],
),
);
}
Widget _primaryItem({
required Key key,
required String label,
required bool selected,
required VoidCallback onTap,
}) {
return InkWell(
key: key,
onTap: onTap,
child: Padding(
padding: const EdgeInsets.only(right: AppDimens.spaceXl),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
label,
style: TextStyle(
color: selected
? AppColors.navSelected
: AppColors.textSecondary,
fontSize: AppDimens.homeSectionFont,
fontWeight: selected ? FontWeight.w700 : FontWeight.w500,
letterSpacing: 0,
),
),
const SizedBox(height: AppDimens.spaceXs),
Container(
height: AppDimens.homeSectionIndicator,
width: selected ? 20 : 0,
decoration: BoxDecoration(
color: AppColors.navSelected,
borderRadius: BorderRadius.circular(AppDimens.pillRadius),
),
),
],
),
),
);
}
Widget _tagRow(AppLocalizations l10n) {
return Padding(
padding: const EdgeInsets.fromLTRB(
AppDimens.pageHorizontal,
AppDimens.spaceMd,
AppDimens.pageHorizontal,
AppDimens.spaceSm,
),
child: Row(
children: [
Expanded(
child: SingleChildScrollView(
key: const Key('home-secondary-tags'),
scrollDirection: Axis.horizontal,
child: Row(
children: [
for (final tag in tags) ...[
_tagChip(tag),
const SizedBox(width: AppDimens.spaceSm),
],
],
),
),
),
const SizedBox(width: AppDimens.spaceXs),
Tooltip(
message: l10n.filter,
child: SizedBox.square(
dimension: AppDimens.tapTarget,
child: Material(
color: AppColors.contentSurface,
borderRadius: BorderRadius.circular(AppDimens.inputRadius),
child: InkWell(
key: const Key('home-grid-filter'),
borderRadius: BorderRadius.circular(AppDimens.inputRadius),
onTap: callbacks.onOpenFilter,
child: const Icon(
Icons.filter_list_rounded,
color: AppColors.navSelected,
size: AppDimens.homeHeaderIcon,
),
),
),
),
),
],
),
);
}
Widget _tagChip(HomeGridTagOption tag) {
final selected = selectedTag == tag.id;
return InkWell(
key: Key('home-tag-${tag.id}'),
borderRadius: BorderRadius.circular(AppDimens.pillRadius),
onTap: () => onTagSelected(selected ? null : tag.id),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: AppDimens.spaceMd,
vertical: AppDimens.spaceSm,
),
decoration: BoxDecoration(
color: selected ? AppColors.contentChip : AppColors.contentSurface,
borderRadius: BorderRadius.circular(AppDimens.pillRadius),
),
child: Text(
tag.label,
style: TextStyle(
color: selected ? AppColors.navSelected : AppColors.textSecondary,
fontSize: AppDimens.homeSectionFont - 2,
fontWeight: selected ? FontWeight.w600 : FontWeight.w500,
letterSpacing: 0,
),
),
),
);
}
}