feat(account): 实现多账号管理功能
- 新增 MultiAccountService 处理多账号存储和切换 - 新增 AccountSwitchPage 账号切换页面 - 修改 storage_keys.dart 添加多账号相关键和前缀方法 - 修改 ProfilePage 切换账号按钮跳转到账号切换页面 - 修改退出登录逻辑,保留账号数据只清除会话状态 - 新账号创建时自动添加到账号列表 - 支持旧单账号数据自动迁移到多账号架构 - 支持删除账号(带确认对话框) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
04644ea3f8
commit
f3a475a6f4
|
|
@ -3,6 +3,7 @@ import '../storage/secure_storage.dart';
|
|||
import '../storage/local_storage.dart';
|
||||
import '../network/api_client.dart';
|
||||
import '../services/account_service.dart';
|
||||
import '../services/multi_account_service.dart';
|
||||
import '../services/referral_service.dart';
|
||||
import '../services/authorization_service.dart';
|
||||
import '../services/deposit_service.dart';
|
||||
|
|
@ -36,6 +37,12 @@ final accountServiceProvider = Provider<AccountService>((ref) {
|
|||
);
|
||||
});
|
||||
|
||||
// Multi Account Service Provider
|
||||
final multiAccountServiceProvider = Provider<MultiAccountService>((ref) {
|
||||
final secureStorage = ref.watch(secureStorageProvider);
|
||||
return MultiAccountService(secureStorage);
|
||||
});
|
||||
|
||||
// Referral Service Provider
|
||||
final referralServiceProvider = Provider<ReferralService>((ref) {
|
||||
final apiClient = ref.watch(apiClientProvider);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,349 @@
|
|||
import 'dart:convert';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import '../storage/secure_storage.dart';
|
||||
import '../storage/storage_keys.dart';
|
||||
|
||||
/// 账号信息摘要(用于账号列表显示)
|
||||
class AccountSummary {
|
||||
final String userSerialNum; // 用户序列号,作为唯一标识
|
||||
final String username; // 用户名
|
||||
final String? avatarSvg; // SVG 头像
|
||||
final String? avatarUrl; // 自定义头像 URL
|
||||
final DateTime createdAt; // 创建时间
|
||||
|
||||
AccountSummary({
|
||||
required this.userSerialNum,
|
||||
required this.username,
|
||||
this.avatarSvg,
|
||||
this.avatarUrl,
|
||||
required this.createdAt,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'userSerialNum': userSerialNum,
|
||||
'username': username,
|
||||
'avatarSvg': avatarSvg,
|
||||
'avatarUrl': avatarUrl,
|
||||
'createdAt': createdAt.toIso8601String(),
|
||||
};
|
||||
|
||||
factory AccountSummary.fromJson(Map<String, dynamic> json) {
|
||||
return AccountSummary(
|
||||
userSerialNum: json['userSerialNum'] as String,
|
||||
username: json['username'] as String,
|
||||
avatarSvg: json['avatarSvg'] as String?,
|
||||
avatarUrl: json['avatarUrl'] as String?,
|
||||
createdAt: DateTime.parse(json['createdAt'] as String),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => 'AccountSummary(userSerialNum: $userSerialNum, username: $username)';
|
||||
}
|
||||
|
||||
/// 多账号管理服务
|
||||
/// 负责管理多个账号的存储和切换
|
||||
class MultiAccountService {
|
||||
static const String _tag = '[MultiAccountService]';
|
||||
|
||||
final SecureStorage _secureStorage;
|
||||
|
||||
MultiAccountService(this._secureStorage);
|
||||
|
||||
/// 获取账号列表
|
||||
Future<List<AccountSummary>> getAccountList() async {
|
||||
debugPrint('$_tag getAccountList() - 获取账号列表');
|
||||
try {
|
||||
final jsonStr = await _secureStorage.read(key: StorageKeys.accountList);
|
||||
if (jsonStr == null || jsonStr.isEmpty) {
|
||||
debugPrint('$_tag getAccountList() - 账号列表为空');
|
||||
return [];
|
||||
}
|
||||
final List<dynamic> jsonList = jsonDecode(jsonStr);
|
||||
final accounts = jsonList
|
||||
.map((e) => AccountSummary.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
debugPrint('$_tag getAccountList() - 找到 ${accounts.length} 个账号');
|
||||
return accounts;
|
||||
} catch (e) {
|
||||
debugPrint('$_tag getAccountList() - 解析失败: $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/// 保存账号列表
|
||||
Future<void> _saveAccountList(List<AccountSummary> accounts) async {
|
||||
final jsonStr = jsonEncode(accounts.map((e) => e.toJson()).toList());
|
||||
await _secureStorage.write(key: StorageKeys.accountList, value: jsonStr);
|
||||
debugPrint('$_tag _saveAccountList() - 保存 ${accounts.length} 个账号');
|
||||
}
|
||||
|
||||
/// 添加账号到列表
|
||||
Future<void> addAccount(AccountSummary account) async {
|
||||
debugPrint('$_tag addAccount() - 添加账号: ${account.userSerialNum}');
|
||||
final accounts = await getAccountList();
|
||||
|
||||
// 检查是否已存在
|
||||
final existingIndex = accounts.indexWhere(
|
||||
(a) => a.userSerialNum == account.userSerialNum,
|
||||
);
|
||||
|
||||
if (existingIndex >= 0) {
|
||||
// 更新现有账号
|
||||
accounts[existingIndex] = account;
|
||||
debugPrint('$_tag addAccount() - 更新现有账号');
|
||||
} else {
|
||||
// 添加新账号
|
||||
accounts.add(account);
|
||||
debugPrint('$_tag addAccount() - 添加新账号');
|
||||
}
|
||||
|
||||
await _saveAccountList(accounts);
|
||||
}
|
||||
|
||||
/// 从列表中移除账号
|
||||
Future<void> removeAccount(String userSerialNum) async {
|
||||
debugPrint('$_tag removeAccount() - 移除账号: $userSerialNum');
|
||||
final accounts = await getAccountList();
|
||||
accounts.removeWhere((a) => a.userSerialNum == userSerialNum);
|
||||
await _saveAccountList(accounts);
|
||||
}
|
||||
|
||||
/// 获取当前活跃账号ID
|
||||
Future<String?> getCurrentAccountId() async {
|
||||
return await _secureStorage.read(key: StorageKeys.currentAccountId);
|
||||
}
|
||||
|
||||
/// 设置当前活跃账号ID
|
||||
Future<void> setCurrentAccountId(String? accountId) async {
|
||||
if (accountId == null) {
|
||||
await _secureStorage.delete(key: StorageKeys.currentAccountId);
|
||||
} else {
|
||||
await _secureStorage.write(key: StorageKeys.currentAccountId, value: accountId);
|
||||
}
|
||||
debugPrint('$_tag setCurrentAccountId() - 设置为: $accountId');
|
||||
}
|
||||
|
||||
/// 切换到指定账号
|
||||
/// 返回 true 表示切换成功
|
||||
Future<bool> switchToAccount(String userSerialNum) async {
|
||||
debugPrint('$_tag switchToAccount() - 切换到账号: $userSerialNum');
|
||||
|
||||
// 验证账号存在
|
||||
final accounts = await getAccountList();
|
||||
final account = accounts.where((a) => a.userSerialNum == userSerialNum).firstOrNull;
|
||||
|
||||
if (account == null) {
|
||||
debugPrint('$_tag switchToAccount() - 账号不存在');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 从账号专用存储中恢复数据到当前存储
|
||||
await _restoreAccountData(userSerialNum);
|
||||
|
||||
// 设置当前账号
|
||||
await setCurrentAccountId(userSerialNum);
|
||||
|
||||
debugPrint('$_tag switchToAccount() - 切换成功');
|
||||
return true;
|
||||
}
|
||||
|
||||
/// 保存当前账号数据到账号专用存储
|
||||
Future<void> saveCurrentAccountData() async {
|
||||
final currentId = await getCurrentAccountId();
|
||||
if (currentId == null) {
|
||||
debugPrint('$_tag saveCurrentAccountData() - 无当前账号');
|
||||
return;
|
||||
}
|
||||
|
||||
debugPrint('$_tag saveCurrentAccountData() - 保存账号数据: $currentId');
|
||||
|
||||
// 需要保存的账号相关键
|
||||
final keysToSave = [
|
||||
StorageKeys.userSerialNum,
|
||||
StorageKeys.username,
|
||||
StorageKeys.avatarSvg,
|
||||
StorageKeys.avatarUrl,
|
||||
StorageKeys.referralCode,
|
||||
StorageKeys.inviterSequence,
|
||||
StorageKeys.isAccountCreated,
|
||||
StorageKeys.walletAddressBsc,
|
||||
StorageKeys.walletAddressKava,
|
||||
StorageKeys.walletAddressDst,
|
||||
StorageKeys.mnemonic,
|
||||
StorageKeys.isWalletReady,
|
||||
StorageKeys.isMnemonicBackedUp,
|
||||
StorageKeys.accessToken,
|
||||
StorageKeys.refreshToken,
|
||||
];
|
||||
|
||||
for (final key in keysToSave) {
|
||||
final value = await _secureStorage.read(key: key);
|
||||
if (value != null) {
|
||||
final prefixedKey = StorageKeys.withAccountPrefix(currentId, key);
|
||||
await _secureStorage.write(key: prefixedKey, value: value);
|
||||
}
|
||||
}
|
||||
|
||||
debugPrint('$_tag saveCurrentAccountData() - 保存完成');
|
||||
}
|
||||
|
||||
/// 从账号专用存储恢复数据到当前存储
|
||||
Future<void> _restoreAccountData(String accountId) async {
|
||||
debugPrint('$_tag _restoreAccountData() - 恢复账号数据: $accountId');
|
||||
|
||||
// 需要恢复的账号相关键
|
||||
final keysToRestore = [
|
||||
StorageKeys.userSerialNum,
|
||||
StorageKeys.username,
|
||||
StorageKeys.avatarSvg,
|
||||
StorageKeys.avatarUrl,
|
||||
StorageKeys.referralCode,
|
||||
StorageKeys.inviterSequence,
|
||||
StorageKeys.isAccountCreated,
|
||||
StorageKeys.walletAddressBsc,
|
||||
StorageKeys.walletAddressKava,
|
||||
StorageKeys.walletAddressDst,
|
||||
StorageKeys.mnemonic,
|
||||
StorageKeys.isWalletReady,
|
||||
StorageKeys.isMnemonicBackedUp,
|
||||
StorageKeys.accessToken,
|
||||
StorageKeys.refreshToken,
|
||||
];
|
||||
|
||||
for (final key in keysToRestore) {
|
||||
final prefixedKey = StorageKeys.withAccountPrefix(accountId, key);
|
||||
final value = await _secureStorage.read(key: prefixedKey);
|
||||
if (value != null) {
|
||||
await _secureStorage.write(key: key, value: value);
|
||||
} else {
|
||||
// 如果账号存储中没有该键,删除当前存储中的值
|
||||
await _secureStorage.delete(key: key);
|
||||
}
|
||||
}
|
||||
|
||||
debugPrint('$_tag _restoreAccountData() - 恢复完成');
|
||||
}
|
||||
|
||||
/// 退出当前账号(不删除账号数据)
|
||||
/// 清除当前会话状态,但保留账号在列表中
|
||||
Future<void> logoutCurrentAccount() async {
|
||||
debugPrint('$_tag logoutCurrentAccount() - 退出当前账号');
|
||||
|
||||
// 保存当前账号数据
|
||||
await saveCurrentAccountData();
|
||||
|
||||
// 清除当前会话的 Token(但保留在账号存储中)
|
||||
await _secureStorage.delete(key: StorageKeys.accessToken);
|
||||
await _secureStorage.delete(key: StorageKeys.refreshToken);
|
||||
|
||||
// 清除当前账号标记
|
||||
await setCurrentAccountId(null);
|
||||
|
||||
// 清除当前账号信息(但不删除账号列表)
|
||||
await _secureStorage.delete(key: StorageKeys.userSerialNum);
|
||||
await _secureStorage.delete(key: StorageKeys.username);
|
||||
await _secureStorage.delete(key: StorageKeys.isAccountCreated);
|
||||
|
||||
debugPrint('$_tag logoutCurrentAccount() - 退出完成');
|
||||
}
|
||||
|
||||
/// 完全删除账号(包括所有数据)
|
||||
Future<void> deleteAccount(String userSerialNum) async {
|
||||
debugPrint('$_tag deleteAccount() - 删除账号: $userSerialNum');
|
||||
|
||||
// 从列表中移除
|
||||
await removeAccount(userSerialNum);
|
||||
|
||||
// 删除账号专用存储的所有数据
|
||||
final keysToDelete = [
|
||||
StorageKeys.userSerialNum,
|
||||
StorageKeys.username,
|
||||
StorageKeys.avatarSvg,
|
||||
StorageKeys.avatarUrl,
|
||||
StorageKeys.referralCode,
|
||||
StorageKeys.inviterSequence,
|
||||
StorageKeys.isAccountCreated,
|
||||
StorageKeys.walletAddressBsc,
|
||||
StorageKeys.walletAddressKava,
|
||||
StorageKeys.walletAddressDst,
|
||||
StorageKeys.mnemonic,
|
||||
StorageKeys.isWalletReady,
|
||||
StorageKeys.isMnemonicBackedUp,
|
||||
StorageKeys.accessToken,
|
||||
StorageKeys.refreshToken,
|
||||
];
|
||||
|
||||
for (final key in keysToDelete) {
|
||||
final prefixedKey = StorageKeys.withAccountPrefix(userSerialNum, key);
|
||||
await _secureStorage.delete(key: prefixedKey);
|
||||
}
|
||||
|
||||
// 如果删除的是当前账号,清除当前账号标记
|
||||
final currentId = await getCurrentAccountId();
|
||||
if (currentId == userSerialNum) {
|
||||
await logoutCurrentAccount();
|
||||
}
|
||||
|
||||
debugPrint('$_tag deleteAccount() - 删除完成');
|
||||
}
|
||||
|
||||
/// 迁移旧数据到多账号架构
|
||||
/// 如果存在旧的单账号数据,将其添加到账号列表
|
||||
Future<void> migrateFromSingleAccount() async {
|
||||
debugPrint('$_tag migrateFromSingleAccount() - 检查是否需要迁移');
|
||||
|
||||
// 检查是否已有账号列表
|
||||
final accounts = await getAccountList();
|
||||
if (accounts.isNotEmpty) {
|
||||
debugPrint('$_tag migrateFromSingleAccount() - 已有账号列表,无需迁移');
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否存在旧的单账号数据
|
||||
final userSerialNum = await _secureStorage.read(key: StorageKeys.userSerialNum);
|
||||
final isAccountCreated = await _secureStorage.read(key: StorageKeys.isAccountCreated);
|
||||
|
||||
if (userSerialNum == null || isAccountCreated != 'true') {
|
||||
debugPrint('$_tag migrateFromSingleAccount() - 无旧账号数据');
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取账号信息
|
||||
final username = await _secureStorage.read(key: StorageKeys.username) ?? '未知用户';
|
||||
final avatarSvg = await _secureStorage.read(key: StorageKeys.avatarSvg);
|
||||
final avatarUrl = await _secureStorage.read(key: StorageKeys.avatarUrl);
|
||||
|
||||
// 创建账号摘要
|
||||
final account = AccountSummary(
|
||||
userSerialNum: userSerialNum,
|
||||
username: username,
|
||||
avatarSvg: avatarSvg,
|
||||
avatarUrl: avatarUrl,
|
||||
createdAt: DateTime.now(), // 旧数据没有创建时间,使用当前时间
|
||||
);
|
||||
|
||||
// 添加到账号列表
|
||||
await addAccount(account);
|
||||
|
||||
// 设置为当前账号
|
||||
await setCurrentAccountId(userSerialNum);
|
||||
|
||||
// 保存当前账号数据到账号专用存储
|
||||
await saveCurrentAccountData();
|
||||
|
||||
debugPrint('$_tag migrateFromSingleAccount() - 迁移完成: $userSerialNum');
|
||||
}
|
||||
|
||||
/// 检查是否有任何已登录的账号
|
||||
Future<bool> hasAnyAccount() async {
|
||||
final accounts = await getAccountList();
|
||||
return accounts.isNotEmpty;
|
||||
}
|
||||
|
||||
/// 获取账号数量
|
||||
Future<int> getAccountCount() async {
|
||||
final accounts = await getAccountList();
|
||||
return accounts.length;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,10 @@
|
|||
class StorageKeys {
|
||||
StorageKeys._();
|
||||
|
||||
// ===== 多账号管理 =====
|
||||
static const String accountList = 'account_list'; // 账号列表 (JSON 数组)
|
||||
static const String currentAccountId = 'current_account_id'; // 当前活跃账号ID (userSerialNum)
|
||||
|
||||
// 账号信息
|
||||
static const String userSerialNum = 'user_serial_num'; // 用户序列号
|
||||
static const String username = 'username'; // 随机用户名
|
||||
|
|
@ -11,6 +15,12 @@ class StorageKeys {
|
|||
static const String inviterReferralCode = 'inviter_referral_code'; // 邀请人推荐码(注册前临时存储)
|
||||
static const String isAccountCreated = 'is_account_created'; // 账号是否已创建
|
||||
|
||||
/// 生成带账号前缀的存储键
|
||||
/// 用于多账号隔离存储
|
||||
static String withAccountPrefix(String accountId, String key) {
|
||||
return 'account_${accountId}_$key';
|
||||
}
|
||||
|
||||
// 钱包信息
|
||||
static const String walletAddressBsc = 'wallet_address_bsc';
|
||||
static const String walletAddressKava = 'wallet_address_kava';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,392 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import '../../../../core/di/injection_container.dart';
|
||||
import '../../../../core/services/multi_account_service.dart';
|
||||
import '../../../../routes/route_paths.dart';
|
||||
|
||||
/// 账号切换页面
|
||||
/// 显示所有已登录的账号列表,支持切换和添加新账号
|
||||
class AccountSwitchPage extends ConsumerStatefulWidget {
|
||||
const AccountSwitchPage({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<AccountSwitchPage> createState() => _AccountSwitchPageState();
|
||||
}
|
||||
|
||||
class _AccountSwitchPageState extends ConsumerState<AccountSwitchPage> {
|
||||
List<AccountSummary> _accounts = [];
|
||||
String? _currentAccountId;
|
||||
bool _isLoading = true;
|
||||
bool _isSwitching = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadAccounts();
|
||||
}
|
||||
|
||||
Future<void> _loadAccounts() async {
|
||||
setState(() => _isLoading = true);
|
||||
|
||||
final multiAccountService = ref.read(multiAccountServiceProvider);
|
||||
|
||||
// 先执行迁移(如果需要)
|
||||
await multiAccountService.migrateFromSingleAccount();
|
||||
|
||||
final accounts = await multiAccountService.getAccountList();
|
||||
final currentId = await multiAccountService.getCurrentAccountId();
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_accounts = accounts;
|
||||
_currentAccountId = currentId;
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _switchToAccount(AccountSummary account) async {
|
||||
if (account.userSerialNum == _currentAccountId) {
|
||||
// 已是当前账号,直接返回
|
||||
context.pop();
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() => _isSwitching = true);
|
||||
|
||||
try {
|
||||
final multiAccountService = ref.read(multiAccountServiceProvider);
|
||||
|
||||
// 保存当前账号数据
|
||||
await multiAccountService.saveCurrentAccountData();
|
||||
|
||||
// 切换到新账号
|
||||
final success = await multiAccountService.switchToAccount(account.userSerialNum);
|
||||
|
||||
if (success && mounted) {
|
||||
// 切换成功,跳转到主页刷新状态
|
||||
context.go(RoutePaths.ranking);
|
||||
} else if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('切换账号失败')),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('[AccountSwitchPage] 切换账号失败: $e');
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('切换账号失败: $e')),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() => _isSwitching = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _addNewAccount() async {
|
||||
// 保存当前账号数据
|
||||
final multiAccountService = ref.read(multiAccountServiceProvider);
|
||||
await multiAccountService.saveCurrentAccountData();
|
||||
|
||||
// 退出当前账号但保留数据
|
||||
await multiAccountService.logoutCurrentAccount();
|
||||
|
||||
// 跳转到向导页创建新账号
|
||||
if (mounted) {
|
||||
context.go(RoutePaths.guide);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _deleteAccount(AccountSummary account) async {
|
||||
// 显示确认对话框
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('删除账号'),
|
||||
content: Text(
|
||||
'确定要删除账号 "${account.username}" 吗?\n\n'
|
||||
'删除后该账号的所有本地数据将被清除,'
|
||||
'如需恢复请使用助记词重新导入。',
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: const Text('取消'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: const Color(0xFFE57373),
|
||||
),
|
||||
child: const Text('删除'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (confirmed != true) return;
|
||||
|
||||
try {
|
||||
final multiAccountService = ref.read(multiAccountServiceProvider);
|
||||
await multiAccountService.deleteAccount(account.userSerialNum);
|
||||
|
||||
// 重新加载账号列表
|
||||
await _loadAccounts();
|
||||
|
||||
// 如果没有账号了,跳转到向导页
|
||||
if (_accounts.isEmpty && mounted) {
|
||||
context.go(RoutePaths.guide);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('[AccountSwitchPage] 删除账号失败: $e');
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('删除账号失败: $e')),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFFFFBF5),
|
||||
appBar: AppBar(
|
||||
backgroundColor: const Color(0xFFFFFBF5),
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios, color: Color(0xFF5D4037)),
|
||||
onPressed: () => context.pop(),
|
||||
),
|
||||
title: const Text(
|
||||
'切换账号',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontFamily: 'Inter',
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF5D4037),
|
||||
),
|
||||
),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: _isLoading
|
||||
? const Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: Color(0xFFD4AF37),
|
||||
),
|
||||
)
|
||||
: _isSwitching
|
||||
? const Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
CircularProgressIndicator(
|
||||
color: Color(0xFFD4AF37),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
'正在切换账号...',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Color(0xFF5D4037),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
// 账号列表标题
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(bottom: 12),
|
||||
child: Text(
|
||||
'已登录的账号',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontFamily: 'Inter',
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0x995D4037),
|
||||
),
|
||||
),
|
||||
),
|
||||
// 账号列表
|
||||
..._accounts.map((account) => _buildAccountItem(account)),
|
||||
const SizedBox(height: 16),
|
||||
// 添加新账号按钮
|
||||
_buildAddAccountButton(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAccountItem(AccountSummary account) {
|
||||
final isCurrent = account.userSerialNum == _currentAccountId;
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: isCurrent ? const Color(0xFFD4AF37).withValues(alpha: 0.1) : const Color(0xFFFFF5E6),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: isCurrent ? const Color(0xFFD4AF37) : const Color(0x33D4AF37),
|
||||
width: isCurrent ? 2 : 1,
|
||||
),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: () => _switchToAccount(account),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
// 头像
|
||||
_buildAvatar(account),
|
||||
const SizedBox(width: 12),
|
||||
// 账号信息
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
account.username,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontFamily: 'Inter',
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF5D4037),
|
||||
),
|
||||
),
|
||||
if (isCurrent) ...[
|
||||
const SizedBox(width: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFD4AF37),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: const Text(
|
||||
'当前',
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
account.userSerialNum,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Inter',
|
||||
color: Color(0x995D4037),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// 删除按钮(非当前账号才显示)
|
||||
if (!isCurrent)
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
Icons.delete_outline,
|
||||
color: Color(0xFFE57373),
|
||||
size: 20,
|
||||
),
|
||||
onPressed: () => _deleteAccount(account),
|
||||
),
|
||||
// 当前账号显示勾选图标
|
||||
if (isCurrent)
|
||||
const Icon(
|
||||
Icons.check_circle,
|
||||
color: Color(0xFFD4AF37),
|
||||
size: 24,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAvatar(AccountSummary account) {
|
||||
return Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
color: const Color(0xFFE8D5B7),
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: account.avatarSvg != null
|
||||
? SvgPicture.string(
|
||||
account.avatarSvg!,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: const Icon(
|
||||
Icons.person,
|
||||
size: 28,
|
||||
color: Color(0xFF8B5A2B),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAddAccountButton() {
|
||||
return GestureDetector(
|
||||
onTap: _addNewAccount,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFFFF5E6),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: const Color(0x33D4AF37),
|
||||
width: 1,
|
||||
style: BorderStyle.solid,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFD4AF37).withValues(alpha: 0.2),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.add,
|
||||
size: 20,
|
||||
color: Color(0xFFD4AF37),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const Text(
|
||||
'添加新账号',
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontFamily: 'Inter',
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF5D4037),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import '../../../../routes/route_paths.dart';
|
|||
import '../../../../routes/app_router.dart';
|
||||
import '../../../../core/di/injection_container.dart';
|
||||
import '../../../../core/storage/storage_keys.dart';
|
||||
import '../../../../core/services/multi_account_service.dart';
|
||||
|
||||
/// 创建账号页面 - 用户首次进入应用时的引导页面
|
||||
/// 提供创建钱包和导入助记词两种选项
|
||||
|
|
@ -130,6 +131,19 @@ class _OnboardingPageState extends ConsumerState<OnboardingPage> {
|
|||
debugPrint('[OnboardingPage] _createAccount - 已清除临时邀请人推荐码');
|
||||
}
|
||||
|
||||
// 将账号添加到多账号列表
|
||||
final multiAccountService = ref.read(multiAccountServiceProvider);
|
||||
await multiAccountService.addAccount(
|
||||
AccountSummary(
|
||||
userSerialNum: response.userSerialNum,
|
||||
username: response.username,
|
||||
avatarSvg: response.avatarSvg,
|
||||
createdAt: DateTime.now(),
|
||||
),
|
||||
);
|
||||
await multiAccountService.setCurrentAccountId(response.userSerialNum);
|
||||
debugPrint('[OnboardingPage] _createAccount - 已添加到多账号列表');
|
||||
|
||||
if (!mounted) {
|
||||
debugPrint('[OnboardingPage] _createAccount - Widget已卸载,忽略响应');
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -2867,21 +2867,9 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
|||
);
|
||||
}
|
||||
|
||||
/// 切换账号
|
||||
/// 切换账号 - 跳转到账号切换页面
|
||||
void _onSwitchAccount() {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('切换账号'),
|
||||
content: const Text('多账号管理功能即将上线,敬请期待。'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('确定'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
context.push(RoutePaths.accountSwitch);
|
||||
}
|
||||
|
||||
/// 退出登录
|
||||
|
|
@ -2890,7 +2878,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
|||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('退出登录'),
|
||||
content: const Text('确定要退出当前账号吗?\n\n退出后需要重新导入助记词才能恢复账号。'),
|
||||
content: const Text('确定要退出当前账号吗?\n\n退出后账号数据会保留在本地,可在账号切换页面重新登录。'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
|
|
@ -2914,9 +2902,9 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
|||
/// 执行退出登录
|
||||
Future<void> _performLogout() async {
|
||||
try {
|
||||
final accountService = ref.read(accountServiceProvider);
|
||||
// 清除所有本地数据
|
||||
await accountService.logout();
|
||||
final multiAccountService = ref.read(multiAccountServiceProvider);
|
||||
// 退出当前账号(保留账号数据)
|
||||
await multiAccountService.logoutCurrentAccount();
|
||||
// 导航到向导页
|
||||
if (mounted) {
|
||||
context.go(RoutePaths.guide);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import '../features/security/presentation/pages/bind_email_page.dart';
|
|||
import '../features/withdraw/presentation/pages/withdraw_usdt_page.dart';
|
||||
import '../features/withdraw/presentation/pages/withdraw_confirm_page.dart';
|
||||
import '../features/notification/presentation/pages/notification_inbox_page.dart';
|
||||
import '../features/account/presentation/pages/account_switch_page.dart';
|
||||
import 'route_paths.dart';
|
||||
import 'route_names.dart';
|
||||
|
||||
|
|
@ -187,6 +188,13 @@ final appRouterProvider = Provider<GoRouter>((ref) {
|
|||
builder: (context, state) => const NotificationInboxPage(),
|
||||
),
|
||||
|
||||
// Account Switch Page (账号切换)
|
||||
GoRoute(
|
||||
path: RoutePaths.accountSwitch,
|
||||
name: RouteNames.accountSwitch,
|
||||
builder: (context, state) => const AccountSwitchPage(),
|
||||
),
|
||||
|
||||
// Share Page (分享页面)
|
||||
GoRoute(
|
||||
path: RoutePaths.share,
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ class RouteNames {
|
|||
static const referralList = 'referral-list';
|
||||
static const earningsDetail = 'earnings-detail';
|
||||
static const notifications = 'notifications';
|
||||
static const accountSwitch = 'account-switch';
|
||||
static const deposit = 'deposit';
|
||||
static const depositUsdt = 'deposit-usdt';
|
||||
static const plantingQuantity = 'planting-quantity';
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ class RoutePaths {
|
|||
static const referralList = '/profile/referrals';
|
||||
static const earningsDetail = '/profile/earnings';
|
||||
static const notifications = '/notifications';
|
||||
static const accountSwitch = '/account/switch';
|
||||
static const deposit = '/deposit';
|
||||
static const depositUsdt = '/deposit/usdt';
|
||||
static const plantingQuantity = '/planting/quantity';
|
||||
|
|
|
|||
Loading…
Reference in New Issue