diff --git a/frontend/mobile-app/lib/core/di/injection_container.dart b/frontend/mobile-app/lib/core/di/injection_container.dart index 35aa0aac..e639c0ed 100644 --- a/frontend/mobile-app/lib/core/di/injection_container.dart +++ b/frontend/mobile-app/lib/core/di/injection_container.dart @@ -40,7 +40,8 @@ final referralServiceProvider = Provider((ref) { // Deposit Service Provider final depositServiceProvider = Provider((ref) { final apiClient = ref.watch(apiClientProvider); - return DepositService(apiClient: apiClient); + final secureStorage = ref.watch(secureStorageProvider); + return DepositService(apiClient: apiClient, secureStorage: secureStorage); }); // Override provider with initialized instance diff --git a/frontend/mobile-app/lib/core/services/account_service.dart b/frontend/mobile-app/lib/core/services/account_service.dart index 8fd2beb7..7e462b70 100644 --- a/frontend/mobile-app/lib/core/services/account_service.dart +++ b/frontend/mobile-app/lib/core/services/account_service.dart @@ -702,6 +702,22 @@ class AccountService { value: 'true', ); + // 获取并保存钱包地址 + debugPrint('$_tag recoverByMnemonic() - 获取钱包地址'); + try { + final walletInfo = await getWalletInfo(result.userSerialNum); + if (walletInfo.isReady && walletInfo.walletAddresses != null) { + debugPrint('$_tag recoverByMnemonic() - 钱包地址获取成功'); + // _saveWalletData 会保存地址并设置 isWalletReady + await _saveWalletData(walletInfo); + } else { + debugPrint('$_tag recoverByMnemonic() - 钱包地址未就绪: ${walletInfo.status}'); + } + } catch (e) { + // 钱包地址获取失败不影响恢复流程,可以稍后重试 + debugPrint('$_tag recoverByMnemonic() - 获取钱包地址失败 (不影响恢复): $e'); + } + debugPrint('$_tag recoverByMnemonic() - 账户恢复完成'); return result; } on ApiException catch (e) { @@ -776,6 +792,13 @@ class AccountService { value: 'true', ); + // 恢复的账户钱包已就绪(助记词恢复意味着钱包已存在) + debugPrint('$_tag _saveRecoverAccountData() - 标记钱包已就绪'); + await _secureStorage.write( + key: StorageKeys.isWalletReady, + value: 'true', + ); + debugPrint('$_tag _saveRecoverAccountData() - 恢复账号数据保存完成'); } diff --git a/frontend/mobile-app/lib/core/services/deposit_service.dart b/frontend/mobile-app/lib/core/services/deposit_service.dart index 888a9386..8f9b58ea 100644 --- a/frontend/mobile-app/lib/core/services/deposit_service.dart +++ b/frontend/mobile-app/lib/core/services/deposit_service.dart @@ -1,5 +1,7 @@ import 'package:flutter/foundation.dart'; import '../network/api_client.dart'; +import '../storage/secure_storage.dart'; +import '../storage/storage_keys.dart'; /// 充值地址响应 class DepositAddressResponse { @@ -75,28 +77,85 @@ class BalanceResponse { /// 提供充值地址获取和余额查询功能 class DepositService { final ApiClient _apiClient; + final SecureStorage _secureStorage; - DepositService({required ApiClient apiClient}) : _apiClient = apiClient; + DepositService({ + required ApiClient apiClient, + required SecureStorage secureStorage, + }) : _apiClient = apiClient, + _secureStorage = secureStorage; /// 获取充值地址 /// + /// 优先从本地存储获取,如果没有则从服务器获取 /// 返回用户的 KAVA 和 BSC 充值地址 - /// 会验证地址签名,确保地址未被篡改 Future getDepositAddresses() async { try { debugPrint('获取充值地址...'); - final response = await _apiClient.get('/api/deposit/addresses'); - if (response.statusCode == 200) { - final data = response.data as Map; - debugPrint('充值地址获取成功: kava=${data['kavaAddress']}, bsc=${data['bscAddress']}'); - return DepositAddressResponse.fromJson(data); + // 先尝试从本地存储读取 + final kavaAddress = await _secureStorage.read(key: StorageKeys.walletAddressKava); + final bscAddress = await _secureStorage.read(key: StorageKeys.walletAddressBsc); + + if (kavaAddress != null && kavaAddress.isNotEmpty && + bscAddress != null && bscAddress.isNotEmpty) { + debugPrint('从本地存储获取充值地址: kava=$kavaAddress, bsc=$bscAddress'); + return DepositAddressResponse( + kavaAddress: kavaAddress, + bscAddress: bscAddress, + isValid: true, + ); } - throw Exception('获取充值地址失败'); + // 本地没有,从服务器获取 + debugPrint('本地无缓存,从服务器获取钱包地址...'); + final response = await _apiClient.get('/user/wallet'); + + if (response.statusCode == 200) { + final responseData = response.data as Map; + final data = responseData['data'] as Map?; + + if (data != null && data['status'] == 'ready') { + final addresses = data['walletAddresses'] as Map?; + if (addresses != null) { + final kava = addresses['kava'] as String?; + final bsc = addresses['bsc'] as String?; + + debugPrint('从服务器获取充值地址成功: kava=$kava, bsc=$bsc'); + + // 保存到本地存储 + if (kava != null && kava.isNotEmpty) { + await _secureStorage.write(key: StorageKeys.walletAddressKava, value: kava); + } + if (bsc != null && bsc.isNotEmpty) { + await _secureStorage.write(key: StorageKeys.walletAddressBsc, value: bsc); + } + + return DepositAddressResponse( + kavaAddress: kava, + bscAddress: bsc, + isValid: kava != null && bsc != null, + message: (kava == null || bsc == null) ? '部分地址未生成' : null, + ); + } + } else if (data != null && data['status'] == 'generating') { + return DepositAddressResponse( + isValid: false, + message: '钱包生成中,请稍后重试', + ); + } + } + + return DepositAddressResponse( + isValid: false, + message: '获取充值地址失败', + ); } catch (e) { debugPrint('获取充值地址失败: $e'); - rethrow; + return DepositAddressResponse( + isValid: false, + message: '获取充值地址失败: $e', + ); } } diff --git a/frontend/mobile-app/lib/features/mining/presentation/pages/mining_page.dart b/frontend/mobile-app/lib/features/mining/presentation/pages/mining_page.dart index 4d4673e3..7bc98ab2 100644 --- a/frontend/mobile-app/lib/features/mining/presentation/pages/mining_page.dart +++ b/frontend/mobile-app/lib/features/mining/presentation/pages/mining_page.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import '../../../../core/di/injection_container.dart'; /// 挖矿状态枚举 enum MiningStatus { @@ -21,12 +23,37 @@ class _MiningPageState extends ConsumerState { // 当前挖矿状态 MiningStatus _miningStatus = MiningStatus.pending; - // 模拟用户数据 - final String _serialNumber = '12345'; + // 用户数据(从存储加载) + String _serialNumber = '--'; + String? _avatarSvg; + String? _avatarUrl; final String _community = '星空社区'; final String _province = '广东'; final String _city = '深圳'; + @override + void initState() { + super.initState(); + _loadUserData(); + } + + /// 加载用户数据 + Future _loadUserData() async { + final accountService = ref.read(accountServiceProvider); + + final serialNum = await accountService.getUserSerialNum(); + final avatarSvg = await accountService.getAvatarSvg(); + final avatarUrl = await accountService.getAvatarUrl(); + + if (mounted) { + setState(() { + _serialNumber = serialNum?.toString() ?? '--'; + _avatarSvg = avatarSvg; + _avatarUrl = avatarUrl; + }); + } + } + /// 显示帮助信息 void _showHelpInfo() { showDialog( @@ -178,22 +205,11 @@ class _MiningPageState extends ConsumerState { height: 80, decoration: BoxDecoration( borderRadius: BorderRadius.circular(40), - gradient: const LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [ - Color(0xFFFF6B6B), - Color(0xFFFFE66D), - Color(0xFF4ECDC4), - ], - ), + color: const Color(0xFFFFF5E6), ), - child: const Center( - child: Icon( - Icons.person, - size: 40, - color: Colors.white, - ), + child: ClipRRect( + borderRadius: BorderRadius.circular(40), + child: _buildAvatarContent(), ), ), const SizedBox(width: 16), @@ -344,4 +360,54 @@ class _MiningPageState extends ConsumerState { ], ); } + + /// 构建头像内容(支持网络URL和SVG) + Widget _buildAvatarContent() { + // 优先显示已上传的网络图片URL + if (_avatarUrl != null && _avatarUrl!.isNotEmpty) { + return Image.network( + _avatarUrl!, + width: 80, + height: 80, + fit: BoxFit.cover, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) return child; + return const Center( + child: SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation(Color(0xFFD4AF37)), + ), + ), + ); + }, + errorBuilder: (context, error, stackTrace) { + // 加载失败时显示SVG或默认头像 + return _buildSvgOrDefaultAvatar(); + }, + ); + } + + // 显示SVG或默认头像 + return _buildSvgOrDefaultAvatar(); + } + + /// 构建SVG或默认头像 + Widget _buildSvgOrDefaultAvatar() { + if (_avatarSvg != null) { + return SvgPicture.string( + _avatarSvg!, + width: 80, + height: 80, + fit: BoxFit.cover, + ); + } + return const Icon( + Icons.person, + size: 40, + color: Color(0xFF8B5A2B), + ); + } }