feat(mobile): integrate wallet-service API for profile page rewards

- Add WalletService to fetch wallet/rewards data from backend
- Replace hardcoded earnings data with real API calls
- Add loading/error states for earnings section
- Implement claim rewards functionality with API
- Add comprehensive debug logging for troubleshooting

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2025-12-09 20:05:14 -08:00
parent 62270ed202
commit 8335187436
3 changed files with 479 additions and 29 deletions

View File

@ -6,6 +6,7 @@ import '../services/account_service.dart';
import '../services/referral_service.dart'; import '../services/referral_service.dart';
import '../services/authorization_service.dart'; import '../services/authorization_service.dart';
import '../services/deposit_service.dart'; import '../services/deposit_service.dart';
import '../services/wallet_service.dart';
// Storage Providers // Storage Providers
final secureStorageProvider = Provider<SecureStorage>((ref) { final secureStorageProvider = Provider<SecureStorage>((ref) {
@ -51,6 +52,12 @@ final depositServiceProvider = Provider<DepositService>((ref) {
return DepositService(apiClient: apiClient, secureStorage: secureStorage); return DepositService(apiClient: apiClient, secureStorage: secureStorage);
}); });
// Wallet Service Provider
final walletServiceProvider = Provider<WalletService>((ref) {
final apiClient = ref.watch(apiClientProvider);
return WalletService(apiClient: apiClient);
});
// Override provider with initialized instance // Override provider with initialized instance
ProviderContainer createProviderContainer(LocalStorage localStorage) { ProviderContainer createProviderContainer(LocalStorage localStorage) {
return ProviderContainer( return ProviderContainer(

View File

@ -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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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<WalletResponse> 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<String, dynamic>;
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<ClaimRewardsResponse> 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<String, dynamic>;
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<String> 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<String, dynamic>;
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;
}
}
}

View File

@ -55,17 +55,19 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
final int _currentPlanting = 12; final int _currentPlanting = 12;
final int _requiredPlanting = 50; final int _requiredPlanting = 50;
// // wallet-service
final double _pendingUsdt = 320.75; double _pendingUsdt = 0.0;
final double _pendingPower = 50.00; double _pendingPower = 0.0;
final double _settleableUsdt = 1500.00; double _settleableUsdt = 0.0;
final double _settledUsdt = 8500.50; double _settledUsdt = 0.0;
final double _expiredUsdt = 250.00; double _expiredUsdt = 0.0;
final double _expiredPower = 15.00; double _expiredPower = 0.0;
bool _isLoadingWallet = true;
String? _walletError;
// //
Timer? _timer; Timer? _timer;
int _remainingSeconds = 66942; // 18:35:42 int _remainingSeconds = 0;
// //
String _appVersion = '--'; String _appVersion = '--';
@ -78,7 +80,6 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_startCountdown();
// //
_checkLocalAvatarSync(); _checkLocalAvatarSync();
_loadUserData(); _loadUserData();
@ -86,6 +87,8 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
// //
_loadReferralData(); _loadReferralData();
_loadAuthorizationData(); _loadAuthorizationData();
//
_loadWalletData();
} }
/// ///
@ -298,6 +301,74 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
} }
} }
/// (from wallet-service)
Future<void> _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<void> _downloadAndCacheAvatar(String url) async { Future<void> _downloadAndCacheAvatar(String url) async {
final accountService = ref.read(accountServiceProvider); final accountService = ref.read(accountServiceProvider);
@ -358,27 +429,33 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
/// ///
void _claimAllEarnings() { void _claimAllEarnings() {
//
if (_pendingUsdt <= 0 && _pendingPower <= 0) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('暂无可领取的收益'),
backgroundColor: Color(0xFF8B5A2B),
),
);
return;
}
showDialog( showDialog(
context: context, context: context,
builder: (context) => AlertDialog( builder: (dialogContext) => AlertDialog(
title: const Text('确认领取'), title: const Text('确认领取'),
content: Text( content: Text(
'确定领取全部收益吗?\nUSDT: ${_formatNumber(_pendingUsdt)}\n算力: ${_formatNumber(_pendingPower)}', '确定领取全部收益吗?\nUSDT: ${_formatNumber(_pendingUsdt)}\n算力: ${_formatNumber(_pendingPower)}',
), ),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.pop(dialogContext),
child: const Text('取消'), child: const Text('取消'),
), ),
TextButton( TextButton(
onPressed: () { onPressed: () async {
Navigator.pop(context); Navigator.pop(dialogContext);
ScaffoldMessenger.of(context).showSnackBar( await _doClaimRewards();
const SnackBar(
content: Text('领取成功'),
backgroundColor: Color(0xFFD4AF37),
),
);
}, },
child: const Text('确认'), child: const Text('确认'),
), ),
@ -387,6 +464,44 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
); );
} }
///
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() { void _onSettlement() {
context.go(RoutePaths.trading); context.go(RoutePaths.trading);
@ -883,15 +998,68 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
width: 1, width: 1,
), ),
), ),
child: Column( child: _isLoadingWallet
crossAxisAlignment: CrossAxisAlignment.start, ? _buildLoadingState()
children: [ : _walletError != null
// ? _buildErrorState()
Column( : _buildEarningsContent(),
crossAxisAlignment: CrossAxisAlignment.start, );
}
///
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: [ children: [
const Text( const Icon(
'24 小时倒计时', 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( style: TextStyle(
fontSize: 12, fontSize: 12,
fontFamily: 'Inter', fontFamily: 'Inter',
@ -994,8 +1162,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
), ),
), ),
], ],
), );
);
} }
/// ///