// 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'; enum HomePrimaryTab { featured, newReleases, categories, ranking, costume, community, } class HomeGridCategoryShortcut { const HomeGridCategoryShortcut({ required this.label, required this.categoryName, required this.tab, }); final String label; final String categoryName; final HomePrimaryTab tab; } 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.onOpenFilter, }); final VoidCallback onSearch; final VoidCallback onOpenVip; final VoidCallback onOpenWallet; final VoidCallback onOpenFilter; } class HomeGridHeader extends StatelessWidget { const HomeGridHeader({ super.key, required this.callbacks, required this.categories, required this.tags, required this.selectedPrimary, required this.selectedTag, required this.onPrimarySelected, required this.onTagSelected, this.walletBadge, }); final HomeGridCallbacks callbacks; final List categories; final List tags; final HomePrimaryTab selectedPrimary; final String? selectedTag; final ValueChanged onPrimarySelected; final ValueChanged onTagSelected; final int? walletBadge; @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; return Column( key: const Key('home-grid-header'), crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ _searchRow(l10n), _primaryRow(l10n), if (selectedPrimary == HomePrimaryTab.featured) _tagRow(l10n) else const SizedBox(height: AppDimens.homeSecondaryGridGap), ], ); } Widget _searchRow(AppLocalizations l10n) { return Padding( padding: const EdgeInsets.fromLTRB( AppDimens.feedPageHorizontal, AppDimens.homeSearchTopGap, AppDimens.feedPageHorizontal, AppDimens.homeSearchPrimaryGap, ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ Expanded( child: InkWell( key: const Key('home-search-entry'), borderRadius: BorderRadius.circular(AppDimens.searchRadius), onTap: callbacks.onSearch, child: Container( height: AppDimens.searchHeight, padding: const EdgeInsets.symmetric( horizontal: AppDimens.spaceMd, ), decoration: BoxDecoration( color: AppColors.contentSurface, borderRadius: BorderRadius.circular(AppDimens.searchRadius), ), 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.searchPlaceholderFont, ), ), ), ], ), ), ), ), 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.cardBadgeFont, fontWeight: FontWeight.w700, ), ), ), ), ], ), ); } Widget _primaryRow(AppLocalizations l10n) { return SingleChildScrollView( key: const Key('home-primary-sections'), scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric( horizontal: AppDimens.feedPageHorizontal, ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ _primaryItem( key: const Key('home-section-featured'), label: l10n.homeSectionFeatured, tab: HomePrimaryTab.featured, ), _primaryItem( key: const Key('home-section-new'), label: l10n.homeSectionNew, tab: HomePrimaryTab.newReleases, ), _primaryItem( key: const Key('home-section-categories'), label: l10n.homeSectionCategories, tab: HomePrimaryTab.categories, ), _primaryItem( key: const Key('home-section-ranking'), label: l10n.homeSectionRanking, tab: HomePrimaryTab.ranking, ), for (final category in categories) _primaryItem( key: Key( category.categoryName.isEmpty ? 'home-section-${category.tab.name}' : 'home-section-category-${category.categoryName}', ), label: category.label, tab: category.tab, ), ], ), ); } Widget _primaryItem({ required Key key, required String label, required HomePrimaryTab tab, }) { final selected = selectedPrimary == tab; return InkWell( key: key, onTap: () => onPrimarySelected(tab), child: Padding( padding: const EdgeInsets.only(right: AppDimens.homePrimaryCategoryGap), child: SizedBox( height: AppDimens.homePrimaryRowHeight, child: Align( alignment: Alignment.bottomLeft, child: AnimatedDefaultTextStyle( key: Key('home-primary-label-${tab.name}'), duration: AppDimens.homePrimaryTabAnimationDuration, curve: Curves.easeOut, style: TextStyle( color: selected ? AppColors.navSelected : AppColors.textTabInactive, fontSize: selected ? AppDimens.homePrimaryTabSelectedFont : AppDimens.homePrimaryTabFont, fontWeight: selected ? FontWeight.w700 : FontWeight.w400, letterSpacing: 0, ), child: Text(label, textAlign: TextAlign.left), ), ), ), ), ); } Widget _tagRow(AppLocalizations l10n) { return Padding( padding: const EdgeInsets.fromLTRB( AppDimens.feedPageHorizontal, AppDimens.homePrimarySecondaryGap, AppDimens.feedPageHorizontal, AppDimens.homeSecondaryGridGap, ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ Expanded( child: SingleChildScrollView( key: const Key('home-secondary-tags'), scrollDirection: Axis.horizontal, child: Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ for (final tag in tags) ...[ _tagChip(tag), const SizedBox(width: AppDimens.homeSecondaryTagGap), ], ], ), ), ), 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; final label = Text( tag.label, style: TextStyle( color: selected ? AppColors.navSelected : AppColors.textTertiary, fontSize: AppDimens.homeSecondaryTagFont, fontWeight: selected ? FontWeight.w600 : FontWeight.w400, letterSpacing: 0, ), ); return InkWell( key: Key('home-tag-${tag.id}'), borderRadius: BorderRadius.circular(AppDimens.pillRadius), onTap: () => onTagSelected(selected ? null : tag.id), child: selected ? Container( height: AppDimens.homeSecondaryTagHeight, padding: const EdgeInsets.symmetric( horizontal: AppDimens.homeSecondaryTagHorizontalPadding, ), alignment: Alignment.center, decoration: BoxDecoration( color: AppColors.contentSurface, borderRadius: BorderRadius.circular( AppDimens.homeSecondaryTagRadius, ), ), child: label, ) : SizedBox( height: AppDimens.homeSecondaryTagHeight, child: Align(alignment: Alignment.centerLeft, child: label), ), ); } }