import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:shadcn_ui/shadcn_ui.dart'; import 'package:provider/provider.dart'; import '../../../core/theme/app_spacing.dart'; import '../../../core/theme/app_theme.dart'; import '../../../core/theme/app_theme_extension.dart'; import '../../../core/theme/app_color_scheme.dart'; import '../../../core/utils/toast_utils.dart'; import '../../../core/event/app_event_bus.dart'; import '../../../data/services/bonus_service.dart'; import '../../../providers/asset_provider.dart'; /// 福利中心頁面 class WelfareCenterPage extends StatefulWidget { const WelfareCenterPage({super.key}); @override State createState() => _WelfareCenterPageState(); } class _WelfareCenterPageState extends State { Map? _welfareData; bool _isLoading = true; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) => _loadData()); } Future _loadData() async { try { final bonusService = context.read(); final response = await bonusService.getWelfareStatus(); if (mounted) { setState(() { _welfareData = response.data; _isLoading = false; }); } } catch (_) { if (mounted) setState(() => _isLoading = false); } } // ============================================ // 容器樣式輔助 // ============================================ /// 標準卡片容器 BoxDecoration _cardDecoration({Color? borderColor}) { return BoxDecoration( color: context.appColors.surfaceCard, borderRadius: BorderRadius.circular(AppRadius.lg), border: Border.all( color: borderColor ?? context.appColors.ghostBorder, ), ); } /// 金色漸變卡片容器 BoxDecoration _goldGradientDecoration() { final isDark = context.isDark; // Light Mode: 琥珀色漸變(從 amber 15% 到深灰 5%) // Dark Mode: 金色漸變 final gradientColors = isDark ? [ context.appColors.accentPrimary.withValues(alpha: 0.15), context.appColors.surfaceCard, ] : [ const Color(0xFFF59E0B).withValues(alpha: 0.15), // amber-500 with 15% opacity const Color(0xFF1F2937).withValues(alpha: 0.05), // gray-800 with 5% opacity ]; return BoxDecoration( borderRadius: BorderRadius.circular(AppRadius.xl), gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: gradientColors, ), border: Border.all( color: isDark ? context.appColors.accentPrimary.withValues(alpha: 0.3) : const Color(0xFFF59E0B).withValues(alpha: 0.3), width: 1, ), ); } /// 狀態膠囊標籤 Widget _statusBadge(String text, Color textColor, Color bgColor) { return Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 6), // [4,10] → [6,14] decoration: BoxDecoration( color: bgColor, borderRadius: BorderRadius.circular(AppRadius.sm), ), child: Text( text, style: AppTextStyles.labelSmall(context).copyWith(color: textColor), ), ); } /// 全寬按鈕 Widget _fullWidthButton({ required String text, required Color backgroundColor, required Color foregroundColor, required VoidCallback? onPressed, Color? disabledBackgroundColor, }) { return SizedBox( width: double.infinity, height: 44, child: ElevatedButton( onPressed: onPressed, style: ElevatedButton.styleFrom( backgroundColor: backgroundColor, foregroundColor: foregroundColor, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(AppRadius.lg), ), disabledBackgroundColor: disabledBackgroundColor ?? backgroundColor.withValues(alpha: 0.3), disabledForegroundColor: foregroundColor.withValues(alpha: 0.7), ), child: Text( text, style: AppTextStyles.headlineMedium(context).copyWith( fontWeight: FontWeight.w700, color: foregroundColor, ), ), ), ); } @override Widget build(BuildContext context) { final goldAccent = context.appColors.accentPrimary; return Scaffold( backgroundColor: context.colors.surface, appBar: AppBar( backgroundColor: context.colors.surface, elevation: 0, scrolledUnderElevation: 0, centerTitle: false, titleSpacing: 0, title: Text( '福利中心', style: AppTextStyles.headlineLarge(context), ), leading: IconButton( icon: Icon(LucideIcons.arrowLeft, color: context.colors.onSurface, size: 24), onPressed: () => Navigator.of(context).pop(), ), ), body: _isLoading ? Center( child: CircularProgressIndicator( color: goldAccent, strokeWidth: 2.5, ), ) : RefreshIndicator( onRefresh: _loadData, color: goldAccent, child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), padding: const EdgeInsets.fromLTRB(16, 8, 16, 32), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildReferralCodeCard(context), const SizedBox(height: 16), _buildNewUserBonusCard(context), const SizedBox(height: 16), _buildReferralRewardsSection(context), const SizedBox(height: 16), _buildRulesCard(context), ], ), ), ), ); } // ============================================ // 推廣碼卡片(金色漸變邊框 Hero Card) // ============================================ Widget _buildReferralCodeCard(BuildContext context) { final goldAccent = context.appColors.accentPrimary; final referralCode = _welfareData?['referralCode'] as String? ?? ''; return Container( width: double.infinity, padding: const EdgeInsets.all(24), decoration: _goldGradientDecoration(), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Header Row: gift icon + 標題 Row( children: [ Icon(LucideIcons.gift, color: goldAccent, size: 24), const SizedBox(width: 10), Text( '我的邀請碼', style: AppTextStyles.headlineLarge(context).copyWith( fontWeight: FontWeight.w700, ), ), ], ), const SizedBox(height: 16), Text( referralCode.isEmpty ? '暫無邀請碼' : referralCode, style: AppTextStyles.displayMedium(context).copyWith( fontSize: 24, // 明確設置為 24px fontWeight: FontWeight.w800, color: goldAccent, letterSpacing: 2, ), ), const SizedBox(height: 16), SizedBox( width: double.infinity, height: 40, child: ElevatedButton( onPressed: referralCode.isEmpty ? null : () { Clipboard.setData(ClipboardData(text: referralCode)); ToastUtils.show('邀請碼已複製'); }, style: ElevatedButton.styleFrom( backgroundColor: goldAccent, foregroundColor: context.colors.onPrimary, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(AppRadius.lg), ), disabledBackgroundColor: goldAccent.withValues(alpha: 0.4), ), child: Text( '複製邀請碼', style: AppTextStyles.headlineMedium(context).copyWith(color: context.colors.onPrimary), ), ), ), ], ), ); } // ============================================ // 新人福利卡片 // ============================================ Widget _buildNewUserBonusCard(BuildContext context) { final profitGreen = context.appColors.up; final profitGreenBg = context.appColors.upBackground; final newUserBonus = _welfareData?['newUserBonus'] as Map?; final eligible = newUserBonus?['eligible'] as bool? ?? false; final claimed = newUserBonus?['claimed'] as bool? ?? false; final deposited = newUserBonus?['deposited'] as bool? ?? false; // 狀態判定 String badgeText; bool showAvailableBadge; String buttonText; bool canClaim; String description; if (claimed) { badgeText = '已領取'; showAvailableBadge = false; buttonText = '已領取'; canClaim = false; description = '新人福利已領取'; } else if (eligible) { badgeText = '可領取'; showAvailableBadge = true; buttonText = '立即領取'; canClaim = true; description = '完成首次充值即可領取'; } else { badgeText = deposited ? '已充值' : '待解鎖'; showAvailableBadge = false; buttonText = '未解鎖'; canClaim = false; description = deposited ? '已充值,等待系統確認' : '完成首次充值即可領取'; } return Container( width: double.infinity, padding: const EdgeInsets.all(20), decoration: _cardDecoration(), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Header: 標題 + 狀態標籤 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( '新人福利', style: AppTextStyles.headlineLarge(context).copyWith( fontWeight: FontWeight.w700, ), ), if (showAvailableBadge) _statusBadge(badgeText, profitGreen, profitGreenBg), ], ), const SizedBox(height: 12), Text( '+100 USDT', style: AppTextStyles.displayLarge(context).copyWith( fontWeight: FontWeight.w800, color: claimed ? context.colors.onSurfaceVariant : profitGreen, ), ), const SizedBox(height: 8), Text( description, style: AppTextStyles.bodyLarge(context).copyWith( color: context.colors.onSurfaceVariant, ), ), const SizedBox(height: 16), _fullWidthButton( text: buttonText, backgroundColor: profitGreen, foregroundColor: context.colors.onPrimary, onPressed: canClaim ? () => _claimNewUserBonus() : null, ), ], ), ); } // ============================================ // 推廣獎勵列表 // ============================================ Widget _buildReferralRewardsSection(BuildContext context) { final referralRewards = _welfareData?['referralRewards'] as List? ?? []; // 彙總統計 int totalEarned = 0; int totalClaimable = 0; for (var r in referralRewards) { final map = r as Map; final milestones = map['milestones'] as List? ?? []; for (var m in milestones) { final ms = m as Map; if (ms['claimed'] as bool? ?? false) totalEarned++; if (ms['claimable'] as bool? ?? false) totalClaimable++; } } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Section Header Text( '推廣獎勵', style: AppTextStyles.headlineLarge(context), ), const SizedBox(height: 4), Text( '每邀請一位好友充值達標,獎勵100 USDT', style: AppTextStyles.bodySmall(context).copyWith( color: context.appColors.onSurfaceMuted, ), ), // 彙總統計 if (referralRewards.isNotEmpty) ...[ const SizedBox(height: 12), Container( padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md, vertical: AppSpacing.sm), decoration: BoxDecoration( color: context.appColors.surfaceCard, borderRadius: BorderRadius.circular(AppRadius.md), border: Border.all(color: context.appColors.ghostBorder), ), child: Row( children: [ _buildStatItem('已邀請', '${referralRewards.length}人'), Container(width: 1, height: 20, color: context.appColors.ghostBorder), _buildStatItem('已獲得', '${totalEarned * 100} USDT'), if (totalClaimable > 0) ...[ Container(width: 1, height: 20, color: context.appColors.ghostBorder), _buildStatItem('待領取', '$totalClaimable個', highlight: true), ], ], ), ), ], const SizedBox(height: 12), // 推廣列表卡片 Container( width: double.infinity, decoration: _cardDecoration(), child: referralRewards.isEmpty ? _buildEmptyReferralList(context) : _buildReferralListItems(context, referralRewards), ), ], ); } Widget _buildStatItem(String label, String value, {bool highlight = false}) { return Expanded( child: Column( children: [ Text(label, style: AppTextStyles.bodySmall(context).copyWith( color: context.colors.onSurfaceVariant, )), const SizedBox(height: 2), Text(value, style: AppTextStyles.headlineSmall(context).copyWith( fontWeight: FontWeight.w700, color: highlight ? context.appColors.up : context.colors.onSurface, )), ], ), ); } Widget _buildEmptyReferralList(BuildContext context) { return Padding( padding: const EdgeInsets.all(32), child: Center( child: Column( children: [ Icon( LucideIcons.users, size: 36, color: context.appColors.onSurfaceMuted.withValues(alpha: 0.4), ), const SizedBox(height: 8), Text( '暫無推廣用戶', style: AppTextStyles.bodyLarge(context).copyWith( color: context.colors.onSurfaceVariant, ), ), ], ), ), ); } Widget _buildReferralListItems(BuildContext context, List referralRewards) { return Column( children: List.generate(referralRewards.length, (index) { final data = referralRewards[index] as Map; final username = data['username'] as String? ?? ''; final totalDeposit = data['totalDeposit']?.toString() ?? '0'; final claimableCount = data['claimableCount'] as int? ?? 0; final milestones = data['milestones'] as List? ?? []; final userId = data['userId'] as int? ?? 0; final indirectRefCount = data['indirectRefCount'] as int? ?? 0; final indirectClaimableCount = data['indirectClaimableCount'] as int? ?? 0; final indirectMilestones = data['indirectMilestones'] as List? ?? []; final isLast = index == referralRewards.length - 1; // 進度計算 final progress = _computeProgress(milestones, totalDeposit); // 操作按鈕 final actionWidget = _buildReferralAction( data: data, claimableCount: claimableCount, milestones: milestones, progress: progress, ); // 進度條顏色 final progressColor = _referralProgressColor(claimableCount, progress); return Column( children: [ Padding( padding: const EdgeInsets.all(16), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ _buildAvatar(username), const SizedBox(width: 10), Text( username, style: AppTextStyles.headlineSmall(context).copyWith( fontWeight: FontWeight.w500, ), ), const SizedBox(width: 10), Text( '充值: \u00A5$totalDeposit', style: AppTextStyles.bodySmall(context), ), ], ), if (actionWidget != null) actionWidget, ], ), const SizedBox(height: 10), ClipRRect( borderRadius: BorderRadius.circular(3), child: SizedBox( height: 6, child: LinearProgressIndicator( value: progress, backgroundColor: context.appColors.surfaceCardHigh, valueColor: AlwaysStoppedAnimation(progressColor), minHeight: 6, ), ), ), // 里程碑詳情 if (milestones.isNotEmpty) ...[ const SizedBox(height: 12), _buildMilestoneDetails(milestones, userId), ], // 間接推廣獎勵 if (indirectRefCount > 0) ...[ const SizedBox(height: 12), _buildIndirectSection( userId, indirectRefCount, indirectClaimableCount, indirectMilestones, ), ], ], ), ), if (!isLast) Divider( height: 1, thickness: 1, color: context.appColors.ghostBorder, ), ], ); }), ); } /// 里程碑詳情行 — 顯示每個里程碑狀態 Widget _buildMilestoneDetails(List milestones, int userId) { final upColor = context.appColors.up; return Wrap( spacing: 6, runSpacing: 6, children: milestones.map((m) { final ms = m as Map; final milestoneVal = ms['milestone'] as int? ?? 1; final earned = ms['earned'] as bool? ?? false; final claimed = ms['claimed'] as bool? ?? false; final claimable = ms['claimable'] as bool? ?? false; final threshold = ms['threshold']?.toString() ?? '${milestoneVal * 1000}'; Color bgColor; Color textColor; String label; if (claimed) { bgColor = upColor.withValues(alpha: 0.1); textColor = upColor; label = '${threshold}✓'; } else if (claimable) { bgColor = context.appColors.accentPrimary.withValues(alpha: 0.15); textColor = context.appColors.accentPrimary; label = '${threshold}🎁'; } else if (earned) { bgColor = upColor.withValues(alpha: 0.06); textColor = upColor; label = '${threshold}'; } else { bgColor = context.appColors.surfaceCardHigh; textColor = context.colors.onSurfaceVariant; label = '${threshold}'; } return GestureDetector( onTap: claimable ? () => _claimReferralBonus(userId, milestoneVal) : null, child: Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: bgColor, borderRadius: BorderRadius.circular(AppRadius.sm), border: claimable ? Border.all(color: textColor.withValues(alpha: 0.3)) : null, ), child: Text( label, style: AppTextStyles.bodySmall(context).copyWith( fontSize: 11, fontWeight: FontWeight.w600, color: textColor, ), ), ), ); }).toList(), ); } Widget _buildAvatar(String username) { return Container( width: 32, height: 32, decoration: BoxDecoration( color: context.appColors.surfaceCardHigh, shape: BoxShape.circle, ), child: Center( child: Text( username.isNotEmpty ? username[0].toUpperCase() : '?', style: AppTextStyles.headlineSmall(context).copyWith( color: context.colors.onSurfaceVariant, ), ), ), ); } /// 計算推薦獎勵進度 double _computeProgress(List milestones, String totalDeposit) { if (milestones.isNotEmpty) { int earnedCount = milestones.where((m) { final milestone = m as Map; return milestone['earned'] as bool? ?? false; }).length; return earnedCount / milestones.length; } final deposit = double.tryParse(totalDeposit) ?? 0; return (deposit / 1000).clamp(0.0, 1.0); } /// 根據狀態獲取進度條顏色 Color _referralProgressColor(int claimableCount, double progress) { if (claimableCount > 0) return context.appColors.up; if (progress > 0) return context.appColors.accentPrimary; return context.appColors.surfaceCardHigh; } /// 構建推薦獎勵的操作按鈕 Widget? _buildReferralAction({ required Map data, required int claimableCount, required List milestones, required double progress, }) { final profitGreen = context.appColors.up; final profitGreenBg = context.appColors.upBackground; if (claimableCount > 0) { final int milestoneValue = milestones.isNotEmpty ? (milestones.firstWhere( (m) => (m as Map)['claimable'] == true, orElse: () => milestones.first, ) as Map)['milestone'] as int? ?? 1 : 1; return GestureDetector( onTap: () => _claimReferralBonus(data['userId'] as int, milestoneValue), child: _statusBadge('領取', profitGreen, profitGreenBg), ); } if (progress > 0) { final warningColor = AppColorScheme.warning; return _statusBadge('進行中', warningColor, warningColor.withValues(alpha: 0.15)); } return Text( '待達標', style: AppTextStyles.labelLarge(context).copyWith(color: context.appColors.onSurfaceMuted), ); } // ============================================ // 間接推廣獎勵區塊 // ============================================ Widget _buildIndirectSection( int directReferralId, int indirectRefCount, int indirectClaimableCount, List indirectMilestones, ) { final goldAccent = context.appColors.accentPrimary; return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: context.appColors.surfaceCardHigh.withValues(alpha: 0.5), borderRadius: BorderRadius.circular(AppRadius.md), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(LucideIcons.users, size: 14, color: goldAccent), const SizedBox(width: 6), Text( '已推廣 $indirectRefCount 人', style: AppTextStyles.bodyMedium(context).copyWith( color: context.colors.onSurfaceVariant, ), ), if (indirectClaimableCount > 0) ...[ const Spacer(), _statusBadge( '$indirectClaimableCount 個可領', context.appColors.up, context.appColors.upBackground, ), ], ], ), // 間接里程碑標籤 if (indirectMilestones.isNotEmpty) ...[ const SizedBox(height: 8), _buildIndirectMilestoneTags(directReferralId, indirectMilestones), ], ], ), ); } Widget _buildIndirectMilestoneTags(int directReferralId, List milestones) { final upColor = context.appColors.up; return Wrap( spacing: 6, runSpacing: 6, children: milestones.map((m) { final ms = m as Map; final milestoneVal = ms['milestone'] as int? ?? 1; final earned = ms['earned'] as bool? ?? false; final claimed = ms['claimed'] as bool? ?? false; final claimable = ms['claimable'] as bool? ?? false; final indirectReferredUserId = ms['indirectReferredUserId'] as int? ?? 0; // 不顯示未達標的里程碑 if (!earned && !claimed) return const SizedBox.shrink(); Color bgColor; Color textColor; String label; if (claimed) { bgColor = upColor.withValues(alpha: 0.1); textColor = upColor; label = '50\u2713'; } else if (claimable) { bgColor = context.appColors.accentPrimary.withValues(alpha: 0.15); textColor = context.appColors.accentPrimary; label = '50\u{1F381}'; } else { bgColor = context.appColors.surfaceCardHigh; textColor = context.colors.onSurfaceVariant; label = '50'; } return GestureDetector( onTap: claimable ? () => _claimIndirectReferralBonus( directReferralId, indirectReferredUserId, milestoneVal, ) : null, child: Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: bgColor, borderRadius: BorderRadius.circular(AppRadius.sm), border: claimable ? Border.all(color: textColor.withValues(alpha: 0.3)) : null, ), child: Text( label, style: AppTextStyles.bodySmall(context).copyWith( fontSize: 11, fontWeight: FontWeight.w600, color: textColor, ), ), ), ); }).toList(), ); } // ============================================ // 獎勵規則卡片 // ============================================ Widget _buildRulesCard(BuildContext context) { return Container( width: double.infinity, padding: const EdgeInsets.fromLTRB(20, 16, 20, 16), decoration: BoxDecoration( color: context.appColors.surfaceCardHigh, borderRadius: BorderRadius.circular(AppRadius.lg), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '獎勵規則', style: AppTextStyles.headlineSmall(context), ), const SizedBox(height: 8), _buildRuleItem('新用戶註冊完成實名認證獎勵 100 USDT'), _buildRuleItem('邀請好友充值每達 1000 USDT,獎勵 100 USDT'), _buildRuleItem('好友推廣的人充值每達 1000 USDT,額外獎勵 50 USDT'), _buildRuleItem('獎勵直接發放至資金賬戶'), ], ), ); } Widget _buildRuleItem(String text) { return Padding( padding: const EdgeInsets.symmetric(vertical: 3), child: Text( '\u2022 $text', style: AppTextStyles.bodyMedium(context).copyWith( color: context.colors.onSurfaceVariant, ), ), ); } // ============================================ // 業務邏輯 // ============================================ Future _claimNewUserBonus() async { try { final bonusService = context.read(); final response = await bonusService.claimNewUserBonus(); if (!mounted) return; if (response.success) { context.read().refreshAll(force: true); context.read().fire(AppEventType.assetChanged); ToastUtils.show('領取成功!100 USDT 已到賬'); _loadData(); } else { ToastUtils.show(response.message ?? '領取失敗'); } } catch (e) { ToastUtils.show('領取失敗: $e'); } } Future _claimReferralBonus(int referredUserId, int milestone) async { try { final bonusService = context.read(); final response = await bonusService.claimReferralBonus( referredUserId, milestone, ); if (!mounted) return; if (response.success) { context.read().refreshAll(force: true); context.read().fire(AppEventType.assetChanged); ToastUtils.show('領取成功!100 USDT 已到賬'); _loadData(); } else { ToastUtils.show(response.message ?? '領取失敗'); } } catch (e) { ToastUtils.show('領取失敗: $e'); } } Future _claimIndirectReferralBonus( int directReferralId, int indirectReferredUserId, int milestone, ) async { try { final bonusService = context.read(); final response = await bonusService.claimIndirectReferralBonus( directReferralId, indirectReferredUserId, milestone, ); if (!mounted) return; if (response.success) { context.read().refreshAll(force: true); context.read().fire(AppEventType.assetChanged); ToastUtils.show('領取成功!50 USDT 已到賬'); _loadData(); } else { ToastUtils.show(response.message ?? '領取失敗'); } } catch (e) { ToastUtils.show('領取失敗: $e'); } } }