// 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 categories; final List tags; final String? selectedCategory; final String? selectedTag; final ValueChanged onCategorySelected; final ValueChanged 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, ), ), ), ); } }