import 'package:flutter/material.dart'; import '../../../../app/theme/app_colors.dart'; import '../../../../app/theme/app_typography.dart'; import '../../../../app/theme/app_spacing.dart'; import '../../../../shared/widgets/coupon_card.dart'; import '../../../ai_agent/presentation/widgets/ai_fab.dart'; import '../widgets/receive_coupon_sheet.dart'; /// 首页 - 券钱包 + 分类网格 + AI推荐 + 精选券 /// /// Tab导航:首页/交易/消息/我的 class HomePage extends StatefulWidget { const HomePage({super.key}); @override State createState() => _HomePageState(); } class _HomePageState extends State { int _walletFilter = 0; // 0=全部, 1=可使用, 2=待核销, 3=已过期 @override Widget build(BuildContext context) { return Scaffold( body: Stack( children: [ CustomScrollView( slivers: [ // Floating App Bar SliverAppBar( floating: true, pinned: false, backgroundColor: AppColors.background, elevation: 0, toolbarHeight: 60, title: _buildSearchBar(context), actions: [ IconButton( icon: const Icon(Icons.qr_code_scanner_rounded, size: 24), onPressed: () {}, color: AppColors.textPrimary, ), ], ), // Coupon Wallet (replaces Banner) SliverToBoxAdapter(child: _buildCouponWallet(context)), // Category Grid (6 new categories) SliverToBoxAdapter(child: _buildCategoryGrid()), // AI Smart Suggestions SliverToBoxAdapter(child: _buildAiSuggestions()), // Section: Featured Coupons SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.fromLTRB(20, 24, 20, 12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('精选好券', style: AppTypography.h2), GestureDetector( onTap: () {}, child: Text('查看全部', style: AppTypography.labelSmall.copyWith( color: AppColors.primary, )), ), ], ), ), ), // Coupon List SliverPadding( padding: AppSpacing.pagePadding, sliver: SliverList( delegate: SliverChildBuilderDelegate( (context, index) => Padding( padding: const EdgeInsets.only(bottom: 12), child: CouponCard( brandName: _mockBrands[index % _mockBrands.length], couponName: _mockNames[index % _mockNames.length], faceValue: _mockFaceValues[index % _mockFaceValues.length], currentPrice: _mockPrices[index % _mockPrices.length], creditRating: _mockRatings[index % _mockRatings.length], expiryDate: DateTime.now().add(Duration(days: (index + 1) * 5)), onTap: () { Navigator.pushNamed(context, '/coupon/detail'); }, ), ), childCount: 10, ), ), ), const SliverPadding(padding: EdgeInsets.only(bottom: 100)), ], ), // AI FAB Positioned( right: 20, bottom: 100, child: AiFab( unreadCount: 3, onTap: () { Navigator.pushNamed(context, '/ai-chat'); }, ), ), ], ), ); } Widget _buildSearchBar(BuildContext context) { return GestureDetector( onTap: () { Navigator.pushNamed(context, '/search'); }, child: Container( height: 40, decoration: BoxDecoration( color: AppColors.gray50, borderRadius: AppSpacing.borderRadiusFull, border: Border.all(color: AppColors.borderLight), ), child: Row( children: [ const SizedBox(width: 14), const Icon(Icons.search_rounded, size: 20, color: AppColors.textTertiary), const SizedBox(width: 8), Text( '搜索券、品牌、分类...', style: AppTypography.bodyMedium.copyWith(color: AppColors.textTertiary), ), ], ), ), ); } // ============================================================ // Coupon Wallet Section (replaces Banner) // ============================================================ Widget _buildCouponWallet(BuildContext context) { return Container( margin: const EdgeInsets.fromLTRB(20, 8, 20, 0), decoration: BoxDecoration( gradient: AppColors.cardGradient, borderRadius: AppSpacing.borderRadiusLg, boxShadow: AppSpacing.shadowPrimary, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Header: 我的钱包 + 接收 button Padding( padding: const EdgeInsets.fromLTRB(16, 16, 16, 0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ const Icon(Icons.account_balance_wallet_rounded, size: 20, color: Colors.white), const SizedBox(width: 8), Text('我的钱包', style: AppTypography.h3.copyWith(color: Colors.white)), ], ), GestureDetector( onTap: () => _showReceiveSheet(context), child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.2), borderRadius: AppSpacing.borderRadiusFull, border: Border.all( color: Colors.white.withValues(alpha: 0.3), width: 0.5, ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.qr_code_rounded, size: 14, color: Colors.white), const SizedBox(width: 4), Text('接收', style: AppTypography.labelSmall.copyWith( color: Colors.white, fontWeight: FontWeight.w500, )), ], ), ), ), ], ), ), // Stats row Padding( padding: const EdgeInsets.fromLTRB(16, 14, 16, 0), child: Row( children: [ _buildWalletStat('可使用', '3', true), const SizedBox(width: 20), _buildWalletStat('待核销', '1', false), const SizedBox(width: 20), _buildWalletStat('已过期', '0', false), ], ), ), // Filter tabs Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 0), child: Row( children: [ _buildWalletTab('全部', 0), const SizedBox(width: 6), _buildWalletTab('可使用', 1), const SizedBox(width: 6), _buildWalletTab('待核销', 2), const SizedBox(width: 6), _buildWalletTab('已过期', 3), ], ), ), // Coupon mini-cards (horizontal scroll) SizedBox( height: 88, child: _filteredWalletCoupons.isEmpty ? Center( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Text( '暂无券,去交易市场看看吧', style: AppTypography.bodySmall.copyWith( color: Colors.white.withValues(alpha: 0.6), ), ), ), ) : ListView.separated( scrollDirection: Axis.horizontal, padding: const EdgeInsets.fromLTRB(16, 10, 16, 14), itemCount: _filteredWalletCoupons.length, separatorBuilder: (_, __) => const SizedBox(width: 10), itemBuilder: (context, index) { final coupon = _filteredWalletCoupons[index]; return _buildWalletCouponCard(context, coupon); }, ), ), // Quick actions bar Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), decoration: BoxDecoration( color: Colors.black.withValues(alpha: 0.1), borderRadius: const BorderRadius.only( bottomLeft: Radius.circular(16), bottomRight: Radius.circular(16), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _buildWalletAction(Icons.qr_code_rounded, '接收', () { _showReceiveSheet(context); }), _buildWalletAction(Icons.card_giftcard_rounded, '转赠', () { Navigator.pushNamed(context, '/transfer'); }), _buildWalletAction(Icons.sell_rounded, '出售', () { Navigator.pushNamed(context, '/sell'); }), _buildWalletAction(Icons.check_circle_outline_rounded, '核销', () { Navigator.pushNamed(context, '/redeem'); }), ], ), ), ], ), ); } Widget _buildWalletStat(String label, String count, bool highlight) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( count, style: AppTypography.h2.copyWith( color: highlight ? Colors.white : Colors.white.withValues(alpha: 0.7), fontWeight: FontWeight.w700, ), ), const SizedBox(height: 2), Text( label, style: AppTypography.caption.copyWith( color: Colors.white.withValues(alpha: 0.6), ), ), ], ); } Widget _buildWalletTab(String label, int index) { final isSelected = _walletFilter == index; return GestureDetector( onTap: () => setState(() => _walletFilter = index), child: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: isSelected ? Colors.white.withValues(alpha: 0.25) : Colors.transparent, borderRadius: AppSpacing.borderRadiusFull, border: Border.all( color: isSelected ? Colors.white.withValues(alpha: 0.4) : Colors.transparent, width: 0.5, ), ), child: Text( label, style: AppTypography.caption.copyWith( color: isSelected ? Colors.white : Colors.white.withValues(alpha: 0.5), fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400, ), ), ), ); } Widget _buildWalletCouponCard(BuildContext context, _WalletCoupon coupon) { return GestureDetector( onTap: () => Navigator.pushNamed(context, '/coupon/mine/detail'), child: Container( width: 140, padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.15), borderRadius: AppSpacing.borderRadiusMd, border: Border.all( color: Colors.white.withValues(alpha: 0.2), width: 0.5, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Icon(Icons.confirmation_number_outlined, size: 14, color: Colors.white.withValues(alpha: 0.7)), const SizedBox(width: 4), Expanded( child: Text( coupon.brandName, style: AppTypography.caption.copyWith( color: Colors.white.withValues(alpha: 0.7), ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), ], ), Text( coupon.name, style: AppTypography.labelSmall.copyWith( color: Colors.white, fontWeight: FontWeight.w600, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( '\$${coupon.faceValue.toStringAsFixed(0)}', style: AppTypography.priceSmall.copyWith( color: Colors.white, fontSize: 13, ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 1), decoration: BoxDecoration( color: coupon.statusColor.withValues(alpha: 0.3), borderRadius: AppSpacing.borderRadiusFull, ), child: Text( coupon.statusLabel, style: AppTypography.caption.copyWith( color: Colors.white, fontSize: 9, fontWeight: FontWeight.w500, ), ), ), ], ), ], ), ), ); } Widget _buildWalletAction(IconData icon, String label, VoidCallback onTap) { return GestureDetector( onTap: onTap, child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, size: 20, color: Colors.white.withValues(alpha: 0.9)), const SizedBox(height: 4), Text( label, style: AppTypography.caption.copyWith( color: Colors.white.withValues(alpha: 0.8), fontWeight: FontWeight.w500, ), ), ], ), ); } void _showReceiveSheet(BuildContext context) { showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (_) => const ReceiveCouponSheet(), ); } List<_WalletCoupon> get _filteredWalletCoupons { if (_walletFilter == 0) return _mockWalletCoupons; if (_walletFilter == 1) { return _mockWalletCoupons .where((c) => c.status == CouponStatus.active) .toList(); } if (_walletFilter == 2) { return _mockWalletCoupons .where((c) => c.status == CouponStatus.pending) .toList(); } return _mockWalletCoupons .where((c) => c.status == CouponStatus.expired) .toList(); } // ============================================================ // Category Grid (6 new savings-focused categories) // ============================================================ Widget _buildCategoryGrid() { final categories = [ ('限时抢购', Icons.flash_on_rounded, AppColors.error), ('新券首发', Icons.fiber_new_rounded, AppColors.primary), ('折扣排行', Icons.trending_up_rounded, AppColors.couponEntertainment), ('即将到期', Icons.timer_rounded, AppColors.info), ('比价', Icons.compare_arrows_rounded, AppColors.couponShopping), ('全部分类', Icons.grid_view_rounded, AppColors.textSecondary), ]; return Padding( padding: const EdgeInsets.fromLTRB(20, 20, 20, 0), child: GridView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, mainAxisSpacing: 8, crossAxisSpacing: 8, childAspectRatio: 1.1, ), itemCount: categories.length, itemBuilder: (context, index) { final (name, icon, color) = categories[index]; return GestureDetector( onTap: () {}, child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 48, height: 48, decoration: BoxDecoration( color: color.withValues(alpha: 0.1), borderRadius: AppSpacing.borderRadiusMd, ), child: Icon(icon, color: color, size: 24), ), const SizedBox(height: 6), Text(name, style: AppTypography.caption.copyWith( color: AppColors.textPrimary, fontWeight: FontWeight.w500, )), ], ), ); }, ), ); } Widget _buildAiSuggestions() { return Container( margin: const EdgeInsets.fromLTRB(20, 16, 20, 0), padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: AppColors.primarySurface, borderRadius: AppSpacing.borderRadiusMd, border: Border.all(color: AppColors.primary.withValues(alpha: 0.15)), ), child: Row( children: [ Container( width: 32, height: 32, decoration: BoxDecoration( gradient: AppColors.primaryGradient, borderRadius: AppSpacing.borderRadiusSm, ), child: const Icon(Icons.auto_awesome_rounded, color: Colors.white, size: 16), ), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('AI 推荐', style: AppTypography.labelSmall.copyWith( color: AppColors.primary, )), const SizedBox(height: 2), Text( '根据你的偏好,发现了3张高性价比券', style: AppTypography.bodySmall, maxLines: 1, overflow: TextOverflow.ellipsis, ), ], ), ), const Icon(Icons.chevron_right_rounded, color: AppColors.primary, size: 20), ], ), ); } } // ============================================================ // Wallet Coupon Model // ============================================================ class _WalletCoupon { final String brandName; final String name; final double faceValue; final CouponStatus status; final DateTime expiryDate; const _WalletCoupon({ required this.brandName, required this.name, required this.faceValue, required this.status, required this.expiryDate, }); String get statusLabel { switch (status) { case CouponStatus.active: return '可使用'; case CouponStatus.pending: return '待核销'; case CouponStatus.expired: return '已过期'; case CouponStatus.used: return '已使用'; } } Color get statusColor { switch (status) { case CouponStatus.active: return AppColors.success; case CouponStatus.pending: return AppColors.warning; case CouponStatus.expired: return AppColors.textTertiary; case CouponStatus.used: return AppColors.textDisabled; } } } // Mock data final _mockWalletCoupons = [ _WalletCoupon( brandName: 'Starbucks', name: '星巴克 \$25 礼品卡', faceValue: 25.0, status: CouponStatus.active, expiryDate: DateTime.now().add(const Duration(days: 30)), ), _WalletCoupon( brandName: 'Amazon', name: 'Amazon \$100 购物券', faceValue: 100.0, status: CouponStatus.active, expiryDate: DateTime.now().add(const Duration(days: 45)), ), _WalletCoupon( brandName: 'Nike', name: 'Nike \$80 运动券', faceValue: 80.0, status: CouponStatus.pending, expiryDate: DateTime.now().add(const Duration(days: 15)), ), _WalletCoupon( brandName: 'Target', name: 'Target \$30 折扣券', faceValue: 30.0, status: CouponStatus.active, expiryDate: DateTime.now().add(const Duration(days: 60)), ), ]; const _mockBrands = ['Starbucks', 'Amazon', 'Walmart', 'Target', 'Nike']; const _mockNames = ['星巴克 \$25 礼品卡', 'Amazon \$100 购物券', 'Walmart \$50 生活券', 'Target \$30 折扣券', 'Nike \$80 运动券']; const _mockFaceValues = [25.0, 100.0, 50.0, 30.0, 80.0]; const _mockPrices = [21.25, 85.0, 42.5, 24.0, 68.0]; const _mockRatings = ['AAA', 'AA', 'AAA', 'A', 'AA'];