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:
hailin 2025-12-08 12:13:39 -08:00
parent 3c2144ad7c
commit e8e0c6db86
4 changed files with 176 additions and 27 deletions

View File

@ -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

View File

@ -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() - 恢复账号数据保存完成');
}

View File

@ -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',
);
}
}

View File

@ -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),
);
}
}