2663 lines
89 KiB
Dart
2663 lines
89 KiB
Dart
import 'dart:io';
|
||
import 'dart:async';
|
||
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 '../../../../core/di/injection_container.dart';
|
||
import '../../../../core/services/referral_service.dart';
|
||
import '../../../../core/services/reward_service.dart';
|
||
import '../../../../routes/route_paths.dart';
|
||
import '../../../../routes/app_router.dart';
|
||
import '../widgets/team_tree_widget.dart';
|
||
|
||
/// 个人中心页面 - 显示用户信息、社区数据、收益和设置
|
||
/// 包含用户资料、推荐信息、社区考核、收益领取等功能
|
||
class ProfilePage extends ConsumerStatefulWidget {
|
||
const ProfilePage({super.key});
|
||
|
||
@override
|
||
ConsumerState<ProfilePage> createState() => _ProfilePageState();
|
||
}
|
||
|
||
class _ProfilePageState extends ConsumerState<ProfilePage> {
|
||
// 用户数据(从存储/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<Map<String, dynamic>> _referrals = [];
|
||
|
||
// 社区考核数据(从 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 = true;
|
||
String? _walletError;
|
||
|
||
// 待领取奖励列表(每一笔单独显示)
|
||
List<PendingRewardItem> _pendingRewards = [];
|
||
|
||
// 倒计时
|
||
Timer? _timer;
|
||
int _remainingSeconds = 0;
|
||
|
||
// 应用版本信息
|
||
String _appVersion = '--';
|
||
String _buildNumber = '--';
|
||
String _packageName = '--';
|
||
String _deviceModel = '--';
|
||
String _osVersion = '--';
|
||
String _platform = '--';
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
// 先同步检查本地头像,再异步加载其他数据
|
||
_checkLocalAvatarSync();
|
||
_loadUserData();
|
||
_loadAppInfo();
|
||
// 加载推荐和授权数据
|
||
_loadReferralData();
|
||
_loadAuthorizationData();
|
||
// 加载钱包和收益数据
|
||
_loadWalletData();
|
||
}
|
||
|
||
/// 加载应用信息
|
||
Future<void> _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<void> _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<void> _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)
|
||
Future<void> _loadReferralData() async {
|
||
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(() {
|
||
_directReferralCount = referralInfo.directReferralCount;
|
||
_totalTeamCount = referralInfo.totalTeamCount;
|
||
_personalPlantingCount = referralInfo.personalPlantingCount;
|
||
_teamPlantingCount = referralInfo.teamPlantingCount;
|
||
_leaderboardScore = referralInfo.leaderboardScore;
|
||
_leaderboardRank = referralInfo.leaderboardRank;
|
||
|
||
// 转换直推列表格式
|
||
_referrals = directReferrals.referrals.map((r) => <String, dynamic>{
|
||
'serial': r.accountSequence, // 使用8位账户序列号显示
|
||
'personal': r.personalPlantingCount, // 个人认种量
|
||
'team': r.teamPlantingCount, // 团队认种量
|
||
}).toList();
|
||
});
|
||
}
|
||
} catch (e, stackTrace) {
|
||
debugPrint('[ProfilePage] 加载推荐数据失败: $e');
|
||
debugPrint('[ProfilePage] 堆栈: $stackTrace');
|
||
// 失败时保持默认数据
|
||
}
|
||
}
|
||
|
||
/// 加载授权数据 (from authorization-service)
|
||
Future<void> _loadAuthorizationData() async {
|
||
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(() {
|
||
_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');
|
||
// 失败时保持默认数据
|
||
}
|
||
}
|
||
|
||
/// 加载收益数据 (直接从 reward-service 获取)
|
||
Future<void> _loadWalletData() async {
|
||
try {
|
||
debugPrint('[ProfilePage] ========== 加载收益数据 ==========');
|
||
debugPrint('[ProfilePage] mounted: $mounted');
|
||
setState(() {
|
||
_isLoadingWallet = true;
|
||
_walletError = null;
|
||
});
|
||
|
||
debugPrint('[ProfilePage] 获取 rewardServiceProvider...');
|
||
final rewardService = ref.read(rewardServiceProvider);
|
||
|
||
// 并行加载汇总数据和待领取列表
|
||
debugPrint('[ProfilePage] 调用 getMyRewardSummary() 和 getPendingRewards()...');
|
||
final results = await Future.wait([
|
||
rewardService.getMyRewardSummary(),
|
||
rewardService.getPendingRewards(),
|
||
]);
|
||
|
||
final summary = results[0] as RewardSummary;
|
||
final pendingRewards = results[1] as List<PendingRewardItem>;
|
||
|
||
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}');
|
||
|
||
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;
|
||
_isLoadingWallet = false;
|
||
});
|
||
|
||
debugPrint('[ProfilePage] UI 状态更新完成');
|
||
debugPrint('[ProfilePage] _pendingUsdt: $_pendingUsdt');
|
||
debugPrint('[ProfilePage] _remainingSeconds: $_remainingSeconds');
|
||
debugPrint('[ProfilePage] _pendingRewards: ${_pendingRewards.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 = '加载失败,点击重试';
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 后台下载并缓存头像
|
||
Future<void> _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();
|
||
super.dispose();
|
||
}
|
||
|
||
/// 开始倒计时
|
||
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(
|
||
'确定领取全部收益吗?\nUSDT: ${_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<void> _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<void> _goToEditProfile() async {
|
||
final result = await context.push<bool>(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]},',
|
||
);
|
||
}
|
||
|
||
@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: SingleChildScrollView(
|
||
child: Padding(
|
||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
const SizedBox(height: 16),
|
||
// 用户头像和基本信息
|
||
_buildUserHeader(),
|
||
const SizedBox(height: 16),
|
||
// 推荐人信息卡片
|
||
_buildReferralInfoCard(),
|
||
const SizedBox(height: 16),
|
||
// 社区/省份标签
|
||
_buildCommunityLabel(),
|
||
const SizedBox(height: 8),
|
||
// 认种按钮
|
||
_buildPlantingButton(),
|
||
const SizedBox(height: 16),
|
||
// 主要内容卡片
|
||
_buildMainContentCard(),
|
||
const SizedBox(height: 24),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 构建用户头像和基本信息
|
||
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>(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>(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('<svg') || _avatarSvg!.startsWith('<?xml')) {
|
||
debugPrint('[ProfilePage] _buildSvgOrDefaultAvatar() - 检测到是SVG字符串,使用SvgPicture');
|
||
return SvgPicture.string(
|
||
_avatarSvg!,
|
||
width: 80,
|
||
height: 80,
|
||
fit: BoxFit.cover,
|
||
);
|
||
}
|
||
|
||
debugPrint('[ProfilePage] _buildSvgOrDefaultAvatar() - 未知格式,使用默认图标');
|
||
}
|
||
debugPrint('[ProfilePage] _buildSvgOrDefaultAvatar() - 无头像数据,使用默认图标');
|
||
return const Icon(
|
||
Icons.person,
|
||
size: 40,
|
||
color: Color(0xFF8B5A2B),
|
||
);
|
||
}
|
||
|
||
/// 构建推荐人信息卡片
|
||
Widget _buildReferralInfoCard() {
|
||
return Container(
|
||
width: double.infinity,
|
||
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(
|
||
children: [
|
||
Expanded(
|
||
child: _buildInfoItem('推荐人的序列号', _referrerSerial),
|
||
),
|
||
Expanded(
|
||
child: _buildInfoItem('下级社区', _childCommunity),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 12),
|
||
// 第二行:所属社区 | 市团队
|
||
Row(
|
||
children: [
|
||
Expanded(
|
||
child: _buildInfoItem('所属社区', _community),
|
||
),
|
||
Expanded(
|
||
child: _buildInfoItem('市团队', _authCityCompany),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 12),
|
||
// 第三行:上级社区 | 市区域
|
||
Row(
|
||
children: [
|
||
Expanded(
|
||
child: _buildInfoItem('上级社区', _parentCommunity),
|
||
),
|
||
Expanded(
|
||
child: _buildInfoItem('市区域', _cityCompany),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 12),
|
||
// 第四行:省团队 | 省区域
|
||
Row(
|
||
children: [
|
||
Expanded(
|
||
child: _buildInfoItem('省团队', _authProvinceCompany),
|
||
),
|
||
Expanded(
|
||
child: _buildInfoItem('省区域', _provinceCompany),
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 构建信息项(标签 + 值)
|
||
Widget _buildInfoItem(String label, String value) {
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
'$label:',
|
||
style: const TextStyle(
|
||
fontSize: 12,
|
||
fontFamily: 'Inter',
|
||
height: 1.5,
|
||
color: Color(0x995D4037),
|
||
),
|
||
),
|
||
const SizedBox(height: 2),
|
||
Text(
|
||
value,
|
||
style: const TextStyle(
|
||
fontSize: 14,
|
||
fontFamily: 'Inter',
|
||
fontWeight: FontWeight.w600,
|
||
height: 1.5,
|
||
color: Color(0xFF5D4037),
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
/// 构建社区/省份标签
|
||
Widget _buildCommunityLabel() {
|
||
return const Text(
|
||
'社区 / 省份',
|
||
style: TextStyle(
|
||
fontSize: 16,
|
||
fontFamily: 'Inter',
|
||
height: 1.5,
|
||
color: Color(0xFF5D4037),
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 构建认种按钮
|
||
Widget _buildPlantingButton() {
|
||
return GestureDetector(
|
||
onTap: _goToPlanting,
|
||
child: Container(
|
||
width: double.infinity,
|
||
height: 48,
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFFD4AF37),
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
child: const Center(
|
||
child: Text(
|
||
'认种 / Planting',
|
||
style: TextStyle(
|
||
fontSize: 16,
|
||
fontFamily: 'Inter',
|
||
fontWeight: FontWeight.w700,
|
||
height: 1.5,
|
||
letterSpacing: 0.24,
|
||
color: Colors.white,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 构建主要内容卡片
|
||
Widget _buildMainContentCard() {
|
||
return Container(
|
||
width: double.infinity,
|
||
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(
|
||
children: [
|
||
// 收益区域
|
||
_buildEarningsSection(),
|
||
const SizedBox(height: 16),
|
||
// 结算区域
|
||
_buildSettlementSection(),
|
||
const SizedBox(height: 16),
|
||
// 已过期区域
|
||
_buildExpiredSection(),
|
||
const SizedBox(height: 16),
|
||
// 操作按钮
|
||
_buildActionButtons(),
|
||
const SizedBox(height: 16),
|
||
// 团队统计
|
||
_buildTeamStats(),
|
||
const SizedBox(height: 16),
|
||
// 直推列表
|
||
_buildReferralList(),
|
||
const SizedBox(height: 16),
|
||
// 我的伞下
|
||
_buildMyTeamTree(),
|
||
const SizedBox(height: 16),
|
||
// 分享邀请按钮
|
||
_buildShareButton(),
|
||
const SizedBox(height: 16),
|
||
// 社区权益考核
|
||
_buildCommunityAssessment(),
|
||
// 市团队权益考核
|
||
if (_hasAuthCityCompanyAuth) ...[
|
||
const SizedBox(height: 16),
|
||
_buildRoleAssessment(
|
||
title: '市团队权益考核',
|
||
hasAuth: _hasAuthCityCompanyAuth,
|
||
benefitActive: _authCityCompanyBenefitActive,
|
||
currentTreeCount: _authCityCompanyCurrentTreeCount,
|
||
initialTarget: _authCityCompanyInitialTarget,
|
||
monthlyTarget: _authCityCompanyMonthlyTarget,
|
||
monthIndex: _authCityCompanyMonthIndex,
|
||
rewardDescription: '每新增认种 1 棵可获得 40 USDT',
|
||
),
|
||
],
|
||
// 省团队权益考核
|
||
if (_hasAuthProvinceCompanyAuth) ...[
|
||
const SizedBox(height: 16),
|
||
_buildRoleAssessment(
|
||
title: '省团队权益考核',
|
||
hasAuth: _hasAuthProvinceCompanyAuth,
|
||
benefitActive: _authProvinceCompanyBenefitActive,
|
||
currentTreeCount: _authProvinceCompanyCurrentTreeCount,
|
||
initialTarget: _authProvinceCompanyInitialTarget,
|
||
monthlyTarget: _authProvinceCompanyMonthlyTarget,
|
||
monthIndex: _authProvinceCompanyMonthIndex,
|
||
rewardDescription: '每新增认种 1 棵可获得 20 USDT',
|
||
),
|
||
],
|
||
// 市区域权益考核
|
||
if (_hasCityCompanyAuth) ...[
|
||
const SizedBox(height: 16),
|
||
_buildRoleAssessment(
|
||
title: '市区域权益考核',
|
||
hasAuth: _hasCityCompanyAuth,
|
||
benefitActive: _cityCompanyBenefitActive,
|
||
currentTreeCount: _cityCompanyCurrentTreeCount,
|
||
initialTarget: _cityCompanyInitialTarget,
|
||
monthlyTarget: _cityCompanyMonthlyTarget,
|
||
monthIndex: _cityCompanyMonthIndex,
|
||
rewardDescription: '每新增认种 1 棵可获得 35 USDT',
|
||
),
|
||
],
|
||
// 省区域权益考核
|
||
if (_hasProvinceCompanyAuth) ...[
|
||
const SizedBox(height: 16),
|
||
_buildRoleAssessment(
|
||
title: '省区域权益考核',
|
||
hasAuth: _hasProvinceCompanyAuth,
|
||
benefitActive: _provinceCompanyBenefitActive,
|
||
currentTreeCount: _provinceCompanyCurrentTreeCount,
|
||
initialTarget: _provinceCompanyInitialTarget,
|
||
monthlyTarget: _provinceCompanyMonthlyTarget,
|
||
monthIndex: _provinceCompanyMonthIndex,
|
||
rewardDescription: '每新增认种 1 棵可获得 15 USDT',
|
||
),
|
||
],
|
||
const SizedBox(height: 16),
|
||
// 设置菜单
|
||
_buildSettingsMenu(),
|
||
const SizedBox(height: 16),
|
||
// 应用版本信息
|
||
_buildAppVersionInfo(),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 构建收益区域
|
||
Widget _buildEarningsSection() {
|
||
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: _isLoadingWallet
|
||
? _buildLoadingState()
|
||
: _walletError != null
|
||
? _buildErrorState()
|
||
: _buildEarningsContent(),
|
||
);
|
||
}
|
||
|
||
/// 构建加载状态
|
||
Widget _buildLoadingState() {
|
||
return const SizedBox(
|
||
height: 200,
|
||
child: Center(
|
||
child: CircularProgressIndicator(
|
||
color: Color(0xFFD4AF37),
|
||
strokeWidth: 2,
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 构建错误状态
|
||
Widget _buildErrorState() {
|
||
return GestureDetector(
|
||
onTap: _loadWalletData,
|
||
child: SizedBox(
|
||
height: 200,
|
||
child: Center(
|
||
child: Column(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
const Icon(
|
||
Icons.error_outline,
|
||
color: Color(0xFFE65100),
|
||
size: 40,
|
||
),
|
||
const SizedBox(height: 8),
|
||
Text(
|
||
_walletError!,
|
||
style: const TextStyle(
|
||
fontSize: 14,
|
||
color: Color(0xFFE65100),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 构建收益内容
|
||
Widget _buildEarningsContent() {
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
// 汇总信息区域
|
||
Row(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
// 倒计时
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
const Text(
|
||
'24小时倒计时',
|
||
style: TextStyle(
|
||
fontSize: 12,
|
||
fontFamily: 'Inter',
|
||
fontWeight: FontWeight.w500,
|
||
height: 1.5,
|
||
color: Color(0xCC5D4037),
|
||
),
|
||
),
|
||
Text(
|
||
_formatCountdown(),
|
||
style: const TextStyle(
|
||
fontSize: 14,
|
||
fontFamily: 'Consolas',
|
||
fontWeight: FontWeight.w700,
|
||
height: 1.25,
|
||
letterSpacing: 0.7,
|
||
color: Color(0xFFD4AF37),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
// 汇总待领取 USDT
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
const Text(
|
||
'待领取 (USDT)',
|
||
style: TextStyle(
|
||
fontSize: 12,
|
||
fontFamily: 'Inter',
|
||
fontWeight: FontWeight.w500,
|
||
height: 1.5,
|
||
color: Color(0xCC5D4037),
|
||
),
|
||
),
|
||
Text(
|
||
_formatNumber(_pendingUsdt),
|
||
style: const TextStyle(
|
||
fontSize: 16,
|
||
fontFamily: 'Inter',
|
||
fontWeight: FontWeight.w700,
|
||
height: 1.25,
|
||
color: Color(0xFF5D4037),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
// 汇总待领取算力
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
const Text(
|
||
'待领取 (算力)',
|
||
style: TextStyle(
|
||
fontSize: 12,
|
||
fontFamily: 'Inter',
|
||
fontWeight: FontWeight.w500,
|
||
height: 1.5,
|
||
color: Color(0xCC5D4037),
|
||
),
|
||
),
|
||
Text(
|
||
_formatNumber(_pendingPower),
|
||
style: const TextStyle(
|
||
fontSize: 16,
|
||
fontFamily: 'Inter',
|
||
fontWeight: FontWeight.w700,
|
||
height: 1.25,
|
||
color: Color(0xFF5D4037),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
// 待领取奖励列表
|
||
if (_pendingRewards.isNotEmpty) ...[
|
||
const SizedBox(height: 16),
|
||
const Divider(color: Color(0x33D4AF37), height: 1),
|
||
const SizedBox(height: 12),
|
||
const Text(
|
||
'待领取明细',
|
||
style: TextStyle(
|
||
fontSize: 14,
|
||
fontFamily: 'Inter',
|
||
fontWeight: FontWeight.w600,
|
||
color: Color(0xFF5D4037),
|
||
),
|
||
),
|
||
const SizedBox(height: 8),
|
||
// 奖励条目列表
|
||
...(_pendingRewards.map((item) => _buildPendingRewardItem(item))),
|
||
],
|
||
const SizedBox(height: 15),
|
||
// 领取全部按钮
|
||
GestureDetector(
|
||
onTap: _claimAllEarnings,
|
||
child: Container(
|
||
width: double.infinity,
|
||
height: 40,
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFFD4AF37),
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
child: const Center(
|
||
child: Text(
|
||
'领取全部',
|
||
style: TextStyle(
|
||
fontSize: 14,
|
||
fontFamily: 'Inter',
|
||
fontWeight: FontWeight.w700,
|
||
height: 1.5,
|
||
letterSpacing: 0.21,
|
||
color: Colors.white,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
/// 构建单条待领取奖励项
|
||
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<String> amountParts = [];
|
||
if (item.usdtAmount > 0) {
|
||
amountParts.add('${_formatNumber(item.usdtAmount)} USDT');
|
||
}
|
||
if (item.hashpowerAmount > 0) {
|
||
amountParts.add('${_formatNumber(item.hashpowerAmount)} 算力');
|
||
}
|
||
// 如果都为0,显示默认文本
|
||
final amountText = amountParts.isNotEmpty ? amountParts.join(' ') : '0 USDT';
|
||
|
||
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
|
||
Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
const Text(
|
||
'可结算 (USDT)',
|
||
style: TextStyle(
|
||
fontSize: 14,
|
||
fontFamily: 'Inter',
|
||
fontWeight: FontWeight.w500,
|
||
height: 1.5,
|
||
color: Color(0xCC5D4037),
|
||
),
|
||
),
|
||
Text(
|
||
_formatNumber(_settleableUsdt),
|
||
style: const TextStyle(
|
||
fontSize: 20,
|
||
fontFamily: 'Inter',
|
||
fontWeight: FontWeight.w700,
|
||
height: 1.25,
|
||
color: Color(0xFF5D4037),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 11),
|
||
// 已结算 USDT
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
const Text(
|
||
'已结算 (USDT)',
|
||
style: TextStyle(
|
||
fontSize: 14,
|
||
fontFamily: 'Inter',
|
||
fontWeight: FontWeight.w500,
|
||
height: 1.5,
|
||
color: Color(0xCC5D4037),
|
||
),
|
||
),
|
||
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,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 4),
|
||
Text(
|
||
_formatNumber(_settledUsdt),
|
||
style: const TextStyle(
|
||
fontSize: 20,
|
||
fontFamily: 'Inter',
|
||
fontWeight: FontWeight.w700,
|
||
height: 1.25,
|
||
color: Color(0xFF5D4037),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 构建已过期区域
|
||
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
|
||
Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
const Text(
|
||
'已过期 (USDT)',
|
||
style: TextStyle(
|
||
fontSize: 14,
|
||
fontFamily: 'Inter',
|
||
fontWeight: FontWeight.w500,
|
||
height: 1.5,
|
||
color: Color(0xCC5D4037),
|
||
),
|
||
),
|
||
Text(
|
||
_formatNumber(_expiredUsdt),
|
||
style: const TextStyle(
|
||
fontSize: 20,
|
||
fontFamily: 'Inter',
|
||
fontWeight: FontWeight.w700,
|
||
height: 1.25,
|
||
color: Color(0xFF5D4037),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 11),
|
||
// 已过期 算力
|
||
Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
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: 20,
|
||
fontFamily: 'Inter',
|
||
fontWeight: FontWeight.w700,
|
||
height: 1.25,
|
||
color: Color(0xFF5D4037),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 构建操作按钮
|
||
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(
|
||
'充值 USDT (KAVA / BSC)',
|
||
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 → USDT)',
|
||
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() {
|
||
// 创建根节点(自己)
|
||
final rootNode = 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: rootNode,
|
||
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 USDT'
|
||
: '需团队认种达到 $_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('包名', _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,
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
}
|