fix(mobile-app): fix avatar and wallet address issues after mnemonic recovery
- Mining page: use accountService to load avatar (consistent with profile page) - Account recovery: fetch and save wallet addresses after successful recovery - Account recovery: set isWalletReady=true to skip backup mnemonic page - Deposit service: read wallet addresses from local storage or fetch from /user/wallet API 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
3c2144ad7c
commit
e8e0c6db86
|
|
@ -40,7 +40,8 @@ final referralServiceProvider = Provider<ReferralService>((ref) {
|
|||
// Deposit Service Provider
|
||||
final depositServiceProvider = Provider<DepositService>((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
|
||||
|
|
|
|||
|
|
@ -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() - 恢复账号数据保存完成');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<DepositAddressResponse> getDepositAddresses() async {
|
||||
try {
|
||||
debugPrint('获取充值地址...');
|
||||
final response = await _apiClient.get('/api/deposit/addresses');
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = response.data as Map<String, dynamic>;
|
||||
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<String, dynamic>;
|
||||
final data = responseData['data'] as Map<String, dynamic>?;
|
||||
|
||||
if (data != null && data['status'] == 'ready') {
|
||||
final addresses = data['walletAddresses'] as Map<String, dynamic>?;
|
||||
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',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<MiningPage> {
|
|||
// 当前挖矿状态
|
||||
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<void> _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<MiningPage> {
|
|||
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<MiningPage> {
|
|||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建头像内容(支持网络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>(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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue