rwadurian/frontend/mobile-app/lib/features/auth/presentation/pages/onboarding_page.dart

574 lines
19 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
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';
/// 创建账号页面 - 用户首次进入应用时的引导页面
/// 提供创建钱包和导入助记词两种选项
class OnboardingPage extends ConsumerStatefulWidget {
const OnboardingPage({super.key});
@override
ConsumerState<OnboardingPage> createState() => _OnboardingPageState();
}
class _OnboardingPageState extends ConsumerState<OnboardingPage> {
// 用户协议勾选状态
bool _isAgreed = false;
// 创建钱包加载状态
bool _isCreating = false;
// 账号是否已创建
bool _isAccountCreated = false;
// 是否正在加载状态
bool _isLoading = true;
// 已创建的账号数据
String? _userSerialNum;
String? _username;
String? _avatarSvg;
String? _referralCode;
@override
void initState() {
super.initState();
debugPrint('[OnboardingPage] initState - 检查账号状态');
_checkAccountStatus();
}
/// 检查账号是否已创建
Future<void> _checkAccountStatus() async {
debugPrint('[OnboardingPage] _checkAccountStatus - 开始检查');
try {
final accountService = ref.read(accountServiceProvider);
// 检查是否已创建账号
final hasAccount = await accountService.hasAccount();
debugPrint('[OnboardingPage] _checkAccountStatus - hasAccount: $hasAccount');
if (hasAccount) {
// 读取已保存的账号数据
debugPrint('[OnboardingPage] _checkAccountStatus - 读取已保存账号数据');
final userSerialNum = await accountService.getUserSerialNum();
final username = await accountService.getUsername();
final avatarSvg = await accountService.getAvatarSvg();
final referralCode = await accountService.getReferralCode();
debugPrint('[OnboardingPage] _checkAccountStatus - userSerialNum: $userSerialNum, username: $username');
if (mounted) {
setState(() {
_isAccountCreated = true;
_userSerialNum = userSerialNum;
_username = username;
_avatarSvg = avatarSvg;
_referralCode = referralCode;
_isLoading = false;
// 如果账号已创建,自动勾选协议
_isAgreed = true;
});
}
} else {
debugPrint('[OnboardingPage] _checkAccountStatus - 账号未创建');
if (mounted) {
setState(() {
_isAccountCreated = false;
_isLoading = false;
});
}
}
} catch (e, stackTrace) {
debugPrint('[OnboardingPage] _checkAccountStatus - 异常: $e');
debugPrint('[OnboardingPage] _checkAccountStatus - 堆栈: $stackTrace');
if (mounted) {
setState(() {
_isAccountCreated = false;
_isLoading = false;
});
}
}
}
/// 创建账号
///
/// 调用后端 API 快速创建账号(不含钱包信息)
Future<void> _createAccount() async {
debugPrint('[OnboardingPage] _createAccount - 用户点击创建账号');
if (!_isAgreed) {
debugPrint('[OnboardingPage] _createAccount - 未同意协议,显示提示');
_showAgreementTip();
return;
}
setState(() {
_isCreating = true;
});
try {
// 获取 AccountService 和 SecureStorage
final accountService = ref.read(accountServiceProvider);
final secureStorage = ref.read(secureStorageProvider);
// 读取邀请人推荐码(从向导页保存的)
final inviterReferralCode = await secureStorage.read(
key: StorageKeys.inviterReferralCode,
);
debugPrint('[OnboardingPage] _createAccount - 邀请人推荐码: ${inviterReferralCode ?? ""}');
// 调用后端 API 创建账号
debugPrint('[OnboardingPage] _createAccount - 调用 accountService.createAccount()');
final response = await accountService.createAccount(
inviterReferralCode: inviterReferralCode,
);
debugPrint('[OnboardingPage] _createAccount - 成功! 序列号=${response.userSerialNum}, 用户名=${response.username}');
// 创建成功后清除临时存储的邀请人推荐码
if (inviterReferralCode != null) {
await secureStorage.delete(key: StorageKeys.inviterReferralCode);
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;
}
// 更新状态
debugPrint('[OnboardingPage] _createAccount - 更新本地状态');
setState(() {
_isAccountCreated = true;
_userSerialNum = response.userSerialNum;
_username = response.username;
_avatarSvg = response.avatarSvg;
_referralCode = response.referralCode;
});
// 跳转到备份助记词页面
debugPrint('[OnboardingPage] _createAccount - 跳转到备份助记词页面');
context.push(
RoutePaths.backupMnemonic,
extra: BackupMnemonicParams(
userSerialNum: response.userSerialNum,
referralCode: response.referralCode,
),
);
} catch (e, stackTrace) {
debugPrint('[OnboardingPage] _createAccount - 创建失败: $e');
debugPrint('[OnboardingPage] _createAccount - 堆栈: $stackTrace');
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('创建账号失败: ${e.toString().replaceAll('Exception: ', '')}'),
backgroundColor: Colors.red,
duration: const Duration(seconds: 5),
),
);
} finally {
if (mounted) {
setState(() => _isCreating = false);
}
}
}
/// 显示需要同意协议的提示
void _showAgreementTip() {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('请先阅读并同意用户协议和隐私政策'),
backgroundColor: Color(0xFFD4AF37),
),
);
}
/// 显示用户协议
void _showUserAgreement() {
// TODO: 跳转到用户协议页面
debugPrint('显示用户协议');
}
/// 显示隐私政策
void _showPrivacyPolicy() {
// TODO: 跳转到隐私政策页面
debugPrint('显示隐私政策');
}
/// 导入助记词
void _importMnemonic() {
debugPrint('[OnboardingPage] _importMnemonic - 跳转到导入助记词页面');
context.push(RoutePaths.importMnemonic);
}
/// 跳转到备份助记词页面(账号已创建的情况)
void _goToBackupMnemonic() {
debugPrint('[OnboardingPage] _goToBackupMnemonic - 跳转到备份页面');
if (_userSerialNum == null) {
debugPrint('[OnboardingPage] _goToBackupMnemonic - userSerialNum为空显示错误');
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('账号数据不完整,请重新创建'),
backgroundColor: Colors.red,
),
);
return;
}
debugPrint('[OnboardingPage] _goToBackupMnemonic - userSerialNum: $_userSerialNum');
context.push(
RoutePaths.backupMnemonic,
extra: BackupMnemonicParams(
userSerialNum: _userSerialNum!,
referralCode: _referralCode,
),
);
}
/// 处理按钮点击
void _handleButtonTap() {
debugPrint('[OnboardingPage] _handleButtonTap - isAccountCreated: $_isAccountCreated');
if (_isAccountCreated) {
// 账号已创建,跳转到备份页面
_goToBackupMnemonic();
} else {
// 账号未创建,创建新账号
_createAccount();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
width: double.infinity,
height: double.infinity,
// 背景图片
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/backgrounds/onboarding_bg.jpg'),
fit: BoxFit.cover,
),
),
child: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15),
child: Column(
children: [
// 顶部返回按钮
_buildHeader(),
// 留出空间给背景图的 logo
const Spacer(flex: 3),
// 创建账户说明区域
_buildDescription(),
const SizedBox(height: 16),
// 底部操作区域
_buildActionSection(),
const Spacer(flex: 1),
],
),
),
),
),
);
}
/// 返回向导页第5页仅在账号未创建时可用
void _goBackToGuide() {
debugPrint('[OnboardingPage] _goBackToGuide - 返回向导页第5页');
// 返回向导页传入初始页索引4第5页
context.go(RoutePaths.guide, extra: 4);
}
/// 构建顶部返回按钮
Widget _buildHeader() {
return SizedBox(
height: 32,
child: (!_isLoading && !_isAccountCreated)
? Align(
alignment: Alignment.centerLeft,
child: GestureDetector(
onTap: _goBackToGuide,
behavior: HitTestBehavior.opaque,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.9),
borderRadius: BorderRadius.circular(16),
),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.arrow_back_ios,
size: 16,
color: Color(0xFF2E7D32),
),
SizedBox(width: 4),
Text(
'返回',
style: TextStyle(
fontSize: 14,
fontFamily: 'Inter',
fontWeight: FontWeight.w600,
color: Color(0xFF2E7D32),
),
),
],
),
),
),
)
: null,
);
}
/// 构建创建账户说明区域
Widget _buildDescription() {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.95),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.1),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: const Column(
children: [
// 标题
Text(
'创建账户',
style: TextStyle(
fontSize: 22,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.25,
letterSpacing: -0.33,
color: Color(0xFF2E7D32),
),
),
SizedBox(height: 12),
// 说明文字
Text(
'我们将为你创建三个链的钱包地址KAVA / DST / BSC同时生成唯一序列号用于推荐与权益。',
style: TextStyle(
fontSize: 15,
fontFamily: 'Inter',
height: 1.5,
color: Color(0xFF424242),
),
textAlign: TextAlign.center,
),
],
),
);
}
/// 构建底部操作区域
Widget _buildActionSection() {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.95),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.1),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Column(
children: [
// 生成钱包按钮
_buildCreateButton(),
const SizedBox(height: 16),
// 用户协议勾选
_buildAgreementCheckbox(),
const SizedBox(height: 16),
// 导入助记词链接
_buildImportLink(),
],
),
);
}
/// 构建创建钱包按钮
Widget _buildCreateButton() {
// 加载中显示骨架
if (_isLoading) {
return Container(
width: double.infinity,
height: 48,
decoration: BoxDecoration(
color: const Color(0xFFD4AF37).withValues(alpha: 0.5),
borderRadius: BorderRadius.circular(8),
),
child: const Center(
child: SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
),
),
);
}
// 根据账号状态决定按钮文字和行为
final buttonText = _isAccountCreated
? '账号已创建(点击备份助记词)'
: '生成钱包(创建账户)';
// 账号已创建时,不需要勾选协议
final isEnabled = _isAccountCreated || _isAgreed;
return GestureDetector(
onTap: (_isCreating || !isEnabled) ? null : _handleButtonTap,
child: Opacity(
opacity: isEnabled ? 1.0 : 0.5,
child: Container(
width: double.infinity,
height: 48,
decoration: BoxDecoration(
color: _isAccountCreated
? const Color(0xFF52C41A) // 绿色表示已创建
: const Color(0xFFD4AF37), // 金色表示待创建
borderRadius: BorderRadius.circular(8),
),
child: Center(
child: _isCreating
? const SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
)
: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (_isAccountCreated) ...[
const Icon(
Icons.check_circle,
color: Colors.white,
size: 20,
),
const SizedBox(width: 8),
],
Text(
buttonText,
style: const TextStyle(
fontSize: 16,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
height: 1.5,
letterSpacing: 0.24,
color: Colors.white,
),
),
],
),
),
),
),
);
}
/// 构建用户协议勾选框
Widget _buildAgreementCheckbox() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 勾选框
SizedBox(
width: 20,
height: 20,
child: Checkbox(
value: _isAgreed,
onChanged: (value) {
setState(() {
_isAgreed = value ?? false;
});
},
activeColor: const Color(0xFF2E7D32),
side: const BorderSide(color: Color(0xFF757575)),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
),
),
),
const SizedBox(width: 8),
// 协议文字
Flexible(
child: RichText(
text: TextSpan(
style: const TextStyle(
fontSize: 14,
fontFamily: 'Inter',
fontWeight: FontWeight.w500,
height: 1.43,
color: Color(0xFF424242),
),
children: [
const TextSpan(text: '我已阅读并同意 '),
TextSpan(
text: '《用户协议》',
style: const TextStyle(color: Color(0xFF2E7D32)),
recognizer: TapGestureRecognizer()..onTap = _showUserAgreement,
),
TextSpan(
text: '《隐私政策》',
style: const TextStyle(color: Color(0xFF2E7D32)),
recognizer: TapGestureRecognizer()..onTap = _showPrivacyPolicy,
),
],
),
),
),
],
);
}
/// 构建导入助记词链接
Widget _buildImportLink() {
return GestureDetector(
onTap: _importMnemonic,
child: const Text(
'已有账号? 导入助记词',
style: TextStyle(
fontSize: 14,
fontFamily: 'Inter',
fontWeight: FontWeight.w600,
height: 1.43,
color: Color(0xFF2E7D32),
),
),
);
}
}