import 'dart:io'; import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:shimmer/shimmer.dart'; import 'package:visibility_detector/visibility_detector.dart'; import '../../../../core/di/injection_container.dart'; import '../../../../core/services/referral_service.dart'; import '../../../../core/services/reward_service.dart'; import '../../../../core/services/notification_service.dart'; import '../../../../routes/route_paths.dart'; import '../../../../routes/app_router.dart'; import '../../../auth/presentation/providers/auth_provider.dart'; import '../widgets/team_tree_widget.dart'; import '../widgets/stacked_cards_widget.dart'; /// 个人中心页面 - 显示用户信息、社区数据、收益和设置 /// 包含用户资料、推荐信息、社区考核、收益领取等功能 class ProfilePage extends ConsumerStatefulWidget { const ProfilePage({super.key}); @override ConsumerState createState() => _ProfilePageState(); } class _ProfilePageState extends ConsumerState { // 用户数据(从存储/API加载) String _nickname = '加载中...'; String _serialNumber = '--'; String? _avatarSvg; String? _avatarUrl; String? _localAvatarPath; // 本地头像文件路径 String _referrerSerial = '--'; // 推荐人序列号(从API获取) String _referralCode = '--'; // 我的推荐码 // 授权数据(从 authorization-service 获取) String _community = '--'; String _parentCommunity = '--'; String _childCommunity = '--'; String _authCityCompany = '--'; // 市团队 String _cityCompany = '--'; // 市区域 String _authProvinceCompany = '--'; // 省团队 String _provinceCompany = '--'; // 省区域 String _province = '--'; // 团队数据(从 referral-service 获取) int _directReferralCount = 0; int _totalTeamCount = 0; int _personalPlantingCount = 0; int _teamPlantingCount = 0; double _leaderboardScore = 0; int? _leaderboardRank; // 直推数据(从 referral-service 获取) List> _referrals = []; // 团队树根节点(缓存以保持展开状态) TeamTreeNode? _teamTreeRootNode; // 社区考核数据(从 authorization-service 获取) bool _hasCommunityAuth = false; // 是否有社区授权 bool _communityBenefitActive = false; // 社区权益是否激活 int _communityCurrentTreeCount = 0; // 当前团队认种数量 int _communityInitialTarget = 10; // 初始考核目标(社区固定10) int _communityMonthlyTarget = 10; // 月度考核目标 int _communityMonthIndex = 0; // 当前考核月份 // 市团队考核数据 bool _hasAuthCityCompanyAuth = false; bool _authCityCompanyBenefitActive = false; int _authCityCompanyCurrentTreeCount = 0; int _authCityCompanyInitialTarget = 100; int _authCityCompanyMonthlyTarget = 100; int _authCityCompanyMonthIndex = 0; // 省团队考核数据 bool _hasAuthProvinceCompanyAuth = false; bool _authProvinceCompanyBenefitActive = false; int _authProvinceCompanyCurrentTreeCount = 0; int _authProvinceCompanyInitialTarget = 500; int _authProvinceCompanyMonthlyTarget = 500; int _authProvinceCompanyMonthIndex = 0; // 市区域考核数据 bool _hasCityCompanyAuth = false; bool _cityCompanyBenefitActive = false; int _cityCompanyCurrentTreeCount = 0; int _cityCompanyInitialTarget = 100; int _cityCompanyMonthlyTarget = 100; int _cityCompanyMonthIndex = 0; // 省区域考核数据 bool _hasProvinceCompanyAuth = false; bool _provinceCompanyBenefitActive = false; int _provinceCompanyCurrentTreeCount = 0; int _provinceCompanyInitialTarget = 500; int _provinceCompanyMonthlyTarget = 500; int _provinceCompanyMonthIndex = 0; // 收益数据(从 reward-service 直接获取) double _pendingUsdt = 0.0; double _pendingPower = 0.0; double _settleableUsdt = 0.0; double _settledUsdt = 0.0; double _expiredUsdt = 0.0; double _expiredPower = 0.0; bool _isLoadingWallet = false; // 懒加载,初始为 false String? _walletError; // 待领取奖励列表(每一笔单独显示) List _pendingRewards = []; // 可结算奖励列表(逐笔显示) List _settleableRewards = []; // 已过期奖励列表(逐笔显示) List _expiredRewards = []; // 倒计时 Timer? _timer; int _remainingSeconds = 0; // 应用版本信息 String _appVersion = '--'; String _buildNumber = '--'; String _packageName = '--'; String _deviceModel = '--'; String _osVersion = '--'; String _platform = '--'; // 通知未读数量 int _unreadNotificationCount = 0; // ========== 懒加载 + 重试机制相关状态 ========== // 各区域加载状态(懒加载区域初始为 false,等待可见时触发) bool _isLoadingUserData = true; bool _isLoadingReferral = false; bool _isLoadingAuthorization = false; String? _userDataError; String? _referralError; String? _authorizationError; // 各区域重试计数 int _userDataRetryCount = 0; int _referralRetryCount = 0; int _authorizationRetryCount = 0; int _walletRetryCount = 0; static const int _maxRetries = 3; static const int _retryDelayMs = 1000; // 懒加载标记(是否已触发过加载) bool _hasLoadedReferral = false; bool _hasLoadedAuthorization = false; bool _hasLoadedWallet = false; // 定时刷新相关 Timer? _refreshTimer; static const int _autoRefreshIntervalSeconds = 60; // 60秒自动刷新一次可见区域 bool _isReferralVisible = false; bool _isAuthorizationVisible = false; bool _isWalletVisible = false; // 防抖相关(防止快速滑动导致大量请求) Timer? _referralDebounceTimer; Timer? _authorizationDebounceTimer; Timer? _walletDebounceTimer; static const int _debounceDelayMs = 300; // 300ms 防抖延迟 @override void initState() { super.initState(); // 首屏数据:用户基本信息(从本地存储优先) _checkLocalAvatarSync(); _loadUserData(); _loadAppInfo(); // 通知数量(轻量级请求) _loadUnreadNotificationCount(); // 启动定时刷新(可见区域的数据) _startAutoRefreshTimer(); } /// 加载应用信息 Future _loadAppInfo() async { try { // 加载包信息 final packageInfo = await PackageInfo.fromPlatform(); // 加载设备信息 final deviceInfoPlugin = DeviceInfoPlugin(); String deviceModel = ''; String osVersion = ''; String platform = ''; if (Platform.isAndroid) { final androidInfo = await deviceInfoPlugin.androidInfo; deviceModel = '${androidInfo.brand} ${androidInfo.model}'; osVersion = 'Android ${androidInfo.version.release} (SDK ${androidInfo.version.sdkInt})'; platform = 'Android'; } else if (Platform.isIOS) { final iosInfo = await deviceInfoPlugin.iosInfo; deviceModel = iosInfo.model; osVersion = '${iosInfo.systemName} ${iosInfo.systemVersion}'; platform = 'iOS'; } if (mounted) { setState(() { _appVersion = packageInfo.version; _buildNumber = packageInfo.buildNumber; _packageName = packageInfo.packageName; _deviceModel = deviceModel; _osVersion = osVersion; _platform = platform; }); } } catch (e) { debugPrint('[ProfilePage] 加载应用信息失败: $e'); } } /// 同步检查本地头像文件(在 build 之前快速获取) void _checkLocalAvatarSync() { // 使用 WidgetsBinding 确保在第一帧渲染前执行 WidgetsBinding.instance.addPostFrameCallback((_) async { final accountService = ref.read(accountServiceProvider); final localPath = await accountService.getLocalAvatarPath(); if (mounted && localPath != null && _localAvatarPath == null) { setState(() { _localAvatarPath = localPath; }); } }); } /// 加载用户数据 Future _loadUserData() async { debugPrint('[ProfilePage] _loadUserData() - 开始加载本地存储数据...'); final accountService = ref.read(accountServiceProvider); // 并行加载本地存储数据 final results = await Future.wait([ accountService.getUsername(), accountService.getUserSerialNum(), accountService.getAvatarSvg(), accountService.getAvatarUrl(), accountService.getLocalAvatarPath(), accountService.getReferralCode(), ]); final username = results[0] as String?; final serialNum = results[1] as String?; final avatarSvg = results[2] as String?; final avatarUrl = results[3] as String?; final localAvatarPath = results[4] as String?; final referralCode = results[5] as String?; debugPrint('[ProfilePage] _loadUserData() - 本地存储数据:'); debugPrint('[ProfilePage] username: $username'); debugPrint('[ProfilePage] serialNum: $serialNum'); debugPrint('[ProfilePage] avatarSvg: ${avatarSvg != null ? "长度=${avatarSvg.length}" : "null"}'); debugPrint('[ProfilePage] avatarUrl: $avatarUrl'); debugPrint('[ProfilePage] localAvatarPath: $localAvatarPath'); if (mounted) { setState(() { _nickname = username ?? '未设置昵称'; _serialNumber = serialNum?.toString() ?? '--'; _avatarSvg = avatarSvg; _avatarUrl = avatarUrl; _localAvatarPath = localAvatarPath; _referralCode = referralCode ?? '--'; }); // 如果有远程URL但没有本地缓存,后台下载并缓存 if (avatarUrl != null && avatarUrl.isNotEmpty && localAvatarPath == null) { _downloadAndCacheAvatar(avatarUrl); } } // 从API获取完整用户信息(包括推荐人) _loadMeData(); } /// 从API加载完整用户信息 Future _loadMeData() async { try { debugPrint('[ProfilePage] _loadMeData() - 开始加载用户信息...'); final accountService = ref.read(accountServiceProvider); final meData = await accountService.getMe(); debugPrint('[ProfilePage] _loadMeData() - API返回数据:'); debugPrint('[ProfilePage] nickname: ${meData.nickname}'); debugPrint('[ProfilePage] avatarUrl: ${meData.avatarUrl != null ? "长度=${meData.avatarUrl!.length}" : "null"}'); debugPrint('[ProfilePage] avatarUrl内容前100字符: ${meData.avatarUrl?.substring(0, meData.avatarUrl!.length > 100 ? 100 : meData.avatarUrl!.length) ?? "null"}'); debugPrint('[ProfilePage] 当前_avatarSvg: ${_avatarSvg != null ? "长度=${_avatarSvg!.length}" : "null"}'); if (mounted) { setState(() { _referrerSerial = meData.inviterSequence?.toString() ?? '无'; // 如果API返回了更新的数据,也更新本地显示 if (meData.nickname.isNotEmpty) { _nickname = meData.nickname; } if (meData.referralCode.isNotEmpty) { _referralCode = meData.referralCode; } // 如果本地没有头像但API返回了头像,更新显示 debugPrint('[ProfilePage] _loadMeData() - 检查头像同步条件:'); debugPrint('[ProfilePage] _avatarSvg == null: ${_avatarSvg == null}'); debugPrint('[ProfilePage] meData.avatarUrl != null: ${meData.avatarUrl != null}'); debugPrint('[ProfilePage] meData.avatarUrl.isNotEmpty: ${meData.avatarUrl?.isNotEmpty ?? false}'); if (_avatarSvg == null && meData.avatarUrl != null && meData.avatarUrl!.isNotEmpty) { debugPrint('[ProfilePage] _loadMeData() - 同步头像到本地!'); _avatarSvg = meData.avatarUrl; // 同时保存到本地存储,避免下次还需要从API获取 accountService.updateLocalAvatarSvg(meData.avatarUrl!); } else { debugPrint('[ProfilePage] _loadMeData() - 不需要同步头像 (条件不满足)'); } }); } } catch (e, stackTrace) { debugPrint('[ProfilePage] 加载用户信息失败: $e'); debugPrint('[ProfilePage] 堆栈: $stackTrace'); // 失败时保持现有数据,不影响页面显示 } } /// 加载推荐数据 (from referral-service) /// [isRefresh] 是否为刷新操作(刷新时不显示加载状态) Future _loadReferralData({bool isRefresh = false}) async { if (!isRefresh) { setState(() { _isLoadingReferral = true; _referralError = null; }); } try { debugPrint('[ProfilePage] 开始加载推荐数据...'); final referralService = ref.read(referralServiceProvider); // 并行加载推荐信息和直推列表 final results = await Future.wait([ referralService.getMyReferralInfo(), referralService.getDirectReferrals(limit: 10), ]); final referralInfo = results[0] as ReferralInfoResponse; final directReferrals = results[1] as DirectReferralsResponse; debugPrint('[ProfilePage] 推荐数据加载成功: directReferralCount=${referralInfo.directReferralCount}, totalTeamCount=${referralInfo.totalTeamCount}'); if (mounted) { setState(() { _isLoadingReferral = false; _referralError = null; _referralRetryCount = 0; _directReferralCount = referralInfo.directReferralCount; _totalTeamCount = referralInfo.totalTeamCount; _personalPlantingCount = referralInfo.personalPlantingCount; _teamPlantingCount = referralInfo.teamPlantingCount; _leaderboardScore = referralInfo.leaderboardScore; _leaderboardRank = referralInfo.leaderboardRank; // 转换直推列表格式 _referrals = directReferrals.referrals.map((r) => { 'serial': r.accountSequence, // 使用8位账户序列号显示 'personal': r.personalPlantingCount, // 个人认种量 'team': r.teamPlantingCount, // 团队认种量 }).toList(); }); } } catch (e, stackTrace) { debugPrint('[ProfilePage] 加载推荐数据失败: $e'); debugPrint('[ProfilePage] 堆栈: $stackTrace'); if (mounted) { setState(() { _isLoadingReferral = false; _referralError = '加载失败'; }); // 自动重试 _scheduleRetry( section: '推荐数据', retryCount: _referralRetryCount, loadFunction: () => _loadReferralData(), updateRetryCount: (count) => _referralRetryCount = count, ); } } } /// 加载授权数据 (from authorization-service) /// [isRefresh] 是否为刷新操作(刷新时不显示加载状态) Future _loadAuthorizationData({bool isRefresh = false}) async { if (!isRefresh) { setState(() { _isLoadingAuthorization = true; _authorizationError = null; }); } try { debugPrint('[ProfilePage] 开始加载授权数据...'); final authorizationService = ref.read(authorizationServiceProvider); final summary = await authorizationService.getMyAuthorizationSummary(); debugPrint('[ProfilePage] 授权数据加载成功:'); debugPrint('[ProfilePage] 社区授权: ${summary.community != null}'); debugPrint('[ProfilePage] 市团队授权: ${summary.authCityCompany != null}'); debugPrint('[ProfilePage] 市区域授权: ${summary.cityCompany != null}'); debugPrint('[ProfilePage] 省团队授权: ${summary.authProvinceCompany != null}'); debugPrint('[ProfilePage] 省区域授权: ${summary.provinceCompany != null}'); // 获取社区层级数据 try { final hierarchy = await authorizationService.getMyCommunityHierarchy(); if (mounted) { setState(() { _parentCommunity = hierarchy.parentCommunity.communityName; _childCommunity = hierarchy.childCommunityCount > 0 ? '${hierarchy.childCommunities.first.communityName}${hierarchy.childCommunityCount > 1 ? " 等${hierarchy.childCommunityCount}个" : ""}' : '--'; }); } debugPrint('[ProfilePage] 社区层级: 上级=${hierarchy.parentCommunity.communityName}, 下级数量=${hierarchy.childCommunityCount}'); } catch (e) { debugPrint('[ProfilePage] 获取社区层级失败: $e'); // 失败时保持默认值 } if (mounted) { setState(() { _isLoadingAuthorization = false; _authorizationError = null; _authorizationRetryCount = 0; _community = summary.communityName ?? '--'; _authCityCompany = summary.authCityCompanyName ?? '--'; _cityCompany = summary.cityCompanyName ?? '--'; _authProvinceCompany = summary.authProvinceCompanyName ?? '--'; _provinceCompany = summary.provinceCompanyName ?? '--'; // 更新社区考核数据 if (summary.community != null) { _hasCommunityAuth = true; _communityBenefitActive = summary.community!.benefitActive; _communityCurrentTreeCount = summary.community!.currentTreeCount; _communityInitialTarget = summary.community!.initialTargetTreeCount; _communityMonthlyTarget = summary.community!.monthlyTargetTreeCount; _communityMonthIndex = summary.community!.currentMonthIndex; debugPrint('[ProfilePage] 社区考核数据:'); debugPrint('[ProfilePage] 权益激活: $_communityBenefitActive'); debugPrint('[ProfilePage] 当前数量: $_communityCurrentTreeCount'); debugPrint('[ProfilePage] 初始目标: $_communityInitialTarget'); debugPrint('[ProfilePage] 月度目标: $_communityMonthlyTarget'); debugPrint('[ProfilePage] 考核月份: $_communityMonthIndex'); } else { _hasCommunityAuth = false; } // 更新市团队考核数据 if (summary.authCityCompany != null) { _hasAuthCityCompanyAuth = true; _authCityCompanyBenefitActive = summary.authCityCompany!.benefitActive; _authCityCompanyCurrentTreeCount = summary.authCityCompany!.currentTreeCount; _authCityCompanyInitialTarget = summary.authCityCompany!.initialTargetTreeCount; _authCityCompanyMonthlyTarget = summary.authCityCompany!.monthlyTargetTreeCount; _authCityCompanyMonthIndex = summary.authCityCompany!.currentMonthIndex; } else { _hasAuthCityCompanyAuth = false; } // 更新省团队考核数据 if (summary.authProvinceCompany != null) { _hasAuthProvinceCompanyAuth = true; _authProvinceCompanyBenefitActive = summary.authProvinceCompany!.benefitActive; _authProvinceCompanyCurrentTreeCount = summary.authProvinceCompany!.currentTreeCount; _authProvinceCompanyInitialTarget = summary.authProvinceCompany!.initialTargetTreeCount; _authProvinceCompanyMonthlyTarget = summary.authProvinceCompany!.monthlyTargetTreeCount; _authProvinceCompanyMonthIndex = summary.authProvinceCompany!.currentMonthIndex; } else { _hasAuthProvinceCompanyAuth = false; } // 更新市区域考核数据 if (summary.cityCompany != null) { _hasCityCompanyAuth = true; _cityCompanyBenefitActive = summary.cityCompany!.benefitActive; _cityCompanyCurrentTreeCount = summary.cityCompany!.currentTreeCount; _cityCompanyInitialTarget = summary.cityCompany!.initialTargetTreeCount; _cityCompanyMonthlyTarget = summary.cityCompany!.monthlyTargetTreeCount; _cityCompanyMonthIndex = summary.cityCompany!.currentMonthIndex; } else { _hasCityCompanyAuth = false; } // 更新省区域考核数据 if (summary.provinceCompany != null) { _hasProvinceCompanyAuth = true; _provinceCompanyBenefitActive = summary.provinceCompany!.benefitActive; _provinceCompanyCurrentTreeCount = summary.provinceCompany!.currentTreeCount; _provinceCompanyInitialTarget = summary.provinceCompany!.initialTargetTreeCount; _provinceCompanyMonthlyTarget = summary.provinceCompany!.monthlyTargetTreeCount; _provinceCompanyMonthIndex = summary.provinceCompany!.currentMonthIndex; } else { _hasProvinceCompanyAuth = false; } }); } } catch (e, stackTrace) { debugPrint('[ProfilePage] 加载授权数据失败: $e'); debugPrint('[ProfilePage] 堆栈: $stackTrace'); if (mounted) { setState(() { _isLoadingAuthorization = false; _authorizationError = '加载失败'; }); // 自动重试 _scheduleRetry( section: '授权数据', retryCount: _authorizationRetryCount, loadFunction: () => _loadAuthorizationData(), updateRetryCount: (count) => _authorizationRetryCount = count, ); } } } /// 加载通知未读数量 Future _loadUnreadNotificationCount() async { try { final authState = ref.read(authProvider); final userSerialNum = authState.userSerialNum; if (userSerialNum == null) return; final notificationService = ref.read(notificationServiceProvider); final count = await notificationService.getUnreadCount( userSerialNum: userSerialNum, ); if (mounted) { setState(() { _unreadNotificationCount = count; }); } } catch (e) { debugPrint('[ProfilePage] 加载通知未读数量失败: $e'); } } /// 跳转到通知中心 void _goToNotifications() { context.push(RoutePaths.notifications).then((_) { // 从通知页面返回后刷新未读数量 _loadUnreadNotificationCount(); }); } /// 加载收益数据 (直接从 reward-service 获取) /// [isRefresh] 是否为刷新操作(刷新时不显示加载状态) Future _loadWalletData({bool isRefresh = false}) async { try { debugPrint('[ProfilePage] ========== 加载收益数据 =========='); debugPrint('[ProfilePage] mounted: $mounted, isRefresh: $isRefresh'); if (!isRefresh) { setState(() { _isLoadingWallet = true; _walletError = null; }); } debugPrint('[ProfilePage] 获取 rewardServiceProvider...'); final rewardService = ref.read(rewardServiceProvider); // 并行加载汇总数据、待领取列表、可结算列表、已过期列表 debugPrint('[ProfilePage] 调用 getMyRewardSummary()、getPendingRewards()、getSettleableRewards()、getExpiredRewards()...'); final results = await Future.wait([ rewardService.getMyRewardSummary(), rewardService.getPendingRewards(), rewardService.getSettleableRewards(), rewardService.getExpiredRewards(), ]); final summary = results[0] as RewardSummary; final pendingRewards = results[1] as List; final settleableRewards = results[2] as List; final expiredRewards = results[3] as List; debugPrint('[ProfilePage] -------- 收益数据加载成功 --------'); debugPrint('[ProfilePage] 待领取 USDT: ${summary.pendingUsdt}'); debugPrint('[ProfilePage] 待领取 贡献值: ${summary.pendingHashpower}'); debugPrint('[ProfilePage] 可结算 USDT: ${summary.settleableUsdt}'); debugPrint('[ProfilePage] 已结算 USDT: ${summary.settledTotalUsdt}'); debugPrint('[ProfilePage] 已过期 USDT: ${summary.expiredTotalUsdt}'); debugPrint('[ProfilePage] 已过期 贡献值: ${summary.expiredTotalHashpower}'); debugPrint('[ProfilePage] 过期时间: ${summary.pendingExpireAt}'); debugPrint('[ProfilePage] 剩余秒数: ${summary.pendingRemainingSeconds}'); debugPrint('[ProfilePage] 待领取奖励条目数: ${pendingRewards.length}'); debugPrint('[ProfilePage] 可结算奖励条目数: ${settleableRewards.length}'); debugPrint('[ProfilePage] 已过期奖励条目数: ${expiredRewards.length}'); if (mounted) { debugPrint('[ProfilePage] 更新 UI 状态...'); setState(() { _pendingUsdt = summary.pendingUsdt; _pendingPower = summary.pendingHashpower; _settleableUsdt = summary.settleableUsdt; _settledUsdt = summary.settledTotalUsdt; _expiredUsdt = summary.expiredTotalUsdt; _expiredPower = summary.expiredTotalHashpower; _remainingSeconds = summary.pendingRemainingSeconds; _pendingRewards = pendingRewards; _settleableRewards = settleableRewards; _expiredRewards = expiredRewards; _isLoadingWallet = false; _walletError = null; _walletRetryCount = 0; }); debugPrint('[ProfilePage] UI 状态更新完成'); debugPrint('[ProfilePage] _pendingUsdt: $_pendingUsdt'); debugPrint('[ProfilePage] _remainingSeconds: $_remainingSeconds'); debugPrint('[ProfilePage] _pendingRewards: ${_pendingRewards.length} 条'); debugPrint('[ProfilePage] _settleableRewards: ${_settleableRewards.length} 条'); debugPrint('[ProfilePage] _expiredRewards: ${_expiredRewards.length} 条'); // 启动倒计时(如果有待领取收益) if (_remainingSeconds > 0) { debugPrint('[ProfilePage] 启动倒计时: $_remainingSeconds 秒'); _startCountdown(); } else { debugPrint('[ProfilePage] 无需启动倒计时(剩余时间为0)'); } } else { debugPrint('[ProfilePage] 组件已卸载,跳过 UI 更新'); } debugPrint('[ProfilePage] ================================'); } catch (e, stackTrace) { debugPrint('[ProfilePage] !!!!!!!!!! 加载收益数据失败 !!!!!!!!!!'); debugPrint('[ProfilePage] 错误类型: ${e.runtimeType}'); debugPrint('[ProfilePage] 错误信息: $e'); debugPrint('[ProfilePage] 堆栈跟踪:'); debugPrint('$stackTrace'); if (mounted) { setState(() { _isLoadingWallet = false; _walletError = '加载失败'; }); // 自动重试 _scheduleRetry( section: '收益数据', retryCount: _walletRetryCount, loadFunction: () => _loadWalletData(), updateRetryCount: (count) => _walletRetryCount = count, ); } } } /// 后台下载并缓存头像 Future _downloadAndCacheAvatar(String url) async { final accountService = ref.read(accountServiceProvider); final localPath = await accountService.downloadAndCacheAvatar(url); if (mounted && localPath != null) { setState(() { _localAvatarPath = localPath; }); } } @override void dispose() { _timer?.cancel(); _refreshTimer?.cancel(); // 取消防抖定时器 _referralDebounceTimer?.cancel(); _authorizationDebounceTimer?.cancel(); _walletDebounceTimer?.cancel(); super.dispose(); } /// 启动定时刷新(每60秒刷新一次可见区域的数据) void _startAutoRefreshTimer() { _refreshTimer = Timer.periodic( const Duration(seconds: _autoRefreshIntervalSeconds), (_) => _refreshVisibleSections(), ); } /// 刷新当前可见区域的数据 Future _refreshVisibleSections() async { debugPrint('[ProfilePage] 定时刷新可见区域...'); if (_isWalletVisible && !_isLoadingWallet) { debugPrint('[ProfilePage] 刷新收益数据'); _loadWalletData(isRefresh: true); } if (_isReferralVisible && !_isLoadingReferral) { debugPrint('[ProfilePage] 刷新推荐数据'); _loadReferralData(isRefresh: true); } if (_isAuthorizationVisible && !_isLoadingAuthorization) { debugPrint('[ProfilePage] 刷新授权数据'); _loadAuthorizationData(isRefresh: true); } } /// 下拉刷新 - 刷新所有数据 Future _onRefresh() async { debugPrint('[ProfilePage] 下拉刷新 - 刷新所有数据'); // 重置重试计数 _userDataRetryCount = 0; _referralRetryCount = 0; _authorizationRetryCount = 0; _walletRetryCount = 0; // 并行刷新所有已加载的数据 await Future.wait([ _loadUserData(), if (_hasLoadedReferral) _loadReferralData(isRefresh: true), if (_hasLoadedAuthorization) _loadAuthorizationData(isRefresh: true), if (_hasLoadedWallet) _loadWalletData(isRefresh: true), _loadUnreadNotificationCount(), ]); } /// 处理推荐数据区域可见性变化(带防抖) void _onReferralVisibilityChanged(VisibilityInfo info) { final isVisible = info.visibleFraction > 0.1; _isReferralVisible = isVisible; // 取消之前的防抖定时器 _referralDebounceTimer?.cancel(); // 首次可见时触发加载(带防抖) if (isVisible && !_hasLoadedReferral && !_isLoadingReferral) { _referralDebounceTimer = Timer( const Duration(milliseconds: _debounceDelayMs), () { if (mounted && _isReferralVisible && !_hasLoadedReferral) { _hasLoadedReferral = true; _loadReferralData(); } }, ); } } /// 处理授权数据区域可见性变化(带防抖) void _onAuthorizationVisibilityChanged(VisibilityInfo info) { final isVisible = info.visibleFraction > 0.1; _isAuthorizationVisible = isVisible; // 取消之前的防抖定时器 _authorizationDebounceTimer?.cancel(); // 首次可见时触发加载(带防抖) if (isVisible && !_hasLoadedAuthorization && !_isLoadingAuthorization) { _authorizationDebounceTimer = Timer( const Duration(milliseconds: _debounceDelayMs), () { if (mounted && _isAuthorizationVisible && !_hasLoadedAuthorization) { _hasLoadedAuthorization = true; _loadAuthorizationData(); } }, ); } } /// 处理收益数据区域可见性变化(带防抖) void _onWalletVisibilityChanged(VisibilityInfo info) { final isVisible = info.visibleFraction > 0.1; _isWalletVisible = isVisible; // 取消之前的防抖定时器 _walletDebounceTimer?.cancel(); // 首次可见时触发加载(带防抖) if (isVisible && !_hasLoadedWallet && !_isLoadingWallet) { _walletDebounceTimer = Timer( const Duration(milliseconds: _debounceDelayMs), () { if (mounted && _isWalletVisible && !_hasLoadedWallet) { _hasLoadedWallet = true; _loadWalletData(); } }, ); } } /// 自动重试调度(指数退避) void _scheduleRetry({ required String section, required int retryCount, required Future Function() loadFunction, required void Function(int) updateRetryCount, }) { if (retryCount >= _maxRetries) { debugPrint('[ProfilePage] $section 已达最大重试次数'); return; } final delay = _retryDelayMs * (1 << retryCount); // 指数退避 debugPrint('[ProfilePage] $section 将在 ${delay}ms 后重试 (第 ${retryCount + 1} 次)'); Future.delayed(Duration(milliseconds: delay), () { if (mounted) { updateRetryCount(retryCount + 1); loadFunction(); } }); } /// 开始倒计时 void _startCountdown() { _timer = Timer.periodic(const Duration(seconds: 1), (timer) { if (_remainingSeconds > 0) { setState(() { _remainingSeconds--; }); } }); } /// 格式化倒计时 String _formatCountdown() { final hours = (_remainingSeconds ~/ 3600).toString().padLeft(2, '0'); final minutes = ((_remainingSeconds % 3600) ~/ 60).toString().padLeft(2, '0'); final seconds = (_remainingSeconds % 60).toString().padLeft(2, '0'); return '$hours : $minutes : $seconds'; } /// 复制序列号 void _copySerialNumber() { Clipboard.setData(ClipboardData(text: _serialNumber)); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('序列号已复制'), backgroundColor: Color(0xFFD4AF37), ), ); } /// 跳转到分享页面 void _goToSharePage() { context.push( RoutePaths.share, extra: SharePageParams( shareLink: 'https://s3.szaiai.com/rwadurian/app-release.apk', referralCode: _referralCode, ), ); } /// 领取全部收益 void _claimAllEarnings() { // 如果没有待领取收益,提示用户 if (_pendingUsdt <= 0 && _pendingPower <= 0) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('暂无可领取的收益'), backgroundColor: Color(0xFF8B5A2B), ), ); return; } showDialog( context: context, builder: (dialogContext) => AlertDialog( title: const Text('确认领取'), content: Text( '确定领取全部收益吗?\n绿积分: ${_formatNumber(_pendingUsdt)}', // TODO: 暂时隐藏贡献值显示 // '确定领取全部收益吗?\n绿积分: ${_formatNumber(_pendingUsdt)}\n贡献值: ${_formatNumber(_pendingPower)}', ), actions: [ TextButton( onPressed: () => Navigator.pop(dialogContext), child: const Text('取消'), ), TextButton( onPressed: () async { Navigator.pop(dialogContext); await _doClaimRewards(); }, child: const Text('确认'), ), ], ), ); } /// 执行领取奖励 Future _doClaimRewards() async { try { debugPrint('[ProfilePage] ========== 执行领取奖励 =========='); debugPrint('[ProfilePage] 当前待领取: USDT=$_pendingUsdt, 贡献值=$_pendingPower'); final walletService = ref.read(walletServiceProvider); debugPrint('[ProfilePage] 调用 claimRewards()...'); await walletService.claimRewards(); debugPrint('[ProfilePage] 领取成功,准备刷新数据'); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('领取成功'), backgroundColor: Color(0xFFD4AF37), ), ); // 刷新钱包数据 debugPrint('[ProfilePage] 刷新钱包数据...'); _loadWalletData(); } debugPrint('[ProfilePage] ================================'); } catch (e, stackTrace) { debugPrint('[ProfilePage] !!!!!!!!!! 领取奖励失败 !!!!!!!!!!'); debugPrint('[ProfilePage] 错误: $e'); debugPrint('[ProfilePage] 堆栈: $stackTrace'); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('领取失败: $e'), backgroundColor: Colors.red, ), ); } } } /// 结算 void _onSettlement() { context.go(RoutePaths.trading); } /// 充值USDT void _onDeposit() { context.push(RoutePaths.depositUsdt); } /// 进入交易 void _goToTrading() { context.go(RoutePaths.trading); } /// 进入认种 void _goToPlanting() { context.push(RoutePaths.plantingQuantity); } /// 谷歌验证器 void _goToGoogleAuth() { context.push(RoutePaths.googleAuth); } /// 修改密码 void _goToChangePassword() { context.push(RoutePaths.changePassword); } /// 绑定邮箱 void _goToBindEmail() { context.push(RoutePaths.bindEmail); } /// 编辑资料 Future _goToEditProfile() async { final result = await context.push(RoutePaths.editProfile); // 如果编辑页面返回 true,说明有更新,刷新用户数据 if (result == true && mounted) { _loadUserData(); } } /// 格式化数字 String _formatNumber(double number) { final parts = number.toStringAsFixed(2).split('.'); final intPart = parts[0].replaceAllMapped( RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'), (Match m) => '${m[1]},', ); return '$intPart.${parts[1]}'; } /// 格式化整数 String _formatInt(int number) { return number.toString().replaceAllMapped( RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'), (Match m) => '${m[1]},', ); } /// 构建骨架屏 Widget _buildSkeleton({double height = 100, double? width}) { return Shimmer.fromColors( baseColor: const Color(0xFFE0E0E0), highlightColor: const Color(0xFFF5F5F5), child: Container( height: height, width: width, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), ), ), ); } /// 构建错误重试组件 Widget _buildErrorRetry({ required String error, required VoidCallback onRetry, required int retryCount, double height = 100, }) { return Container( height: height, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: const Color(0xFFFFF3E0), borderRadius: BorderRadius.circular(8), border: Border.all( color: const Color(0xFFFFCC80), width: 1, ), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon( Icons.error_outline, color: Color(0xFFE65100), size: 24, ), const SizedBox(height: 8), Text( retryCount >= _maxRetries ? '加载失败,点击重试' : '加载失败,正在重试...', style: const TextStyle( fontSize: 12, color: Color(0xFFE65100), ), textAlign: TextAlign.center, ), if (retryCount >= _maxRetries) ...[ const SizedBox(height: 8), GestureDetector( onTap: onRetry, child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), decoration: BoxDecoration( color: const Color(0xFFD4AF37), borderRadius: BorderRadius.circular(4), ), child: const Text( '重试', style: TextStyle( fontSize: 12, color: Colors.white, fontWeight: FontWeight.w600, ), ), ), ), ], ], ), ); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, body: Container( width: double.infinity, height: double.infinity, decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Color(0xFFFFF5E6), Color(0xFFFFE4B5), ], ), ), child: SafeArea( child: RefreshIndicator( onRefresh: _onRefresh, color: const Color(0xFFD4AF37), backgroundColor: Colors.white, child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 页面标题行(带通知图标) _buildPageHeader(), const SizedBox(height: 16), // 用户头像和基本信息 _buildUserHeader(), const SizedBox(height: 16), // 推荐人信息卡片(懒加载:推荐数据) VisibilityDetector( key: const Key('referral_section'), onVisibilityChanged: _onReferralVisibilityChanged, child: _buildReferralInfoCard(), ), const SizedBox(height: 16), // 社区/省份标签(懒加载:授权数据) VisibilityDetector( key: const Key('authorization_section'), onVisibilityChanged: _onAuthorizationVisibilityChanged, child: _buildCommunityLabel(), ), const SizedBox(height: 8), // 认种按钮 _buildPlantingButton(), const SizedBox(height: 16), // 主要内容卡片(懒加载:收益数据) VisibilityDetector( key: const Key('wallet_section'), onVisibilityChanged: _onWalletVisibilityChanged, child: _buildMainContentCard(), ), const SizedBox(height: 24), ], ), ), ), ), ), ), ); } /// 构建页面标题行(含通知图标) Widget _buildPageHeader() { return Container( height: 48, padding: const EdgeInsets.only(top: 8), child: Stack( alignment: Alignment.center, children: [ // 页面标题(居中显示) const Center( child: Text( '我的', style: TextStyle( fontSize: 20, fontFamily: 'Inter', fontWeight: FontWeight.w700, color: Color(0xFF5D4037), ), ), ), // 通知图标(右侧,带未读角标) Positioned( right: 0, child: GestureDetector( onTap: _goToNotifications, child: Container( width: 40, height: 40, alignment: Alignment.center, child: Stack( clipBehavior: Clip.none, children: [ const Icon( Icons.notifications_outlined, size: 26, color: Color(0xFF5D4037), ), // 未读角标 if (_unreadNotificationCount > 0) Positioned( top: -4, right: -4, child: Container( padding: const EdgeInsets.symmetric( horizontal: 5, vertical: 2, ), decoration: BoxDecoration( color: const Color(0xFFD4AF37), borderRadius: BorderRadius.circular(10), ), constraints: const BoxConstraints( minWidth: 18, minHeight: 18, ), child: Text( _unreadNotificationCount > 99 ? '99+' : _unreadNotificationCount.toString(), style: const TextStyle( fontSize: 10, fontWeight: FontWeight.w600, color: Colors.white, ), textAlign: TextAlign.center, ), ), ), ], ), ), ), ), ], ), ); } /// 构建用户头像和基本信息 Widget _buildUserHeader() { return Row( children: [ // 头像(点击跳转到编辑资料页面) GestureDetector( onTap: _goToEditProfile, child: Container( width: 80, height: 80, decoration: BoxDecoration( borderRadius: BorderRadius.circular(40), color: const Color(0xFFFFF5E6), ), child: ClipRRect( borderRadius: BorderRadius.circular(40), child: _buildAvatarContent(), ), ), ), const SizedBox(width: 16), // 用户信息 Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( _nickname, style: const TextStyle( fontSize: 20, fontFamily: 'Inter', fontWeight: FontWeight.w700, height: 1.25, letterSpacing: -0.3, color: Color(0xFF5D4037), ), ), const SizedBox(height: 3), Row( children: [ Text( '序列号: $_serialNumber', style: const TextStyle( fontSize: 14, fontFamily: 'Inter', height: 1.5, color: Color(0xCC5D4037), ), ), const SizedBox(width: 8), GestureDetector( onTap: _copySerialNumber, child: const Icon( Icons.copy, size: 16, color: Color(0xCC5D4037), ), ), ], ), ], ), ), ], ); } /// 构建头像内容(优先本地文件,其次网络URL,最后SVG) Widget _buildAvatarContent() { debugPrint('[ProfilePage] _buildAvatarContent() - 开始构建头像'); debugPrint('[ProfilePage] _localAvatarPath: $_localAvatarPath'); debugPrint('[ProfilePage] _avatarUrl: $_avatarUrl'); debugPrint('[ProfilePage] _avatarSvg: ${_avatarSvg != null ? "长度=${_avatarSvg!.length}" : "null"}'); // 1. 优先显示本地缓存的头像文件 if (_localAvatarPath != null && _localAvatarPath!.isNotEmpty) { final file = File(_localAvatarPath!); // 同步检查文件是否存在 if (file.existsSync()) { debugPrint('[ProfilePage] _buildAvatarContent() - 使用本地文件: $_localAvatarPath'); return Image.file( file, width: 80, height: 80, fit: BoxFit.cover, gaplessPlayback: true, // 防止图片切换时闪烁 errorBuilder: (context, error, stackTrace) { debugPrint('[ProfilePage] _buildAvatarContent() - 本地文件加载失败: $error'); // 本地文件加载失败,尝试网络URL return _buildNetworkOrSvgAvatar(); }, ); } else { debugPrint('[ProfilePage] _buildAvatarContent() - 本地文件不存在'); } } // 2. 没有本地缓存,尝试网络URL或SVG return _buildNetworkOrSvgAvatar(); } /// 构建网络URL或SVG头像 Widget _buildNetworkOrSvgAvatar() { debugPrint('[ProfilePage] _buildNetworkOrSvgAvatar() - 尝试网络URL或SVG'); // 尝试显示网络图片URL if (_avatarUrl != null && _avatarUrl!.isNotEmpty) { debugPrint('[ProfilePage] _buildNetworkOrSvgAvatar() - 使用网络URL: $_avatarUrl'); return Image.network( _avatarUrl!, width: 80, height: 80, fit: BoxFit.cover, loadingBuilder: (context, child, loadingProgress) { if (loadingProgress == null) return child; return const Center( child: SizedBox( width: 24, height: 24, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation(Color(0xFFD4AF37)), ), ), ); }, errorBuilder: (context, error, stackTrace) { debugPrint('[ProfilePage] _buildNetworkOrSvgAvatar() - 网络图片加载失败: $error'); // 加载失败时显示SVG或默认头像 return _buildSvgOrDefaultAvatar(); }, ); } // 显示SVG或默认头像 debugPrint('[ProfilePage] _buildNetworkOrSvgAvatar() - 无网络URL,尝试SVG'); return _buildSvgOrDefaultAvatar(); } /// 构建SVG或默认头像 /// 注意:_avatarSvg 可能存储的是 URL(用户上传的图片)或 SVG 字符串(随机生成的头像) Widget _buildSvgOrDefaultAvatar() { if (_avatarSvg != null && _avatarSvg!.isNotEmpty) { debugPrint('[ProfilePage] _buildSvgOrDefaultAvatar() - 检查头像内容,长度=${_avatarSvg!.length}'); debugPrint('[ProfilePage] 内容前50字符: ${_avatarSvg!.substring(0, _avatarSvg!.length > 50 ? 50 : _avatarSvg!.length)}'); // 检测是否是 URL(用户上传的头像图片) if (_avatarSvg!.startsWith('http://') || _avatarSvg!.startsWith('https://')) { debugPrint('[ProfilePage] _buildSvgOrDefaultAvatar() - 检测到是URL,使用网络图片'); return Image.network( _avatarSvg!, width: 80, height: 80, fit: BoxFit.cover, loadingBuilder: (context, child, loadingProgress) { if (loadingProgress == null) return child; return const Center( child: SizedBox( width: 24, height: 24, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation(Color(0xFFD4AF37)), ), ), ); }, errorBuilder: (context, error, stackTrace) { debugPrint('[ProfilePage] _buildSvgOrDefaultAvatar() - 网络图片加载失败: $error'); return const Icon( Icons.person, size: 40, color: Color(0xFF8B5A2B), ); }, ); } // 检测是否是 SVG 字符串(随机生成的 SVG 头像) if (_avatarSvg!.contains('( items: _pendingRewards, peekHeight: 28, expandedCardHeight: 110, enableSound: true, itemBuilder: (item, isSelected, index) => _buildStackedPendingRewardCard(item, isSelected), ), ], ], ); } /// 构建堆叠卡片样式的待领取奖励项 Widget _buildStackedPendingRewardCard(PendingRewardItem item, bool isSelected) { // 确保剩余时间不为负数 final remainingSeconds = item.remainingSeconds > 0 ? item.remainingSeconds : 0; final hours = (remainingSeconds ~/ 3600).toString().padLeft(2, '0'); final minutes = ((remainingSeconds % 3600) ~/ 60).toString().padLeft(2, '0'); final seconds = (remainingSeconds % 60).toString().padLeft(2, '0'); final countdown = '$hours:$minutes:$seconds'; // 构建金额显示文本 final List amountParts = []; if (item.usdtAmount > 0) { amountParts.add('${_formatNumber(item.usdtAmount)} 绿积分'); } // TODO: 暂时隐藏贡献值显示 // if (item.hashpowerAmount > 0) { // amountParts.add('${_formatNumber(item.hashpowerAmount)} 贡献值'); // } final amountText = amountParts.isNotEmpty ? amountParts.join(' ') : '0 绿积分'; return Container( height: isSelected ? 110 : 48, decoration: BoxDecoration( color: isSelected ? Colors.white : const Color(0xFFFFFDF8), borderRadius: BorderRadius.circular(8), border: Border.all( color: isSelected ? const Color(0x44D4AF37) : const Color(0x22D4AF37), width: isSelected ? 1.5 : 1, ), ), child: isSelected ? Padding( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 第一行:权益类型 + 倒计时 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( item.rightTypeName, style: const TextStyle( fontSize: 14, fontFamily: 'Inter', fontWeight: FontWeight.w600, color: Color(0xFF5D4037), ), ), Row( children: [ const Icon( Icons.timer_outlined, size: 14, color: Color(0xFFD4AF37), ), const SizedBox(width: 4), Text( countdown, style: const TextStyle( fontSize: 12, fontFamily: 'Consolas', fontWeight: FontWeight.w600, color: Color(0xFFD4AF37), ), ), ], ), ], ), const SizedBox(height: 8), // 第二行:金额信息 Text( amountText, style: const TextStyle( fontSize: 16, fontFamily: 'Inter', fontWeight: FontWeight.w700, color: Color(0xFF5D4037), ), ), // 第三行:备注信息 if (item.memo.isNotEmpty) ...[ const SizedBox(height: 4), Text( item.memo, style: const TextStyle( fontSize: 11, fontFamily: 'Inter', color: Color(0x995D4037), ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ], ], ), ) : Padding( // 未选中状态:只显示卡片头部预览 padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( item.rightTypeName, style: const TextStyle( fontSize: 13, fontFamily: 'Inter', fontWeight: FontWeight.w500, color: Color(0xFF5D4037), ), ), Text( amountText, style: const TextStyle( fontSize: 13, fontFamily: 'Inter', fontWeight: FontWeight.w600, color: Color(0xFFD4AF37), ), ), ], ), ), ); } /// 构建单条待领取奖励项 Widget _buildPendingRewardItem(PendingRewardItem item) { // 确保剩余时间不为负数 final remainingSeconds = item.remainingSeconds > 0 ? item.remainingSeconds : 0; final hours = (remainingSeconds ~/ 3600).toString().padLeft(2, '0'); final minutes = ((remainingSeconds % 3600) ~/ 60).toString().padLeft(2, '0'); final seconds = (remainingSeconds % 60).toString().padLeft(2, '0'); final countdown = '$hours:$minutes:$seconds'; // 构建金额显示文本 final List amountParts = []; if (item.usdtAmount > 0) { amountParts.add('${_formatNumber(item.usdtAmount)} 绿积分'); } // TODO: 暂时隐藏贡献值显示 // if (item.hashpowerAmount > 0) { // amountParts.add('${_formatNumber(item.hashpowerAmount)} 贡献值'); // } // 如果都为0,显示默认文本 final amountText = amountParts.isNotEmpty ? amountParts.join(' ') : '0 绿积分'; return Container( margin: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), border: Border.all( color: const Color(0x22D4AF37), width: 1, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 第一行:权益类型 + 倒计时 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( item.rightTypeName, style: const TextStyle( fontSize: 14, fontFamily: 'Inter', fontWeight: FontWeight.w600, color: Color(0xFF5D4037), ), ), Row( children: [ const Icon( Icons.timer_outlined, size: 14, color: Color(0xFFD4AF37), ), const SizedBox(width: 4), Text( countdown, style: const TextStyle( fontSize: 12, fontFamily: 'Consolas', fontWeight: FontWeight.w600, color: Color(0xFFD4AF37), ), ), ], ), ], ), const SizedBox(height: 8), // 第二行:金额信息 Text( amountText, style: const TextStyle( fontSize: 16, fontFamily: 'Inter', fontWeight: FontWeight.w700, color: Color(0xFF5D4037), ), ), // 第三行:备注信息(如果有) if (item.memo.isNotEmpty) ...[ const SizedBox(height: 4), Text( item.memo, style: const TextStyle( fontSize: 11, fontFamily: 'Inter', color: Color(0x995D4037), ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ], ], ), ); } /// 构建结算区域 Widget _buildSettlementSection() { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: const Color(0xFFFFF5E6), borderRadius: BorderRadius.circular(8), border: Border.all( color: const Color(0x33D4AF37), width: 1, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 可结算 USDT Row( children: [ const Text( '可结算 (绿积分):', style: TextStyle( fontSize: 14, fontFamily: 'Inter', fontWeight: FontWeight.w500, height: 1.5, color: Color(0xCC5D4037), ), ), Text( _formatNumber(_settleableUsdt), style: const TextStyle( fontSize: 14, fontFamily: 'Inter', fontWeight: FontWeight.w700, height: 1.5, color: Color(0xFF5D4037), ), ), ], ), // 可结算奖励明细列表 if (_settleableRewards.isNotEmpty) ...[ const SizedBox(height: 16), const Divider(color: Color(0x33D4AF37), height: 1), const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( '可结算明细', style: TextStyle( fontSize: 14, fontFamily: 'Inter', fontWeight: FontWeight.w600, color: Color(0xFF5D4037), ), ), Text( '共 ${_settleableRewards.length} 笔', style: const TextStyle( fontSize: 12, fontFamily: 'Inter', color: Color(0xFF8B5A2B), ), ), ], ), const SizedBox(height: 8), // 堆叠卡片展示 StackedCardsView( items: _settleableRewards, peekHeight: 28, expandedCardHeight: 90, enableSound: true, itemBuilder: (item, isSelected, index) => _buildStackedSettleableRewardCard(item, isSelected), ), ], const SizedBox(height: 11), // 已结算 USDT Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ const Text( '已结算 (绿积分):', style: TextStyle( fontSize: 14, fontFamily: 'Inter', fontWeight: FontWeight.w500, height: 1.5, color: Color(0xCC5D4037), ), ), Text( _formatNumber(_settledUsdt), style: const TextStyle( fontSize: 14, fontFamily: 'Inter', fontWeight: FontWeight.w700, height: 1.5, color: Color(0xFF5D4037), ), ), ], ), GestureDetector( onTap: _onSettlement, child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 7), decoration: BoxDecoration( color: const Color(0xFFD4AF37), borderRadius: BorderRadius.circular(4), ), child: const Text( '结算', style: TextStyle( fontSize: 12, fontFamily: 'Inter', fontWeight: FontWeight.w700, height: 1.5, letterSpacing: 0.18, color: Colors.white, ), ), ), ), ], ), ], ), ); } /// 构建单条可结算奖励项 Widget _buildSettleableRewardItem(SettleableRewardItem item) { // 格式化时间(优先使用 claimedAt,否则使用 createdAt) final displayDate = item.claimedAt ?? item.createdAt; final settledDate = '${displayDate.month}/${displayDate.day} ${displayDate.hour.toString().padLeft(2, '0')}:${displayDate.minute.toString().padLeft(2, '0')}'; // 构建金额显示文本 final List amountParts = []; if (item.usdtAmount > 0) { amountParts.add('${_formatNumber(item.usdtAmount)} 绿积分'); } // TODO: 暂时隐藏贡献值显示 // if (item.hashpowerAmount > 0) { // amountParts.add('${_formatNumber(item.hashpowerAmount)} 贡献值'); // } final amountText = amountParts.isNotEmpty ? amountParts.join(' ') : '0 绿积分'; return Container( margin: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), border: Border.all( color: const Color(0x22D4AF37), width: 1, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 第一行:权益类型 + 结算时间 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( item.rightTypeName, style: const TextStyle( fontSize: 14, fontFamily: 'Inter', fontWeight: FontWeight.w600, color: Color(0xFF5D4037), ), ), Row( children: [ const Icon( Icons.check_circle_outline, size: 14, color: Color(0xFF4CAF50), ), const SizedBox(width: 4), Text( settledDate, style: const TextStyle( fontSize: 12, fontFamily: 'Inter', fontWeight: FontWeight.w500, color: Color(0xFF4CAF50), ), ), ], ), ], ), const SizedBox(height: 8), // 第二行:金额信息 Text( amountText, style: const TextStyle( fontSize: 16, fontFamily: 'Inter', fontWeight: FontWeight.w700, color: Color(0xFF5D4037), ), ), ], ), ); } /// 构建堆叠卡片样式的可结算奖励项 Widget _buildStackedSettleableRewardCard(SettleableRewardItem item, bool isSelected) { // 格式化时间(优先使用 claimedAt,否则使用 createdAt) final displayDate = item.claimedAt ?? item.createdAt; final settledDate = '${displayDate.month}/${displayDate.day} ${displayDate.hour.toString().padLeft(2, '0')}:${displayDate.minute.toString().padLeft(2, '0')}'; // 构建金额显示文本 final List amountParts = []; if (item.usdtAmount > 0) { amountParts.add('${_formatNumber(item.usdtAmount)} 绿积分'); } // TODO: 暂时隐藏贡献值显示 // if (item.hashpowerAmount > 0) { // amountParts.add('${_formatNumber(item.hashpowerAmount)} 贡献值'); // } final amountText = amountParts.isNotEmpty ? amountParts.join(' ') : '0 绿积分'; return Container( height: isSelected ? 90 : 48, decoration: BoxDecoration( color: isSelected ? Colors.white : const Color(0xFFFFFDF8), borderRadius: BorderRadius.circular(8), border: Border.all( color: isSelected ? const Color(0x44D4AF37) : const Color(0x22D4AF37), width: isSelected ? 1.5 : 1, ), ), child: isSelected ? Padding( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 第一行:权益类型 + 结算时间 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( item.rightTypeName, style: const TextStyle( fontSize: 14, fontFamily: 'Inter', fontWeight: FontWeight.w600, color: Color(0xFF5D4037), ), ), Row( children: [ const Icon( Icons.check_circle_outline, size: 14, color: Color(0xFF4CAF50), ), const SizedBox(width: 4), Text( settledDate, style: const TextStyle( fontSize: 12, fontFamily: 'Inter', fontWeight: FontWeight.w500, color: Color(0xFF4CAF50), ), ), ], ), ], ), const SizedBox(height: 8), // 第二行:金额信息 Text( amountText, style: const TextStyle( fontSize: 16, fontFamily: 'Inter', fontWeight: FontWeight.w700, color: Color(0xFF5D4037), ), ), ], ), ) : Padding( // 未选中状态:只显示卡片头部预览 padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( item.rightTypeName, style: const TextStyle( fontSize: 13, fontFamily: 'Inter', fontWeight: FontWeight.w500, color: Color(0xFF5D4037), ), ), Text( amountText, style: const TextStyle( fontSize: 13, fontFamily: 'Inter', fontWeight: FontWeight.w600, color: Color(0xFF4CAF50), ), ), ], ), ), ); } /// 构建已过期区域 Widget _buildExpiredSection() { return SizedBox( width: double.infinity, child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: const Color(0xFFFFF5E6), borderRadius: BorderRadius.circular(8), border: Border.all( color: const Color(0x33D4AF37), width: 1, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '已过期', style: TextStyle( fontSize: 16, fontFamily: 'Inter', fontWeight: FontWeight.w700, height: 1.5, color: Color(0xFF5D4037), ), ), const SizedBox(height: 7), // 已过期 USDT Row( children: [ const Text( '绿积分:', style: TextStyle( fontSize: 14, fontFamily: 'Inter', fontWeight: FontWeight.w500, height: 1.5, color: Color(0xCC5D4037), ), ), Text( _formatNumber(_expiredUsdt), style: const TextStyle( fontSize: 14, fontFamily: 'Inter', fontWeight: FontWeight.w700, height: 1.5, color: Color(0xFF5D4037), ), ), ], ), // TODO: 暂时隐藏贡献值显示 // const SizedBox(height: 8), // // 已过期 贡献值 // Row( // children: [ // const Text( // '贡献值:', // style: TextStyle( // fontSize: 14, // fontFamily: 'Inter', // fontWeight: FontWeight.w500, // height: 1.5, // color: Color(0xCC5D4037), // ), // ), // Text( // _formatNumber(_expiredPower), // style: const TextStyle( // fontSize: 14, // fontFamily: 'Inter', // fontWeight: FontWeight.w700, // height: 1.5, // color: Color(0xFF5D4037), // ), // ), // ], // ), // 已过期奖励明细列表 if (_expiredRewards.isNotEmpty) ...[ const SizedBox(height: 16), const Divider(color: Color(0x33D4AF37), height: 1), const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( '已过期明细', style: TextStyle( fontSize: 14, fontFamily: 'Inter', fontWeight: FontWeight.w600, color: Color(0xFF5D4037), ), ), Text( '共 ${_expiredRewards.length} 笔', style: const TextStyle( fontSize: 12, fontFamily: 'Inter', color: Color(0xFF8B5A2B), ), ), ], ), const SizedBox(height: 8), // 堆叠卡片展示 StackedCardsView( items: _expiredRewards, peekHeight: 28, expandedCardHeight: 90, enableSound: true, itemBuilder: (item, isSelected, index) => _buildStackedExpiredRewardCard(item, isSelected), ), ], ], ), ), ); } /// 构建单条已过期奖励项 Widget _buildExpiredRewardItem(ExpiredRewardItem item) { // 格式化过期时间 final expiredDate = '${item.expiredAt.month}/${item.expiredAt.day} ${item.expiredAt.hour.toString().padLeft(2, '0')}:${item.expiredAt.minute.toString().padLeft(2, '0')}'; // 构建金额显示文本 final List amountParts = []; if (item.usdtAmount > 0) { amountParts.add('${_formatNumber(item.usdtAmount)} 绿积分'); } // TODO: 暂时隐藏贡献值显示 // if (item.hashpowerAmount > 0) { // amountParts.add('${_formatNumber(item.hashpowerAmount)} 贡献值'); // } final amountText = amountParts.isNotEmpty ? amountParts.join(' ') : '0 绿积分'; return Container( margin: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: const Color(0xFFF5F5F5), // 灰色背景表示已过期 borderRadius: BorderRadius.circular(8), border: Border.all( color: const Color(0x22999999), width: 1, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 第一行:权益类型 + 过期时间 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( item.rightTypeName, style: const TextStyle( fontSize: 14, fontFamily: 'Inter', fontWeight: FontWeight.w600, color: Color(0xFF999999), // 灰色文字 ), ), Row( children: [ const Icon( Icons.cancel_outlined, size: 14, color: Color(0xFFE57373), // 红色图标 ), const SizedBox(width: 4), Text( expiredDate, style: const TextStyle( fontSize: 12, fontFamily: 'Inter', fontWeight: FontWeight.w500, color: Color(0xFFE57373), // 红色文字 ), ), ], ), ], ), const SizedBox(height: 8), // 第二行:金额信息 Text( amountText, style: const TextStyle( fontSize: 16, fontFamily: 'Inter', fontWeight: FontWeight.w700, color: Color(0xFF999999), // 灰色文字 ), ), ], ), ); } /// 构建堆叠卡片样式的已过期奖励项 Widget _buildStackedExpiredRewardCard(ExpiredRewardItem item, bool isSelected) { // 格式化过期时间 final expiredDate = '${item.expiredAt.month}/${item.expiredAt.day} ${item.expiredAt.hour.toString().padLeft(2, '0')}:${item.expiredAt.minute.toString().padLeft(2, '0')}'; // 构建金额显示文本 final List amountParts = []; if (item.usdtAmount > 0) { amountParts.add('${_formatNumber(item.usdtAmount)} 绿积分'); } // TODO: 暂时隐藏贡献值显示 // if (item.hashpowerAmount > 0) { // amountParts.add('${_formatNumber(item.hashpowerAmount)} 贡献值'); // } final amountText = amountParts.isNotEmpty ? amountParts.join(' ') : '0 绿积分'; return Container( height: isSelected ? 90 : 48, decoration: BoxDecoration( color: isSelected ? const Color(0xFFF5F5F5) : const Color(0xFFFAFAFA), borderRadius: BorderRadius.circular(8), border: Border.all( color: isSelected ? const Color(0x44999999) : const Color(0x22999999), width: isSelected ? 1.5 : 1, ), ), child: isSelected ? Padding( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 第一行:权益类型 + 过期时间 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( item.rightTypeName, style: const TextStyle( fontSize: 14, fontFamily: 'Inter', fontWeight: FontWeight.w600, color: Color(0xFF999999), ), ), Row( children: [ const Icon( Icons.cancel_outlined, size: 14, color: Color(0xFFE57373), ), const SizedBox(width: 4), Text( expiredDate, style: const TextStyle( fontSize: 12, fontFamily: 'Inter', fontWeight: FontWeight.w500, color: Color(0xFFE57373), ), ), ], ), ], ), const SizedBox(height: 8), // 第二行:金额信息 Text( amountText, style: const TextStyle( fontSize: 16, fontFamily: 'Inter', fontWeight: FontWeight.w700, color: Color(0xFF999999), ), ), ], ), ) : Padding( // 未选中状态:只显示卡片头部预览 padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( item.rightTypeName, style: const TextStyle( fontSize: 13, fontFamily: 'Inter', fontWeight: FontWeight.w500, color: Color(0xFF999999), ), ), Text( amountText, style: const TextStyle( fontSize: 13, fontFamily: 'Inter', fontWeight: FontWeight.w600, color: Color(0xFFE57373), ), ), ], ), ), ); } /// 构建操作按钮 Widget _buildActionButtons() { return Column( children: [ // 充值 USDT 按钮 GestureDetector( onTap: _onDeposit, child: Container( width: double.infinity, height: 48, decoration: BoxDecoration( color: const Color(0xFFD4AF37), borderRadius: BorderRadius.circular(8), ), child: const Center( child: Text( '充值绿积分', style: TextStyle( fontSize: 16, fontFamily: 'Inter', fontWeight: FontWeight.w700, height: 1.5, letterSpacing: 0.24, color: Colors.white, ), ), ), ), ), // 进入交易按钮(暂时隐藏) // const SizedBox(height: 12), // GestureDetector( // onTap: _goToTrading, // child: Container( // width: double.infinity, // height: 48, // decoration: BoxDecoration( // color: const Color(0xFF8B5A2B), // borderRadius: BorderRadius.circular(8), // ), // child: const Center( // child: Text( // '进入交易 (卖出 DST → 绿积分)', // style: TextStyle( // fontSize: 16, // fontFamily: 'Inter', // fontWeight: FontWeight.w700, // height: 1.5, // letterSpacing: 0.24, // color: Colors.white, // ), // ), // ), // ), // ), ], ); } /// 构建团队统计 Widget _buildTeamStats() { return Row( children: [ Expanded( child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: const Color(0xFFFFF5E6), borderRadius: BorderRadius.circular(8), border: Border.all( color: const Color(0x33D4AF37), width: 1, ), ), child: Column( children: [ const Text( '直推人数', style: TextStyle( fontSize: 12, fontFamily: 'Inter', fontWeight: FontWeight.w500, height: 1.5, color: Color(0xCC5D4037), ), textAlign: TextAlign.center, ), const SizedBox(height: 6), Text( _formatInt(_directReferralCount), style: const TextStyle( fontSize: 20, fontFamily: 'Inter', fontWeight: FontWeight.w700, height: 1.25, color: Color(0xFF5D4037), ), textAlign: TextAlign.center, ), ], ), ), ), const SizedBox(width: 8), Expanded( child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: const Color(0xFFFFF5E6), borderRadius: BorderRadius.circular(8), border: Border.all( color: const Color(0x33D4AF37), width: 1, ), ), child: Column( children: [ const Text( '个人种植数', style: TextStyle( fontSize: 12, fontFamily: 'Inter', fontWeight: FontWeight.w500, height: 1.5, color: Color(0xCC5D4037), ), textAlign: TextAlign.center, ), const SizedBox(height: 6), Text( _formatInt(_personalPlantingCount), style: const TextStyle( fontSize: 20, fontFamily: 'Inter', fontWeight: FontWeight.w700, height: 1.25, color: Color(0xFF5D4037), ), textAlign: TextAlign.center, ), ], ), ), ), const SizedBox(width: 8), Expanded( child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: const Color(0xFFFFF5E6), borderRadius: BorderRadius.circular(8), border: Border.all( color: const Color(0x33D4AF37), width: 1, ), ), child: Column( children: [ const Text( '团队种植数', style: TextStyle( fontSize: 12, fontFamily: 'Inter', fontWeight: FontWeight.w500, height: 1.5, color: Color(0xCC5D4037), ), textAlign: TextAlign.center, ), const SizedBox(height: 6), Text( _formatInt(_teamPlantingCount), style: const TextStyle( fontSize: 20, fontFamily: 'Inter', fontWeight: FontWeight.w700, height: 1.25, color: Color(0xFF5D4037), ), textAlign: TextAlign.center, ), ], ), ), ), ], ); } /// 构建直推列表 Widget _buildReferralList() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Padding( padding: EdgeInsets.only(top: 8), child: Text( '直推列表', style: TextStyle( fontSize: 16, fontFamily: 'Inter', fontWeight: FontWeight.w700, height: 1.5, color: Color(0xFF5D4037), ), ), ), const SizedBox(height: 8), ..._referrals.map((referral) => Padding( padding: const EdgeInsets.only(bottom: 4), child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: const Color(0xCCFFF5E6), borderRadius: BorderRadius.circular(8), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( '序列号: ${referral['serial']}', style: const TextStyle( fontSize: 14, fontFamily: 'Inter', height: 1.43, color: Color(0xFF5D4037), ), ), Text( '个人/团队: ${referral['personal']} / ${referral['team']}', style: const TextStyle( fontSize: 14, fontFamily: 'Inter', height: 1.43, color: Color(0xCC5D4037), ), ), ], ), ), )), ], ); } /// 构建我的团队树 Widget _buildMyTeamTree() { // 创建或更新根节点(缓存以保持展开状态) // 只有在数据变化时才创建新的根节点 if (_teamTreeRootNode == null || _teamTreeRootNode!.accountSequence != _serialNumber || _teamTreeRootNode!.personalPlantingCount != _personalPlantingCount || _teamTreeRootNode!.teamPlantingCount != _teamPlantingCount || _teamTreeRootNode!.directReferralCount != _directReferralCount) { _teamTreeRootNode = TeamTreeNode.createRoot( accountSequence: _serialNumber, personalPlantingCount: _personalPlantingCount, teamPlantingCount: _teamPlantingCount, directReferralCount: _directReferralCount, ); } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Padding( padding: EdgeInsets.only(top: 8), child: Text( '我的团队', style: TextStyle( fontSize: 16, fontFamily: 'Inter', fontWeight: FontWeight.w700, height: 1.5, color: Color(0xFF5D4037), ), ), ), const SizedBox(height: 8), Container( width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: const Color(0xCCFFF5E6), borderRadius: BorderRadius.circular(8), border: Border.all( color: const Color(0x33D4AF37), width: 1, ), ), child: _directReferralCount == 0 ? const Center( child: Padding( padding: EdgeInsets.all(20), child: Text( '暂无下级成员', style: TextStyle( fontSize: 14, fontFamily: 'Inter', color: Color(0x995D4037), ), ), ), ) : TeamTreeWidget( rootNode: _teamTreeRootNode!, referralService: ref.read(referralServiceProvider), ), ), ], ); } /// 构建分享按钮 Widget _buildShareButton() { return GestureDetector( onTap: _goToSharePage, child: Container( width: double.infinity, height: 48, padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), decoration: BoxDecoration( color: const Color(0x33D4AF37), borderRadius: BorderRadius.circular(8), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: const [ Icon( Icons.share, size: 24, color: Color(0xFFD4AF37), ), SizedBox(width: 8), Text( '分享邀请', style: TextStyle( fontSize: 16, fontFamily: 'Inter', fontWeight: FontWeight.w700, height: 1.5, letterSpacing: 0.24, color: Color(0xFFD4AF37), ), ), ], ), ), ); } /// 构建社区权益考核 Widget _buildCommunityAssessment() { // 如果没有社区授权,显示红色提示信息 if (!_hasCommunityAuth) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: const Color(0x15F44336), // 浅红色背景 borderRadius: BorderRadius.circular(12), border: Border.all( color: const Color(0x33F44336), // 红色边框 width: 1, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( '社区权益考核', style: TextStyle( fontSize: 18, fontFamily: 'Inter', fontWeight: FontWeight.w700, height: 1.56, color: Color(0xFF5D4037), ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration( color: const Color(0x20F44336), borderRadius: BorderRadius.circular(4), ), child: const Text( '未授权', style: TextStyle( fontSize: 12, fontFamily: 'Inter', fontWeight: FontWeight.w600, color: Color(0xFFF44336), // 红色 ), ), ), ], ), const SizedBox(height: 15), const Text( '暂无社区授权', style: TextStyle( fontSize: 14, fontFamily: 'Inter', fontWeight: FontWeight.w500, height: 1.43, color: Color(0xFFF44336), // 红色 ), ), const SizedBox(height: 8), const Text( '请联系管理员申请社区授权', style: TextStyle( fontSize: 12, fontFamily: 'Inter', height: 1.5, color: Color(0x99F44336), // 浅红色 ), ), ], ), ); } // 计算进度百分比(用于进度条) final progressRatio = _communityInitialTarget > 0 ? (_communityCurrentTreeCount / _communityInitialTarget).clamp(0.0, 1.0) : 0.0; // 确定状态文字和颜色 final String statusText; final Color statusColor; if (_communityBenefitActive) { statusText = '已激活'; statusColor = const Color(0xFF4CAF50); // 绿色 } else { statusText = '待激活'; statusColor = const Color(0xFFFF9800); // 橙色 } return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: const Color(0x80FFFFFF), borderRadius: BorderRadius.circular(12), border: Border.all( color: const Color(0x33D4AF37), width: 1, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 标题和状态 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( '社区权益考核', style: TextStyle( fontSize: 18, fontFamily: 'Inter', fontWeight: FontWeight.w700, height: 1.56, color: Color(0xFF5D4037), ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration( color: statusColor.withOpacity(0.15), borderRadius: BorderRadius.circular(4), ), child: Text( statusText, style: TextStyle( fontSize: 12, fontFamily: 'Inter', fontWeight: FontWeight.w600, color: statusColor, ), ), ), ], ), const SizedBox(height: 15), // 考核月份(仅在权益激活后显示) if (_communityBenefitActive && _communityMonthIndex > 0) ...[ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( '当前考核月份', style: TextStyle( fontSize: 14, fontFamily: 'Inter', fontWeight: FontWeight.w500, height: 1.43, color: Color(0xCC5D4037), ), ), Text( '第 $_communityMonthIndex 月', style: const TextStyle( fontSize: 14, fontFamily: 'Inter', fontWeight: FontWeight.w700, height: 1.43, color: Color(0xFF5D4037), ), ), ], ), const SizedBox(height: 16), ], // 团队认种数量 / 目标 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( _communityBenefitActive ? '团队认种 / 月度目标' : '团队认种 / 激活目标', style: const TextStyle( fontSize: 14, fontFamily: 'Inter', fontWeight: FontWeight.w500, height: 1.43, color: Color(0xCC5D4037), ), ), Text( '$_communityCurrentTreeCount / ${_communityBenefitActive ? _communityMonthlyTarget : _communityInitialTarget}', style: const TextStyle( fontSize: 14, fontFamily: 'Inter', fontWeight: FontWeight.w500, height: 1.43, color: Color(0xFF5D4037), ), ), ], ), const SizedBox(height: 4), // 进度条 Container( height: 6, decoration: BoxDecoration( color: const Color(0x33D4AF37), borderRadius: BorderRadius.circular(3), ), child: FractionallySizedBox( alignment: Alignment.centerLeft, widthFactor: progressRatio, child: Container( decoration: BoxDecoration( color: const Color(0xFFD4AF37), borderRadius: BorderRadius.circular(3), ), ), ), ), const SizedBox(height: 16), // 社区贡献奖励 Row( children: [ const Text( '社区贡献奖励', style: TextStyle( fontSize: 14, fontFamily: 'Inter', fontWeight: FontWeight.w500, height: 1.43, color: Color(0xCC5D4037), ), ), const SizedBox(width: 39), Expanded( child: Text( _communityBenefitActive ? '每新增认种 1 棵可获得 80 绿积分' : '需团队认种达到 $_communityInitialTarget 棵激活', style: TextStyle( fontSize: 14, fontFamily: 'Inter', fontWeight: FontWeight.w500, height: 1.43, color: _communityBenefitActive ? const Color(0xFFD4AF37) : const Color(0x995D4037), ), ), ), ], ), ], ), ); } /// 构建通用权益考核组件 /// [title] 标题,如"市团队权益考核" /// [hasAuth] 是否有该授权 /// [benefitActive] 权益是否激活 /// [currentTreeCount] 当前团队认种数量 /// [initialTarget] 初始考核目标 /// [monthlyTarget] 月度考核目标 /// [monthIndex] 当前考核月份 /// [rewardDescription] 奖励描述 Widget _buildRoleAssessment({ required String title, required bool hasAuth, required bool benefitActive, required int currentTreeCount, required int initialTarget, required int monthlyTarget, required int monthIndex, required String rewardDescription, }) { // 如果没有该授权,不显示 if (!hasAuth) { return const SizedBox.shrink(); } // 计算进度百分比(用于进度条) final target = benefitActive ? monthlyTarget : initialTarget; final progressRatio = target > 0 ? (currentTreeCount / target).clamp(0.0, 1.0) : 0.0; // 确定状态文字和颜色 final String statusText; final Color statusColor; if (benefitActive) { statusText = '已激活'; statusColor = const Color(0xFF4CAF50); // 绿色 } else { statusText = '待激活'; statusColor = const Color(0xFFFF9800); // 橙色 } return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: const Color(0x80FFFFFF), borderRadius: BorderRadius.circular(12), border: Border.all( color: const Color(0x33D4AF37), width: 1, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 标题和状态 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( title, style: const TextStyle( fontSize: 18, fontFamily: 'Inter', fontWeight: FontWeight.w700, height: 1.56, color: Color(0xFF5D4037), ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration( color: statusColor.withOpacity(0.15), borderRadius: BorderRadius.circular(4), ), child: Text( statusText, style: TextStyle( fontSize: 12, fontFamily: 'Inter', fontWeight: FontWeight.w600, color: statusColor, ), ), ), ], ), const SizedBox(height: 15), // 考核月份(仅在权益激活后显示) if (benefitActive && monthIndex > 0) ...[ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( '当前考核月份', style: TextStyle( fontSize: 14, fontFamily: 'Inter', fontWeight: FontWeight.w500, height: 1.43, color: Color(0xCC5D4037), ), ), Text( '第 $monthIndex 月', style: const TextStyle( fontSize: 14, fontFamily: 'Inter', fontWeight: FontWeight.w700, height: 1.43, color: Color(0xFF5D4037), ), ), ], ), const SizedBox(height: 16), ], // 团队认种数量 / 目标 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( benefitActive ? '团队认种 / 月度目标' : '团队认种 / 激活目标', style: const TextStyle( fontSize: 14, fontFamily: 'Inter', fontWeight: FontWeight.w500, height: 1.43, color: Color(0xCC5D4037), ), ), Text( '$currentTreeCount / $target', style: const TextStyle( fontSize: 14, fontFamily: 'Inter', fontWeight: FontWeight.w500, height: 1.43, color: Color(0xFF5D4037), ), ), ], ), const SizedBox(height: 4), // 进度条 Container( height: 6, decoration: BoxDecoration( color: const Color(0x33D4AF37), borderRadius: BorderRadius.circular(3), ), child: FractionallySizedBox( alignment: Alignment.centerLeft, widthFactor: progressRatio, child: Container( decoration: BoxDecoration( color: const Color(0xFFD4AF37), borderRadius: BorderRadius.circular(3), ), ), ), ), const SizedBox(height: 16), // 贡献奖励 Row( children: [ const Text( '贡献奖励', style: TextStyle( fontSize: 14, fontFamily: 'Inter', fontWeight: FontWeight.w500, height: 1.43, color: Color(0xCC5D4037), ), ), const SizedBox(width: 39), Expanded( child: Text( benefitActive ? rewardDescription : '需团队认种达到 $initialTarget 棵激活', style: TextStyle( fontSize: 14, fontFamily: 'Inter', fontWeight: FontWeight.w500, height: 1.43, color: benefitActive ? const Color(0xFFD4AF37) : const Color(0x995D4037), ), ), ), ], ), ], ), ); } /// 构建设置菜单 Widget _buildSettingsMenu() { return Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: const Color(0x80FFFFFF), borderRadius: BorderRadius.circular(12), border: Border.all( color: const Color(0x33D4AF37), width: 1, ), ), child: Column( children: [ _buildSettingItem( icon: Icons.verified_user, title: '谷歌验证器', onTap: _goToGoogleAuth, ), _buildSettingItem( icon: Icons.lock, title: '修改登录密码', onTap: _goToChangePassword, ), _buildSettingItem( icon: Icons.email, title: '绑定邮箱', onTap: _goToBindEmail, ), ], ), ); } /// 构建设置项 Widget _buildSettingItem({ required IconData icon, required String title, required VoidCallback onTap, }) { return GestureDetector( onTap: onTap, child: Container( height: 48, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), child: Row( children: [ Icon( icon, size: 24, color: const Color(0xFF8B5A2B), ), const SizedBox(width: 16), Expanded( child: Text( title, style: const TextStyle( fontSize: 16, fontFamily: 'Inter', fontWeight: FontWeight.w500, height: 1.5, color: Color(0xFF5D4037), ), ), ), const Icon( Icons.chevron_right, size: 24, color: Color(0xFF8B5A2B), ), ], ), ), ); } /// 构建应用版本信息 Widget _buildAppVersionInfo() { return Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: const Color(0xFFFFF5E6), borderRadius: BorderRadius.circular(8), border: Border.all( color: const Color(0x33D4AF37), width: 1, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 标题 const Row( children: [ Icon( Icons.info_outline, size: 20, color: Color(0xFF8B5A2B), ), SizedBox(width: 8), Text( '应用信息', style: TextStyle( fontSize: 16, fontFamily: 'Inter', fontWeight: FontWeight.w600, height: 1.5, color: Color(0xFF5D4037), ), ), ], ), const SizedBox(height: 12), // 版本信息列表 _buildVersionInfoRow('应用版本', 'v$_appVersion'), const SizedBox(height: 8), _buildVersionInfoRow('构建号', _buildNumber), const SizedBox(height: 8), _buildVersionInfoRow('构建模式', kReleaseMode ? 'Release' : (kDebugMode ? 'Debug' : 'Profile')), const SizedBox(height: 8), _buildVersionInfoRow('包名', _packageName), const Divider( height: 24, color: Color(0x33D4AF37), ), // 设备信息 _buildVersionInfoRow('设备型号', _deviceModel), const SizedBox(height: 8), _buildVersionInfoRow('系统版本', _osVersion), const SizedBox(height: 8), _buildVersionInfoRow('平台', _platform), const SizedBox(height: 12), // 版权信息 Center( child: Text( '© ${DateTime.now().year} RWADurian. All rights reserved.', style: const TextStyle( fontSize: 11, fontFamily: 'Inter', height: 1.5, color: Color(0x995D4037), ), ), ), ], ), ); } /// 构建版本信息行 Widget _buildVersionInfoRow(String label, String value) { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( label, style: const TextStyle( fontSize: 14, fontFamily: 'Inter', height: 1.5, color: Color(0xCC5D4037), ), ), Flexible( child: Text( value, style: const TextStyle( fontSize: 14, fontFamily: 'Inter', fontWeight: FontWeight.w500, height: 1.5, color: Color(0xFF5D4037), ), textAlign: TextAlign.right, overflow: TextOverflow.ellipsis, ), ), ], ); } /// 构建账号操作按钮(切换账号、退出登录) Widget _buildAccountActionButtons() { return Column( children: [ // 切换账号按钮 GestureDetector( onTap: _onSwitchAccount, child: Container( width: double.infinity, height: 48, decoration: BoxDecoration( color: const Color(0xFFFFF5E6), borderRadius: BorderRadius.circular(8), border: Border.all( color: const Color(0x33D4AF37), width: 1, ), ), child: const Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.swap_horiz, size: 20, color: Color(0xFF8B5A2B), ), SizedBox(width: 8), Text( '切换账号', style: TextStyle( fontSize: 15, fontFamily: 'Inter', fontWeight: FontWeight.w500, height: 1.5, color: Color(0xFF5D4037), ), ), ], ), ), ), const SizedBox(height: 12), // 退出登录按钮 GestureDetector( onTap: _onLogout, child: Container( width: double.infinity, height: 48, decoration: BoxDecoration( color: const Color(0xFFFFF5E6), borderRadius: BorderRadius.circular(8), border: Border.all( color: const Color(0x33E57373), width: 1, ), ), child: const Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.logout, size: 20, color: Color(0xFFE57373), ), SizedBox(width: 8), Text( '退出登录', style: TextStyle( fontSize: 15, fontFamily: 'Inter', fontWeight: FontWeight.w500, height: 1.5, color: Color(0xFFE57373), ), ), ], ), ), ), const SizedBox(height: 32), // 底部留白 ], ); } /// 切换账号 - 跳转到账号切换页面 void _onSwitchAccount() { context.push(RoutePaths.accountSwitch); } /// 退出登录 void _onLogout() { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('退出登录'), content: const Text('确定要退出当前账号吗?\n\n退出后账号数据会保留在本地,可在账号切换页面重新登录。'), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('取消'), ), TextButton( onPressed: () { Navigator.of(context).pop(); _performLogout(); }, style: TextButton.styleFrom( foregroundColor: const Color(0xFFE57373), ), child: const Text('退出'), ), ], ), ); } /// 执行退出登录 Future _performLogout() async { try { final multiAccountService = ref.read(multiAccountServiceProvider); // 退出当前账号(保留账号数据) await multiAccountService.logoutCurrentAccount(); // 导航到向导页 if (mounted) { context.go(RoutePaths.guide); } } catch (e) { debugPrint('[ProfilePage] 退出登录失败: $e'); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('退出登录失败: $e')), ); } } } }