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'; import '../../../../app/i18n/app_localizations.dart'; import '../../../../core/services/coupon_service.dart'; import '../../data/models/coupon_model.dart'; import '../../data/models/holdings_summary_model.dart'; /// 首页 - 轻量持仓卡 + 分类网格 + AI推荐 + 精选券 /// /// Tab导航:首页/交易/消息/我的 class HomePage extends StatefulWidget { const HomePage({super.key}); @override State createState() => _HomePageState(); } class _HomePageState extends State { final CouponApiService _couponService = CouponApiService(); List _featuredCoupons = []; HoldingsSummaryModel? _holdingsSummary; bool _isLoading = true; String? _error; @override void initState() { super.initState(); _loadData(); } Future _loadData() async { setState(() { _isLoading = true; _error = null; }); try { final results = await Future.wait([ _couponService.getFeaturedCoupons(limit: 10), _couponService.getHoldingsSummary(), ]); if (mounted) { setState(() { _featuredCoupons = results[0] as List; _holdingsSummary = results[1] as HoldingsSummaryModel; _isLoading = false; }); } } catch (e) { if (mounted) { setState(() { _error = e.toString(); _isLoading = false; }); } } } @override Widget build(BuildContext context) { return Scaffold( body: Stack( children: [ RefreshIndicator( onRefresh: _loadData, child: CustomScrollView( slivers: [ // Floating App Bar SliverAppBar( floating: true, pinned: false, automaticallyImplyLeading: 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, ), ], ), // Lightweight Position Card (持仓) SliverToBoxAdapter(child: _buildWalletCard(context)), // Category Grid (8 items, 4x2) SliverToBoxAdapter(child: _buildCategoryGrid(context)), // AI Smart Suggestions SliverToBoxAdapter(child: _buildAiSuggestions(context)), // Section: Featured Coupons SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.fromLTRB(20, 24, 20, 12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(context.t('home.featuredCoupons'), style: AppTypography.h2), GestureDetector( onTap: () {}, child: Text(context.t('home.viewAllCoupons'), style: AppTypography.labelSmall.copyWith( color: AppColors.primary, )), ), ], ), ), ), // Coupon List if (_isLoading) SliverPadding( padding: AppSpacing.pagePadding, sliver: SliverList( delegate: SliverChildBuilderDelegate( (context, index) => Padding( padding: const EdgeInsets.only(bottom: 12), child: _buildSkeletonCard(), ), childCount: 3, ), ), ) else if (_error != null) SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.all(20), child: Column( children: [ Icon(Icons.cloud_off_rounded, size: 48, color: AppColors.textTertiary), const SizedBox(height: 12), Text( context.t('home.loadFailed'), style: AppTypography.bodyMedium.copyWith(color: AppColors.textSecondary), ), const SizedBox(height: 12), TextButton( onPressed: _loadData, child: Text(context.t('home.retry')), ), ], ), ), ) else if (_featuredCoupons.isEmpty) SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.all(40), child: Center( child: Text( context.t('home.noCoupons'), style: AppTypography.bodyMedium.copyWith(color: AppColors.textTertiary), ), ), ), ) else SliverPadding( padding: AppSpacing.pagePadding, sliver: SliverList( delegate: SliverChildBuilderDelegate( (context, index) { final coupon = _featuredCoupons[index]; return Padding( padding: const EdgeInsets.only(bottom: 12), child: CouponCard( brandName: coupon.brandName ?? '', couponName: coupon.name, faceValue: coupon.faceValue, currentPrice: coupon.currentPrice, creditRating: coupon.creditRating ?? '', expiryDate: coupon.expiryDate, onTap: () { Navigator.pushNamed(context, '/coupon/detail', arguments: coupon.id); }, ), ); }, childCount: _featuredCoupons.length, ), ), ), 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 _buildSkeletonCard() { return Container( height: 100, decoration: BoxDecoration( color: AppColors.gray50, borderRadius: AppSpacing.borderRadiusMd, ), child: const Center( child: SizedBox( width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2), ), ), ); } 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), Expanded( child: Text( context.t('home.searchHint'), style: AppTypography.bodyMedium.copyWith(color: AppColors.textTertiary), overflow: TextOverflow.ellipsis, ), ), ], ), ), ); } // ============================================================ // Lightweight Position Card (持仓卡片) // 点击非快捷入口区域打开完整持仓页面 // ============================================================ Widget _buildWalletCard(BuildContext context) { final count = _holdingsSummary?.count ?? 0; final totalValue = _holdingsSummary?.totalFaceValue ?? 0; return GestureDetector( onTap: () => Navigator.pushNamed(context, '/wallet/coupons'), child: Container( margin: const EdgeInsets.fromLTRB(20, 8, 20, 0), padding: const EdgeInsets.all(16), decoration: BoxDecoration( gradient: AppColors.cardGradient, borderRadius: AppSpacing.borderRadiusLg, boxShadow: AppSpacing.shadowPrimary, ), child: Column( children: [ // Top row: wallet info + receive button Row( children: [ const Icon(Icons.inventory_2_rounded, size: 20, color: Colors.white), const SizedBox(width: 8), Text(context.t('home.position'), style: AppTypography.labelMedium.copyWith(color: Colors.white)), const Spacer(), // Summary Text('${context.t('home.hold')} ', style: AppTypography.bodySmall.copyWith( color: Colors.white.withValues(alpha: 0.7), )), Text('$count', style: AppTypography.h3.copyWith( color: Colors.white, fontWeight: FontWeight.w700, )), Text(' ${context.t('home.couponUnit')} ${context.t('home.totalValue')} ', style: AppTypography.bodySmall.copyWith( color: Colors.white.withValues(alpha: 0.7), )), Text('\$${totalValue.toStringAsFixed(0)}', style: AppTypography.h3.copyWith( color: Colors.white, fontWeight: FontWeight.w700, )), const SizedBox(width: 4), Icon(Icons.chevron_right_rounded, size: 18, color: Colors.white.withValues(alpha: 0.7)), ], ), const SizedBox(height: 14), // Quick action entries Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _buildQuickAction(context, Icons.qr_code_rounded, context.t('home.receive'), () { showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (_) => const ReceiveCouponSheet(), ); }), _buildQuickAction(context, Icons.card_giftcard_rounded, context.t('home.transfer'), () { Navigator.pushNamed(context, '/transfer'); }), _buildQuickAction(context, Icons.sell_rounded, context.t('home.sell'), () { Navigator.pushNamed(context, '/sell'); }), _buildQuickAction(context, Icons.check_circle_outline_rounded, context.t('home.redeem'), () { Navigator.pushNamed(context, '/redeem'); }), ], ), ], ), ), ); } Widget _buildQuickAction( BuildContext context, IconData icon, String label, VoidCallback onTap) { return GestureDetector( onTap: onTap, behavior: HitTestBehavior.opaque, child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 40, height: 40, decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.15), borderRadius: AppSpacing.borderRadiusMd, ), child: Icon(icon, size: 20, color: Colors.white), ), const SizedBox(height: 6), Text( label, style: AppTypography.caption.copyWith( color: Colors.white.withValues(alpha: 0.9), fontWeight: FontWeight.w500, ), ), ], ), ); } // ============================================================ // Category Grid (8 items, 4x2, A+C savings-focused) // ============================================================ Widget _buildCategoryGrid(BuildContext context) { final categories = [ (context.t('home.flashSale'), Icons.flash_on_rounded, AppColors.error), (context.t('home.newRelease'), Icons.fiber_new_rounded, AppColors.primary), (context.t('home.discountRank'), Icons.trending_up_rounded, AppColors.couponEntertainment), (context.t('home.expiringSoon'), Icons.timer_rounded, AppColors.warning), (context.t('home.priceCompare'), Icons.compare_arrows_rounded, AppColors.couponShopping), (context.t('home.resaleMarket'), Icons.swap_horiz_rounded, AppColors.info), (context.t('home.hotTrades'), Icons.local_fire_department_rounded, AppColors.couponDining), (context.t('home.viewAll'), 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: 4, mainAxisSpacing: 4, crossAxisSpacing: 4, childAspectRatio: 0.9, ), itemCount: categories.length, itemBuilder: (context, index) { final (name, icon, color) = categories[index]; return GestureDetector( onTap: () {}, child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 44, height: 44, decoration: BoxDecoration( color: color.withValues(alpha: 0.1), borderRadius: AppSpacing.borderRadiusMd, ), child: Icon(icon, color: color, size: 22), ), const SizedBox(height: 6), Text(name, style: AppTypography.caption.copyWith( color: AppColors.textPrimary, fontWeight: FontWeight.w500, )), ], ), ); }, ), ); } Widget _buildAiSuggestions(BuildContext context) { 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(context.t('home.aiRecommend'), style: AppTypography.labelSmall.copyWith( color: AppColors.primary, )), const SizedBox(height: 2), Text( context.t('home.aiRecommendDesc'), style: AppTypography.bodySmall, maxLines: 1, overflow: TextOverflow.ellipsis, ), ], ), ), const Icon(Icons.chevron_right_rounded, color: AppColors.primary, size: 20), ], ), ); } }