4144 lines
143 KiB
Dart
4144 lines
143 KiB
Dart
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/storage/storage_keys.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 '../../../auth/presentation/providers/wallet_status_provider.dart';
|
||
import '../widgets/team_tree_widget.dart';
|
||
import '../widgets/stacked_cards_widget.dart';
|
||
import '../../../authorization/presentation/widgets/stickman_race_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 = [];
|
||
|
||
// 团队树根节点(缓存以保持展开状态)
|
||
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;
|
||
|
||
// 火柴人排名数据(省公司/市公司排名赛跑)
|
||
List<StickmanRankingData> _provinceRankings = [];
|
||
List<StickmanRankingData> _cityRankings = [];
|
||
String _provinceRegionName = ''; // 省名称
|
||
String _cityRegionName = ''; // 市名称
|
||
bool _isLoadingStickmanRanking = false;
|
||
bool _hasLoadedStickmanRanking = false;
|
||
|
||
// 收益数据(从 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<PendingRewardItem> _pendingRewards = [];
|
||
|
||
// 可结算奖励列表(逐笔显示)
|
||
List<SettleableRewardItem> _settleableRewards = [];
|
||
|
||
// 已过期奖励列表(逐笔显示)
|
||
List<ExpiredRewardItem> _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().then((_) {
|
||
// 用户数据加载完成后,检查钱包状态
|
||
_checkAndStartWalletPolling();
|
||
});
|
||
_loadAppInfo();
|
||
// 通知数量(轻量级请求)
|
||
_loadUnreadNotificationCount();
|
||
// 启动定时刷新(可见区域的数据)
|
||
_startAutoRefreshTimer();
|
||
}
|
||
|
||
/// 加载应用信息
|
||
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)
|
||
/// [isRefresh] 是否为刷新操作(刷新时不显示加载状态)
|
||
Future<void> _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) => <String, dynamic>{
|
||
'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<void> _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;
|
||
}
|
||
});
|
||
|
||
// 授权数据加载完成后,加载火柴人排名数据
|
||
_loadStickmanRankingData();
|
||
}
|
||
} 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<void> _loadStickmanRankingData() async {
|
||
// 只有当用户有省公司或市公司授权时才加载
|
||
if (!_hasAuthProvinceCompanyAuth && !_hasAuthCityCompanyAuth) {
|
||
return;
|
||
}
|
||
|
||
if (_isLoadingStickmanRanking) return;
|
||
|
||
setState(() {
|
||
_isLoadingStickmanRanking = true;
|
||
});
|
||
|
||
try {
|
||
debugPrint('[ProfilePage] 开始加载火柴人排名数据...');
|
||
final authorizationService = ref.read(authorizationServiceProvider);
|
||
|
||
// 获取当前月份
|
||
final now = DateTime.now();
|
||
final month = '${now.year}-${now.month.toString().padLeft(2, '0')}';
|
||
|
||
// 如果有省公司授权,加载省排名
|
||
if (_hasAuthProvinceCompanyAuth && _authProvinceCompany != '--') {
|
||
try {
|
||
// 从省公司名称中提取省代码(假设格式为 "XX省" 或直接省名)
|
||
final provinceName = _authProvinceCompany;
|
||
_provinceRegionName = provinceName;
|
||
|
||
final rankings = await authorizationService.getStickmanRanking(
|
||
month: month,
|
||
roleType: 'AUTH_PROVINCE_COMPANY',
|
||
regionCode: provinceName,
|
||
);
|
||
|
||
if (mounted) {
|
||
setState(() {
|
||
_provinceRankings = rankings.map((r) => StickmanRankingData(
|
||
id: r.id,
|
||
nickname: r.nickname,
|
||
avatarUrl: r.avatarUrl,
|
||
completedCount: r.completedCount,
|
||
targetCount: 50000, // 省公司目标5万
|
||
monthlyEarnings: r.monthlyEarnings,
|
||
isCurrentUser: r.isCurrentUser,
|
||
)).toList();
|
||
});
|
||
}
|
||
debugPrint('[ProfilePage] 省公司排名加载成功: ${_provinceRankings.length}条');
|
||
} catch (e) {
|
||
debugPrint('[ProfilePage] 加载省公司排名失败: $e');
|
||
}
|
||
}
|
||
|
||
// 如果有市公司授权,加载市排名
|
||
if (_hasAuthCityCompanyAuth && _authCityCompany != '--') {
|
||
try {
|
||
final cityName = _authCityCompany;
|
||
_cityRegionName = cityName;
|
||
|
||
final rankings = await authorizationService.getStickmanRanking(
|
||
month: month,
|
||
roleType: 'AUTH_CITY_COMPANY',
|
||
regionCode: cityName,
|
||
);
|
||
|
||
if (mounted) {
|
||
setState(() {
|
||
_cityRankings = rankings.map((r) => StickmanRankingData(
|
||
id: r.id,
|
||
nickname: r.nickname,
|
||
avatarUrl: r.avatarUrl,
|
||
completedCount: r.completedCount,
|
||
targetCount: 10000, // 市公司目标1万
|
||
monthlyEarnings: r.monthlyEarnings,
|
||
isCurrentUser: r.isCurrentUser,
|
||
)).toList();
|
||
});
|
||
}
|
||
debugPrint('[ProfilePage] 市公司排名加载成功: ${_cityRankings.length}条');
|
||
} catch (e) {
|
||
debugPrint('[ProfilePage] 加载市公司排名失败: $e');
|
||
}
|
||
}
|
||
|
||
if (mounted) {
|
||
setState(() {
|
||
_isLoadingStickmanRanking = false;
|
||
_hasLoadedStickmanRanking = true;
|
||
});
|
||
}
|
||
} catch (e) {
|
||
debugPrint('[ProfilePage] 加载火柴人排名失败: $e');
|
||
if (mounted) {
|
||
setState(() {
|
||
_isLoadingStickmanRanking = false;
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 加载通知未读数量
|
||
Future<void> _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<void> _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<PendingRewardItem>;
|
||
final settleableRewards = results[2] as List<SettleableRewardItem>;
|
||
final expiredRewards = results[3] as List<ExpiredRewardItem>;
|
||
|
||
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<void> _downloadAndCacheAvatar(String url) async {
|
||
final accountService = ref.read(accountServiceProvider);
|
||
final localPath = await accountService.downloadAndCacheAvatar(url);
|
||
if (mounted && localPath != null) {
|
||
setState(() {
|
||
_localAvatarPath = localPath;
|
||
});
|
||
}
|
||
}
|
||
|
||
/// 检查并启动钱包轮询
|
||
/// 逻辑:
|
||
/// 1. 先检查本地标志 isWalletReady,如果已 ready 则跳过
|
||
/// 2. 如果不 ready,调用 API 检查钱包状态
|
||
/// 3. API 返回 ready 后,设置本地标志并刷新 UI
|
||
/// 4. API 返回未 ready,启动轮询直到成功
|
||
Future<void> _checkAndStartWalletPolling() async {
|
||
final authState = ref.read(authProvider);
|
||
|
||
debugPrint('[ProfilePage] _checkAndStartWalletPolling() called');
|
||
debugPrint('[ProfilePage] authState.isWalletReady: ${authState.isWalletReady}');
|
||
debugPrint('[ProfilePage] _serialNumber: $_serialNumber');
|
||
|
||
// 1. 先检查本地标志,如果已 ready 则不需要检查 API
|
||
if (authState.isWalletReady) {
|
||
debugPrint('[ProfilePage] Wallet already ready (local flag), skip API check');
|
||
return;
|
||
}
|
||
|
||
// 2. 没有序列号则跳过
|
||
if (_serialNumber.isEmpty || _serialNumber == '--') {
|
||
debugPrint('[ProfilePage] Skip wallet check - no valid serialNumber');
|
||
return;
|
||
}
|
||
|
||
// 3. 调用 API 检查钱包状态
|
||
debugPrint('[ProfilePage] isWalletReady=false, checking wallet status from API...');
|
||
try {
|
||
final accountService = ref.read(accountServiceProvider);
|
||
final walletInfo = await accountService.getWalletInfo(_serialNumber);
|
||
|
||
debugPrint('[ProfilePage] Wallet status from API: isReady=${walletInfo.isReady}, isGenerating=${walletInfo.isGenerating}');
|
||
|
||
if (walletInfo.isReady) {
|
||
// 4. 钱包已就绪,更新本地存储
|
||
debugPrint('[ProfilePage] Wallet is ready, updating local storage');
|
||
final secureStorage = ref.read(secureStorageProvider);
|
||
await secureStorage.write(key: StorageKeys.isWalletReady, value: 'true');
|
||
await secureStorage.write(key: StorageKeys.isAccountCreated, value: 'true');
|
||
|
||
// 刷新 authProvider 状态
|
||
await ref.read(authProvider.notifier).checkAuthStatus();
|
||
|
||
// 停止轮询
|
||
ref.read(walletStatusProvider.notifier).stopPolling();
|
||
|
||
// 强制刷新UI显示序列号
|
||
if (mounted) {
|
||
setState(() {});
|
||
}
|
||
return;
|
||
}
|
||
|
||
// 5. 钱包未就绪,启动轮询直到成功
|
||
debugPrint('[ProfilePage] Wallet not ready, starting polling');
|
||
await ref.read(walletStatusProvider.notifier).startPolling();
|
||
} catch (e) {
|
||
debugPrint('[ProfilePage] Failed to check wallet status: $e');
|
||
// API 调用失败时,也尝试启动轮询
|
||
await ref.read(walletStatusProvider.notifier).startPolling();
|
||
}
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_timer?.cancel();
|
||
_refreshTimer?.cancel();
|
||
// 取消防抖定时器
|
||
_referralDebounceTimer?.cancel();
|
||
_authorizationDebounceTimer?.cancel();
|
||
_walletDebounceTimer?.cancel();
|
||
// 停止钱包轮询(在 super.dispose() 之前安全调用)
|
||
try {
|
||
ref.read(walletStatusProvider.notifier).stopPolling();
|
||
} catch (e) {
|
||
// 忽略 dispose 后的 ref 访问错误
|
||
debugPrint('[ProfilePage] dispose() - 停止轮询时出错(可忽略): $e');
|
||
}
|
||
super.dispose();
|
||
}
|
||
|
||
/// 启动定时刷新(每60秒刷新一次可见区域的数据)
|
||
void _startAutoRefreshTimer() {
|
||
_refreshTimer = Timer.periodic(
|
||
const Duration(seconds: _autoRefreshIntervalSeconds),
|
||
(_) => _refreshVisibleSections(),
|
||
);
|
||
}
|
||
|
||
/// 刷新当前可见区域的数据
|
||
Future<void> _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<void> _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<void> 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<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);
|
||
}
|
||
|
||
/// 自助申请授权
|
||
void _goToAuthorizationApply() {
|
||
context.push(RoutePaths.authorizationApply);
|
||
}
|
||
|
||
/// 编辑资料
|
||
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]},',
|
||
);
|
||
}
|
||
|
||
/// 构建骨架屏
|
||
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),
|
||
// 火柴人排名赛跑(省公司/市公司)
|
||
_buildStickmanRaceSection(),
|
||
// 推荐人信息卡片(懒加载:推荐数据)
|
||
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),
|
||
_buildSerialNumberOrStatus(),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
/// 构建火柴人赛跑排名区域
|
||
Widget _buildStickmanRaceSection() {
|
||
// 如果没有省公司或市公司授权,不显示
|
||
if (!_hasAuthProvinceCompanyAuth && !_hasAuthCityCompanyAuth) {
|
||
return const SizedBox.shrink();
|
||
}
|
||
|
||
// 如果正在加载,显示加载提示
|
||
if (_isLoadingStickmanRanking && !_hasLoadedStickmanRanking) {
|
||
return Container(
|
||
margin: const EdgeInsets.only(bottom: 16),
|
||
padding: const EdgeInsets.all(16),
|
||
decoration: BoxDecoration(
|
||
color: const Color(0x80FFFFFF),
|
||
borderRadius: BorderRadius.circular(16),
|
||
border: Border.all(
|
||
color: const Color(0x33D4AF37),
|
||
width: 1,
|
||
),
|
||
),
|
||
child: const Center(
|
||
child: Column(
|
||
children: [
|
||
CircularProgressIndicator(
|
||
valueColor: AlwaysStoppedAnimation<Color>(Color(0xFFD4AF37)),
|
||
),
|
||
SizedBox(height: 8),
|
||
Text(
|
||
'加载排名数据...',
|
||
style: TextStyle(
|
||
fontSize: 14,
|
||
fontFamily: 'Inter',
|
||
color: Color(0xFF8B5A2B),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
return Column(
|
||
children: [
|
||
// 省公司排名赛跑
|
||
if (_hasAuthProvinceCompanyAuth && _provinceRankings.isNotEmpty) ...[
|
||
StickmanRaceWidget(
|
||
rankings: _provinceRankings,
|
||
roleType: AuthorizationRoleType.province,
|
||
regionName: _provinceRegionName,
|
||
),
|
||
const SizedBox(height: 16),
|
||
],
|
||
// 市公司排名赛跑
|
||
if (_hasAuthCityCompanyAuth && _cityRankings.isNotEmpty) ...[
|
||
StickmanRaceWidget(
|
||
rankings: _cityRankings,
|
||
roleType: AuthorizationRoleType.city,
|
||
regionName: _cityRegionName,
|
||
),
|
||
const SizedBox(height: 16),
|
||
],
|
||
],
|
||
);
|
||
}
|
||
|
||
/// 构建头像内容(优先本地文件,其次网络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() {
|
||
// Widget 结构始终保持,数据值根据状态显示 "--" 或实际值
|
||
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: Row(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
// 左列:推荐人序列号、所属社区、上级社区、下级社区
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
_buildInfoItem('推荐人的序列号', _referrerSerial),
|
||
const SizedBox(height: 12),
|
||
_buildInfoItem('所属社区', _community),
|
||
const SizedBox(height: 12),
|
||
_buildInfoItem('上级社区', _parentCommunity),
|
||
const SizedBox(height: 12),
|
||
_buildInfoItem('下级社区', _childCommunity),
|
||
],
|
||
),
|
||
),
|
||
// 右列:市团队、省团队、市区域、省区域
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
_buildInfoItem('市团队', _authCityCompany),
|
||
const SizedBox(height: 12),
|
||
_buildInfoItem('省团队', _authProvinceCompany),
|
||
const SizedBox(height: 12),
|
||
_buildInfoItem('市区域', _cityCompany),
|
||
const SizedBox(height: 12),
|
||
_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() {
|
||
// Widget 结构始终保持,数据值根据状态显示 "0" 或实际值
|
||
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(),
|
||
// 社区权益考核(仅在有社区授权时显示)
|
||
if (_hasCommunityAuth) ...[
|
||
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 棵可获得 288 绿积分',
|
||
),
|
||
],
|
||
// 省团队权益考核
|
||
if (_hasAuthProvinceCompanyAuth) ...[
|
||
const SizedBox(height: 16),
|
||
_buildRoleAssessment(
|
||
title: '省团队权益考核',
|
||
hasAuth: _hasAuthProvinceCompanyAuth,
|
||
benefitActive: _authProvinceCompanyBenefitActive,
|
||
currentTreeCount: _authProvinceCompanyCurrentTreeCount,
|
||
initialTarget: _authProvinceCompanyInitialTarget,
|
||
monthlyTarget: _authProvinceCompanyMonthlyTarget,
|
||
monthIndex: _authProvinceCompanyMonthIndex,
|
||
rewardDescription: '每新增认种 1 棵可获得 144 绿积分',
|
||
),
|
||
],
|
||
// 市区域权益考核
|
||
if (_hasCityCompanyAuth) ...[
|
||
const SizedBox(height: 16),
|
||
_buildRoleAssessment(
|
||
title: '市区域权益考核',
|
||
hasAuth: _hasCityCompanyAuth,
|
||
benefitActive: _cityCompanyBenefitActive,
|
||
currentTreeCount: _cityCompanyCurrentTreeCount,
|
||
initialTarget: _cityCompanyInitialTarget,
|
||
monthlyTarget: _cityCompanyMonthlyTarget,
|
||
monthIndex: _cityCompanyMonthIndex,
|
||
rewardDescription: '每新增认种 1 棵可获得 252 绿积分',
|
||
),
|
||
],
|
||
// 省区域权益考核
|
||
if (_hasProvinceCompanyAuth) ...[
|
||
const SizedBox(height: 16),
|
||
_buildRoleAssessment(
|
||
title: '省区域权益考核',
|
||
hasAuth: _hasProvinceCompanyAuth,
|
||
benefitActive: _provinceCompanyBenefitActive,
|
||
currentTreeCount: _provinceCompanyCurrentTreeCount,
|
||
initialTarget: _provinceCompanyInitialTarget,
|
||
monthlyTarget: _provinceCompanyMonthlyTarget,
|
||
monthIndex: _provinceCompanyMonthIndex,
|
||
rewardDescription: '每新增认种 1 棵可获得 108 绿积分',
|
||
),
|
||
],
|
||
const SizedBox(height: 16),
|
||
// 设置菜单
|
||
_buildSettingsMenu(),
|
||
const SizedBox(height: 16),
|
||
// 应用版本信息
|
||
_buildAppVersionInfo(),
|
||
const SizedBox(height: 16),
|
||
// 账号操作按钮
|
||
_buildAccountActionButtons(),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 构建收益区域
|
||
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: [
|
||
// 汇总信息区域 - 竖排布局
|
||
Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
// 待领取倒计时
|
||
Row(
|
||
children: [
|
||
const Text(
|
||
'待领取',
|
||
style: TextStyle(
|
||
fontSize: 14,
|
||
fontFamily: 'Inter',
|
||
fontWeight: FontWeight.w700,
|
||
height: 1.5,
|
||
color: Color(0xFF5D4037),
|
||
),
|
||
),
|
||
const SizedBox(width: 8),
|
||
Text(
|
||
_formatCountdown(),
|
||
style: const TextStyle(
|
||
fontSize: 14,
|
||
fontFamily: 'Consolas',
|
||
fontWeight: FontWeight.w700,
|
||
height: 1.5,
|
||
letterSpacing: 0.7,
|
||
color: Color(0xFFD4AF37),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
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(_pendingUsdt),
|
||
style: const TextStyle(
|
||
fontSize: 16,
|
||
fontFamily: 'Inter',
|
||
fontWeight: FontWeight.w500,
|
||
height: 1.25,
|
||
color: Color(0xFF5D4037),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
// TODO: 暂时隐藏贡献值显示
|
||
// const SizedBox(height: 4),
|
||
// // 贡献值
|
||
// Row(
|
||
// children: [
|
||
// const Text(
|
||
// '贡献值:',
|
||
// style: TextStyle(
|
||
// fontSize: 14,
|
||
// fontFamily: 'Inter',
|
||
// fontWeight: FontWeight.w500,
|
||
// height: 1.5,
|
||
// color: Color(0xCC5D4037),
|
||
// ),
|
||
// ),
|
||
// Text(
|
||
// _formatNumber(_pendingPower),
|
||
// style: const TextStyle(
|
||
// fontSize: 16,
|
||
// fontFamily: 'Inter',
|
||
// fontWeight: FontWeight.w500,
|
||
// height: 1.25,
|
||
// color: Color(0xFF5D4037),
|
||
// ),
|
||
// ),
|
||
// ],
|
||
// ),
|
||
],
|
||
),
|
||
// 待领取奖励列表 - 使用堆叠卡片展示
|
||
if (_pendingRewards.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(
|
||
'共 ${_pendingRewards.length} 笔',
|
||
style: const TextStyle(
|
||
fontSize: 12,
|
||
fontFamily: 'Inter',
|
||
color: Color(0xFF8B5A2B),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 8),
|
||
// 堆叠卡片展示
|
||
StackedCardsView<PendingRewardItem>(
|
||
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<String> 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<String> 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<SettleableRewardItem>(
|
||
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<String> 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<String> 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<ExpiredRewardItem>(
|
||
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<String> 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<String> 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 棵可获得 576 绿积分'
|
||
: '需团队认种达到 $_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,
|
||
),
|
||
_buildSettingItem(
|
||
icon: Icons.verified,
|
||
title: '自助申请授权',
|
||
onTap: _goToAuthorizationApply,
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 构建设置项
|
||
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<void> _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')),
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 构建序列号或钱包生成状态显示
|
||
/// 逻辑:先检查 isWalletReady 标志,如果 ready 就显示序列号
|
||
/// 如果不 ready,_checkAndStartWalletPolling 会去 API 检查并轮询
|
||
Widget _buildSerialNumberOrStatus() {
|
||
final walletStatus = ref.watch(walletStatusProvider);
|
||
final authState = ref.watch(authProvider);
|
||
|
||
// 1. 钱包已就绪(本地标志 或 API状态),显示序列号
|
||
if (authState.isWalletReady || walletStatus.isReady) {
|
||
if (_serialNumber.isNotEmpty && _serialNumber != '--') {
|
||
return 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),
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
}
|
||
|
||
// 2. 钱包未就绪,显示审核中状态
|
||
return Row(
|
||
children: [
|
||
const SizedBox(
|
||
width: 12,
|
||
height: 12,
|
||
child: CircularProgressIndicator(
|
||
strokeWidth: 2,
|
||
valueColor: AlwaysStoppedAnimation<Color>(Color(0xFFD4AF37)),
|
||
),
|
||
),
|
||
const SizedBox(width: 8),
|
||
Text(
|
||
'创建账号审核中...',
|
||
style: const TextStyle(
|
||
fontSize: 14,
|
||
fontFamily: 'Inter',
|
||
height: 1.5,
|
||
color: Color(0xFFD4AF37),
|
||
fontStyle: FontStyle.italic,
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
}
|