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
|
// Deposit Service Provider
|
||||||
final depositServiceProvider = Provider<DepositService>((ref) {
|
final depositServiceProvider = Provider<DepositService>((ref) {
|
||||||
final apiClient = ref.watch(apiClientProvider);
|
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
|
// Override provider with initialized instance
|
||||||
|
|
|
||||||
|
|
@ -702,6 +702,22 @@ class AccountService {
|
||||||
value: 'true',
|
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() - 账户恢复完成');
|
debugPrint('$_tag recoverByMnemonic() - 账户恢复完成');
|
||||||
return result;
|
return result;
|
||||||
} on ApiException catch (e) {
|
} on ApiException catch (e) {
|
||||||
|
|
@ -776,6 +792,13 @@ class AccountService {
|
||||||
value: 'true',
|
value: 'true',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 恢复的账户钱包已就绪(助记词恢复意味着钱包已存在)
|
||||||
|
debugPrint('$_tag _saveRecoverAccountData() - 标记钱包已就绪');
|
||||||
|
await _secureStorage.write(
|
||||||
|
key: StorageKeys.isWalletReady,
|
||||||
|
value: 'true',
|
||||||
|
);
|
||||||
|
|
||||||
debugPrint('$_tag _saveRecoverAccountData() - 恢复账号数据保存完成');
|
debugPrint('$_tag _saveRecoverAccountData() - 恢复账号数据保存完成');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import '../network/api_client.dart';
|
import '../network/api_client.dart';
|
||||||
|
import '../storage/secure_storage.dart';
|
||||||
|
import '../storage/storage_keys.dart';
|
||||||
|
|
||||||
/// 充值地址响应
|
/// 充值地址响应
|
||||||
class DepositAddressResponse {
|
class DepositAddressResponse {
|
||||||
|
|
@ -75,28 +77,85 @@ class BalanceResponse {
|
||||||
/// 提供充值地址获取和余额查询功能
|
/// 提供充值地址获取和余额查询功能
|
||||||
class DepositService {
|
class DepositService {
|
||||||
final ApiClient _apiClient;
|
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 充值地址
|
/// 返回用户的 KAVA 和 BSC 充值地址
|
||||||
/// 会验证地址签名,确保地址未被篡改
|
|
||||||
Future<DepositAddressResponse> getDepositAddresses() async {
|
Future<DepositAddressResponse> getDepositAddresses() async {
|
||||||
try {
|
try {
|
||||||
debugPrint('获取充值地址...');
|
debugPrint('获取充值地址...');
|
||||||
final response = await _apiClient.get('/api/deposit/addresses');
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
// 先尝试从本地存储读取
|
||||||
final data = response.data as Map<String, dynamic>;
|
final kavaAddress = await _secureStorage.read(key: StorageKeys.walletAddressKava);
|
||||||
debugPrint('充值地址获取成功: kava=${data['kavaAddress']}, bsc=${data['bscAddress']}');
|
final bscAddress = await _secureStorage.read(key: StorageKeys.walletAddressBsc);
|
||||||
return DepositAddressResponse.fromJson(data);
|
|
||||||
|
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) {
|
} catch (e) {
|
||||||
debugPrint('获取充值地址失败: $e');
|
debugPrint('获取充值地址失败: $e');
|
||||||
rethrow;
|
return DepositAddressResponse(
|
||||||
|
isValid: false,
|
||||||
|
message: '获取充值地址失败: $e',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import '../../../../core/di/injection_container.dart';
|
||||||
|
|
||||||
/// 挖矿状态枚举
|
/// 挖矿状态枚举
|
||||||
enum MiningStatus {
|
enum MiningStatus {
|
||||||
|
|
@ -21,12 +23,37 @@ class _MiningPageState extends ConsumerState<MiningPage> {
|
||||||
// 当前挖矿状态
|
// 当前挖矿状态
|
||||||
MiningStatus _miningStatus = MiningStatus.pending;
|
MiningStatus _miningStatus = MiningStatus.pending;
|
||||||
|
|
||||||
// 模拟用户数据
|
// 用户数据(从存储加载)
|
||||||
final String _serialNumber = '12345';
|
String _serialNumber = '--';
|
||||||
|
String? _avatarSvg;
|
||||||
|
String? _avatarUrl;
|
||||||
final String _community = '星空社区';
|
final String _community = '星空社区';
|
||||||
final String _province = '广东';
|
final String _province = '广东';
|
||||||
final String _city = '深圳';
|
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() {
|
void _showHelpInfo() {
|
||||||
showDialog(
|
showDialog(
|
||||||
|
|
@ -178,22 +205,11 @@ class _MiningPageState extends ConsumerState<MiningPage> {
|
||||||
height: 80,
|
height: 80,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(40),
|
borderRadius: BorderRadius.circular(40),
|
||||||
gradient: const LinearGradient(
|
color: const Color(0xFFFFF5E6),
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
colors: [
|
|
||||||
Color(0xFFFF6B6B),
|
|
||||||
Color(0xFFFFE66D),
|
|
||||||
Color(0xFF4ECDC4),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
child: const Center(
|
child: ClipRRect(
|
||||||
child: Icon(
|
borderRadius: BorderRadius.circular(40),
|
||||||
Icons.person,
|
child: _buildAvatarContent(),
|
||||||
size: 40,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
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