diff --git a/frontend/mobile-app/lib/core/di/injection_container.dart b/frontend/mobile-app/lib/core/di/injection_container.dart index 24363e12..65e98e90 100644 --- a/frontend/mobile-app/lib/core/di/injection_container.dart +++ b/frontend/mobile-app/lib/core/di/injection_container.dart @@ -6,6 +6,7 @@ import '../services/account_service.dart'; import '../services/referral_service.dart'; import '../services/authorization_service.dart'; import '../services/deposit_service.dart'; +import '../services/wallet_service.dart'; // Storage Providers final secureStorageProvider = Provider((ref) { @@ -51,6 +52,12 @@ final depositServiceProvider = Provider((ref) { return DepositService(apiClient: apiClient, secureStorage: secureStorage); }); +// Wallet Service Provider +final walletServiceProvider = Provider((ref) { + final apiClient = ref.watch(apiClientProvider); + return WalletService(apiClient: apiClient); +}); + // Override provider with initialized instance ProviderContainer createProviderContainer(LocalStorage localStorage) { return ProviderContainer( diff --git a/frontend/mobile-app/lib/core/services/wallet_service.dart b/frontend/mobile-app/lib/core/services/wallet_service.dart new file mode 100644 index 00000000..d965586c --- /dev/null +++ b/frontend/mobile-app/lib/core/services/wallet_service.dart @@ -0,0 +1,276 @@ +import 'package:flutter/foundation.dart'; +import '../network/api_client.dart'; + +/// 余额信息 +class BalanceInfo { + final double available; + final double frozen; + + BalanceInfo({ + required this.available, + required this.frozen, + }); + + factory BalanceInfo.fromJson(Map json) { + return BalanceInfo( + available: (json['available'] ?? 0).toDouble(), + frozen: (json['frozen'] ?? 0).toDouble(), + ); + } +} + +/// 各币种余额 +class BalancesInfo { + final BalanceInfo usdt; + final BalanceInfo dst; + final BalanceInfo bnb; + final BalanceInfo og; + final BalanceInfo rwad; + + BalancesInfo({ + required this.usdt, + required this.dst, + required this.bnb, + required this.og, + required this.rwad, + }); + + factory BalancesInfo.fromJson(Map json) { + return BalancesInfo( + usdt: BalanceInfo.fromJson(json['usdt'] ?? {}), + dst: BalanceInfo.fromJson(json['dst'] ?? {}), + bnb: BalanceInfo.fromJson(json['bnb'] ?? {}), + og: BalanceInfo.fromJson(json['og'] ?? {}), + rwad: BalanceInfo.fromJson(json['rwad'] ?? {}), + ); + } +} + +/// 奖励信息 +class RewardsInfo { + final double pendingUsdt; + final double pendingHashpower; + final DateTime? pendingExpireAt; + final double settleableUsdt; + final double settleableHashpower; + final double settledTotalUsdt; + final double settledTotalHashpower; + final double expiredTotalUsdt; + final double expiredTotalHashpower; + + RewardsInfo({ + required this.pendingUsdt, + required this.pendingHashpower, + this.pendingExpireAt, + required this.settleableUsdt, + required this.settleableHashpower, + required this.settledTotalUsdt, + required this.settledTotalHashpower, + required this.expiredTotalUsdt, + required this.expiredTotalHashpower, + }); + + factory RewardsInfo.fromJson(Map json) { + return RewardsInfo( + pendingUsdt: (json['pendingUsdt'] ?? 0).toDouble(), + pendingHashpower: (json['pendingHashpower'] ?? 0).toDouble(), + pendingExpireAt: json['pendingExpireAt'] != null + ? DateTime.tryParse(json['pendingExpireAt']) + : null, + settleableUsdt: (json['settleableUsdt'] ?? 0).toDouble(), + settleableHashpower: (json['settleableHashpower'] ?? 0).toDouble(), + settledTotalUsdt: (json['settledTotalUsdt'] ?? 0).toDouble(), + settledTotalHashpower: (json['settledTotalHashpower'] ?? 0).toDouble(), + expiredTotalUsdt: (json['expiredTotalUsdt'] ?? 0).toDouble(), + expiredTotalHashpower: (json['expiredTotalHashpower'] ?? 0).toDouble(), + ); + } + + /// 计算待领取剩余时间(秒) + int get pendingRemainingSeconds { + if (pendingExpireAt == null) return 0; + final remaining = pendingExpireAt!.difference(DateTime.now()).inSeconds; + return remaining > 0 ? remaining : 0; + } +} + +/// 钱包响应 +class WalletResponse { + final String walletId; + final String userId; + final BalancesInfo balances; + final double hashpower; + final RewardsInfo rewards; + final String status; + + WalletResponse({ + required this.walletId, + required this.userId, + required this.balances, + required this.hashpower, + required this.rewards, + required this.status, + }); + + factory WalletResponse.fromJson(Map json) { + return WalletResponse( + walletId: json['walletId'] ?? '', + userId: json['userId'] ?? '', + balances: BalancesInfo.fromJson(json['balances'] ?? {}), + hashpower: (json['hashpower'] ?? 0).toDouble(), + rewards: RewardsInfo.fromJson(json['rewards'] ?? {}), + status: json['status'] ?? '', + ); + } +} + +/// 领取奖励响应 +class ClaimRewardsResponse { + final String message; + + ClaimRewardsResponse({required this.message}); + + factory ClaimRewardsResponse.fromJson(Map json) { + return ClaimRewardsResponse( + message: json['message'] ?? '', + ); + } +} + +/// 钱包服务 +/// +/// 提供钱包余额查询、奖励领取等功能 +/// 调用 wallet-service 的 API +class WalletService { + final ApiClient _apiClient; + + WalletService({required ApiClient apiClient}) : _apiClient = apiClient; + + /// 获取我的钱包信息 + /// + /// 调用 GET /wallet/my-wallet (wallet-service) + Future getMyWallet() async { + try { + debugPrint('[WalletService] ========== 获取钱包信息 =========='); + debugPrint('[WalletService] 请求: GET /wallet/my-wallet'); + + final response = await _apiClient.get('/wallet/my-wallet'); + + debugPrint('[WalletService] 响应状态码: ${response.statusCode}'); + debugPrint('[WalletService] 响应数据类型: ${response.data.runtimeType}'); + + if (response.statusCode == 200) { + final data = response.data as Map; + debugPrint('[WalletService] 原始响应数据: $data'); + + // 解析并打印详细信息 + final wallet = WalletResponse.fromJson(data); + debugPrint('[WalletService] -------- 解析结果 --------'); + debugPrint('[WalletService] walletId: ${wallet.walletId}'); + debugPrint('[WalletService] userId: ${wallet.userId}'); + debugPrint('[WalletService] status: ${wallet.status}'); + debugPrint('[WalletService] hashpower: ${wallet.hashpower}'); + debugPrint('[WalletService] -------- 余额信息 --------'); + debugPrint('[WalletService] USDT: available=${wallet.balances.usdt.available}, frozen=${wallet.balances.usdt.frozen}'); + debugPrint('[WalletService] DST: available=${wallet.balances.dst.available}, frozen=${wallet.balances.dst.frozen}'); + debugPrint('[WalletService] -------- 奖励信息 --------'); + debugPrint('[WalletService] pendingUsdt: ${wallet.rewards.pendingUsdt}'); + debugPrint('[WalletService] pendingHashpower: ${wallet.rewards.pendingHashpower}'); + debugPrint('[WalletService] pendingExpireAt: ${wallet.rewards.pendingExpireAt}'); + debugPrint('[WalletService] pendingRemainingSeconds: ${wallet.rewards.pendingRemainingSeconds}'); + debugPrint('[WalletService] settleableUsdt: ${wallet.rewards.settleableUsdt}'); + debugPrint('[WalletService] settleableHashpower: ${wallet.rewards.settleableHashpower}'); + debugPrint('[WalletService] settledTotalUsdt: ${wallet.rewards.settledTotalUsdt}'); + debugPrint('[WalletService] settledTotalHashpower: ${wallet.rewards.settledTotalHashpower}'); + debugPrint('[WalletService] expiredTotalUsdt: ${wallet.rewards.expiredTotalUsdt}'); + debugPrint('[WalletService] expiredTotalHashpower: ${wallet.rewards.expiredTotalHashpower}'); + debugPrint('[WalletService] ================================'); + + return wallet; + } + + debugPrint('[WalletService] 请求失败,状态码: ${response.statusCode}'); + debugPrint('[WalletService] 响应内容: ${response.data}'); + throw Exception('获取钱包信息失败: ${response.statusCode}'); + } catch (e, stackTrace) { + debugPrint('[WalletService] !!!!!!!!!! 获取钱包信息异常 !!!!!!!!!!'); + debugPrint('[WalletService] 错误: $e'); + debugPrint('[WalletService] 堆栈: $stackTrace'); + rethrow; + } + } + + /// 领取奖励 + /// + /// 调用 POST /wallet/claim-rewards (wallet-service) + /// 将待领取的奖励转为可结算状态 + Future claimRewards() async { + try { + debugPrint('[WalletService] ========== 领取奖励 =========='); + debugPrint('[WalletService] 请求: POST /wallet/claim-rewards'); + + final response = await _apiClient.post('/wallet/claim-rewards'); + + debugPrint('[WalletService] 响应状态码: ${response.statusCode}'); + debugPrint('[WalletService] 响应数据: ${response.data}'); + + if (response.statusCode == 200) { + final data = response.data as Map; + final result = ClaimRewardsResponse.fromJson(data); + debugPrint('[WalletService] 领取成功: ${result.message}'); + debugPrint('[WalletService] ================================'); + return result; + } + + debugPrint('[WalletService] 领取失败,状态码: ${response.statusCode}'); + throw Exception('领取奖励失败: ${response.statusCode}'); + } catch (e, stackTrace) { + debugPrint('[WalletService] !!!!!!!!!! 领取奖励异常 !!!!!!!!!!'); + debugPrint('[WalletService] 错误: $e'); + debugPrint('[WalletService] 堆栈: $stackTrace'); + rethrow; + } + } + + /// 结算收益 + /// + /// 调用 POST /wallet/settle (wallet-service) + /// 将可结算的USDT兑换为指定币种 + Future settleRewards({ + required double usdtAmount, + required String settleCurrency, + }) async { + try { + debugPrint('[WalletService] ========== 结算收益 =========='); + debugPrint('[WalletService] 请求: POST /wallet/settle'); + debugPrint('[WalletService] 参数: usdtAmount=$usdtAmount, settleCurrency=$settleCurrency'); + + final response = await _apiClient.post( + '/wallet/settle', + data: { + 'usdtAmount': usdtAmount, + 'settleCurrency': settleCurrency, + }, + ); + + debugPrint('[WalletService] 响应状态码: ${response.statusCode}'); + debugPrint('[WalletService] 响应数据: ${response.data}'); + + if (response.statusCode == 200) { + final data = response.data as Map; + final orderId = data['settlementOrderId'] ?? ''; + debugPrint('[WalletService] 结算成功: orderId=$orderId'); + debugPrint('[WalletService] ================================'); + return orderId; + } + + debugPrint('[WalletService] 结算失败,状态码: ${response.statusCode}'); + throw Exception('结算失败: ${response.statusCode}'); + } catch (e, stackTrace) { + debugPrint('[WalletService] !!!!!!!!!! 结算收益异常 !!!!!!!!!!'); + debugPrint('[WalletService] 错误: $e'); + debugPrint('[WalletService] 堆栈: $stackTrace'); + rethrow; + } + } +} diff --git a/frontend/mobile-app/lib/features/profile/presentation/pages/profile_page.dart b/frontend/mobile-app/lib/features/profile/presentation/pages/profile_page.dart index 6503e3fa..f8d39e70 100644 --- a/frontend/mobile-app/lib/features/profile/presentation/pages/profile_page.dart +++ b/frontend/mobile-app/lib/features/profile/presentation/pages/profile_page.dart @@ -55,17 +55,19 @@ class _ProfilePageState extends ConsumerState { final int _currentPlanting = 12; final int _requiredPlanting = 50; - // 收益数据 - final double _pendingUsdt = 320.75; - final double _pendingPower = 50.00; - final double _settleableUsdt = 1500.00; - final double _settledUsdt = 8500.50; - final double _expiredUsdt = 250.00; - final double _expiredPower = 15.00; + // 收益数据(从 wallet-service 获取) + double _pendingUsdt = 0.0; + double _pendingPower = 0.0; + double _settleableUsdt = 0.0; + double _settledUsdt = 0.0; + double _expiredUsdt = 0.0; + double _expiredPower = 0.0; + bool _isLoadingWallet = true; + String? _walletError; // 倒计时 Timer? _timer; - int _remainingSeconds = 66942; // 18:35:42 + int _remainingSeconds = 0; // 应用版本信息 String _appVersion = '--'; @@ -78,7 +80,6 @@ class _ProfilePageState extends ConsumerState { @override void initState() { super.initState(); - _startCountdown(); // 先同步检查本地头像,再异步加载其他数据 _checkLocalAvatarSync(); _loadUserData(); @@ -86,6 +87,8 @@ class _ProfilePageState extends ConsumerState { // 加载推荐和授权数据 _loadReferralData(); _loadAuthorizationData(); + // 加载钱包和收益数据 + _loadWalletData(); } /// 加载应用信息 @@ -298,6 +301,74 @@ class _ProfilePageState extends ConsumerState { } } + /// 加载钱包和收益数据 (from wallet-service) + Future _loadWalletData() async { + try { + debugPrint('[ProfilePage] ========== 加载钱包数据 =========='); + debugPrint('[ProfilePage] mounted: $mounted'); + setState(() { + _isLoadingWallet = true; + _walletError = null; + }); + + debugPrint('[ProfilePage] 获取 walletServiceProvider...'); + final walletService = ref.read(walletServiceProvider); + debugPrint('[ProfilePage] 调用 getMyWallet()...'); + final wallet = await walletService.getMyWallet(); + + debugPrint('[ProfilePage] -------- 钱包数据加载成功 --------'); + debugPrint('[ProfilePage] 待领取 USDT: ${wallet.rewards.pendingUsdt}'); + debugPrint('[ProfilePage] 待领取 算力: ${wallet.rewards.pendingHashpower}'); + debugPrint('[ProfilePage] 可结算 USDT: ${wallet.rewards.settleableUsdt}'); + debugPrint('[ProfilePage] 已结算 USDT: ${wallet.rewards.settledTotalUsdt}'); + debugPrint('[ProfilePage] 已过期 USDT: ${wallet.rewards.expiredTotalUsdt}'); + debugPrint('[ProfilePage] 已过期 算力: ${wallet.rewards.expiredTotalHashpower}'); + debugPrint('[ProfilePage] 过期时间: ${wallet.rewards.pendingExpireAt}'); + debugPrint('[ProfilePage] 剩余秒数: ${wallet.rewards.pendingRemainingSeconds}'); + + if (mounted) { + debugPrint('[ProfilePage] 更新 UI 状态...'); + setState(() { + _pendingUsdt = wallet.rewards.pendingUsdt; + _pendingPower = wallet.rewards.pendingHashpower; + _settleableUsdt = wallet.rewards.settleableUsdt; + _settledUsdt = wallet.rewards.settledTotalUsdt; + _expiredUsdt = wallet.rewards.expiredTotalUsdt; + _expiredPower = wallet.rewards.expiredTotalHashpower; + _remainingSeconds = wallet.rewards.pendingRemainingSeconds; + _isLoadingWallet = false; + }); + + debugPrint('[ProfilePage] UI 状态更新完成'); + debugPrint('[ProfilePage] _pendingUsdt: $_pendingUsdt'); + debugPrint('[ProfilePage] _remainingSeconds: $_remainingSeconds'); + + // 启动倒计时(如果有待领取收益) + if (_remainingSeconds > 0) { + debugPrint('[ProfilePage] 启动倒计时: $_remainingSeconds 秒'); + _startCountdown(); + } else { + debugPrint('[ProfilePage] 无需启动倒计时(剩余时间为0)'); + } + } else { + debugPrint('[ProfilePage] 组件已卸载,跳过 UI 更新'); + } + debugPrint('[ProfilePage] ================================'); + } catch (e, stackTrace) { + debugPrint('[ProfilePage] !!!!!!!!!! 加载钱包数据失败 !!!!!!!!!!'); + debugPrint('[ProfilePage] 错误类型: ${e.runtimeType}'); + debugPrint('[ProfilePage] 错误信息: $e'); + debugPrint('[ProfilePage] 堆栈跟踪:'); + debugPrint('$stackTrace'); + if (mounted) { + setState(() { + _isLoadingWallet = false; + _walletError = '加载失败,点击重试'; + }); + } + } + } + /// 后台下载并缓存头像 Future _downloadAndCacheAvatar(String url) async { final accountService = ref.read(accountServiceProvider); @@ -358,27 +429,33 @@ class _ProfilePageState extends ConsumerState { /// 领取全部收益 void _claimAllEarnings() { + // 如果没有待领取收益,提示用户 + if (_pendingUsdt <= 0 && _pendingPower <= 0) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('暂无可领取的收益'), + backgroundColor: Color(0xFF8B5A2B), + ), + ); + return; + } + showDialog( context: context, - builder: (context) => AlertDialog( + builder: (dialogContext) => AlertDialog( title: const Text('确认领取'), content: Text( '确定领取全部收益吗?\nUSDT: ${_formatNumber(_pendingUsdt)}\n算力: ${_formatNumber(_pendingPower)}', ), actions: [ TextButton( - onPressed: () => Navigator.pop(context), + onPressed: () => Navigator.pop(dialogContext), child: const Text('取消'), ), TextButton( - onPressed: () { - Navigator.pop(context); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('领取成功'), - backgroundColor: Color(0xFFD4AF37), - ), - ); + onPressed: () async { + Navigator.pop(dialogContext); + await _doClaimRewards(); }, child: const Text('确认'), ), @@ -387,6 +464,44 @@ class _ProfilePageState extends ConsumerState { ); } + /// 执行领取奖励 + Future _doClaimRewards() async { + try { + debugPrint('[ProfilePage] ========== 执行领取奖励 =========='); + debugPrint('[ProfilePage] 当前待领取: USDT=$_pendingUsdt, 算力=$_pendingPower'); + + final walletService = ref.read(walletServiceProvider); + debugPrint('[ProfilePage] 调用 claimRewards()...'); + await walletService.claimRewards(); + + debugPrint('[ProfilePage] 领取成功,准备刷新数据'); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('领取成功'), + backgroundColor: Color(0xFFD4AF37), + ), + ); + // 刷新钱包数据 + debugPrint('[ProfilePage] 刷新钱包数据...'); + _loadWalletData(); + } + debugPrint('[ProfilePage] ================================'); + } catch (e, stackTrace) { + debugPrint('[ProfilePage] !!!!!!!!!! 领取奖励失败 !!!!!!!!!!'); + debugPrint('[ProfilePage] 错误: $e'); + debugPrint('[ProfilePage] 堆栈: $stackTrace'); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('领取失败: $e'), + backgroundColor: Colors.red, + ), + ); + } + } + } + /// 结算 void _onSettlement() { context.go(RoutePaths.trading); @@ -883,15 +998,68 @@ class _ProfilePageState extends ConsumerState { width: 1, ), ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // 倒计时 - Column( - crossAxisAlignment: CrossAxisAlignment.start, + 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 Text( - '24 小时倒计时', + 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: [ + const Text( + '24 小时倒计时', style: TextStyle( fontSize: 12, fontFamily: 'Inter', @@ -994,8 +1162,7 @@ class _ProfilePageState extends ConsumerState { ), ), ], - ), - ); + ); } /// 构建结算区域