diff --git a/frontend/mobile-app/assets/images/Label@2x.png b/frontend/mobile-app/assets/images/Label@2x.png new file mode 100644 index 00000000..f9301afc Binary files /dev/null and b/frontend/mobile-app/assets/images/Label@2x.png differ diff --git a/frontend/mobile-app/lib/core/services/account_service.dart b/frontend/mobile-app/lib/core/services/account_service.dart index c2d876a4..e9e8e7a7 100644 --- a/frontend/mobile-app/lib/core/services/account_service.dart +++ b/frontend/mobile-app/lib/core/services/account_service.dart @@ -175,6 +175,44 @@ String _maskAddress(String? address) { return '${address.substring(0, 6)}...${address.substring(address.length - 4)}'; } +/// 恢复账户响应 +class RecoverAccountResponse { + final String userId; + final int userSerialNum; // accountSequence + final String username; // nickname + final String? avatarSvg; // avatarUrl + final String referralCode; + final String accessToken; + final String refreshToken; + + RecoverAccountResponse({ + required this.userId, + required this.userSerialNum, + required this.username, + this.avatarSvg, + required this.referralCode, + required this.accessToken, + required this.refreshToken, + }); + + factory RecoverAccountResponse.fromJson(Map json) { + debugPrint('[AccountService] 解析 RecoverAccountResponse: ${json.keys.toList()}'); + return RecoverAccountResponse( + userId: json['userId'] as String, + userSerialNum: json['accountSequence'] as int, + username: json['nickname'] as String, + avatarSvg: json['avatarUrl'] as String?, + referralCode: json['referralCode'] as String, + accessToken: json['accessToken'] as String, + refreshToken: json['refreshToken'] as String, + ); + } + + @override + String toString() => + 'RecoverAccountResponse(userSerialNum: $userSerialNum, username: $username, referralCode: $referralCode)'; +} + /// 账号服务 /// /// 处理账号创建、钱包获取等功能 @@ -596,6 +634,142 @@ class AccountService { return result; } + /// 通过助记词恢复账户 + /// + /// 使用序列号和 12 个助记词恢复已有账户 + /// [accountSequence] - 用户序列号 + /// [mnemonic] - 12 个助记词,用空格分隔 + Future recoverByMnemonic(int accountSequence, String mnemonic) async { + debugPrint('$_tag recoverByMnemonic() - 开始恢复账户'); + debugPrint('$_tag recoverByMnemonic() - 序列号: $accountSequence'); + + try { + // 获取设备ID + final deviceId = await getDeviceId(); + debugPrint('$_tag recoverByMnemonic() - 获取设备ID成功'); + + // 获取设备硬件信息 + final deviceInfo = await getDeviceHardwareInfo(); + debugPrint('$_tag recoverByMnemonic() - 获取设备硬件信息成功'); + + // 调用 API + debugPrint('$_tag recoverByMnemonic() - 调用 POST /user/recover-by-mnemonic'); + final response = await _apiClient.post( + '/user/recover-by-mnemonic', + data: { + 'accountSequence': accountSequence, + 'mnemonic': mnemonic, + 'newDeviceId': deviceId, + 'deviceName': '${deviceInfo.brand ?? ''} ${deviceInfo.model ?? ''}'.trim(), + }, + ); + debugPrint('$_tag recoverByMnemonic() - API 响应状态码: ${response.statusCode}'); + + if (response.data == null) { + debugPrint('$_tag recoverByMnemonic() - 错误: API 返回空响应'); + throw const ApiException('恢复账户失败: 空响应'); + } + + debugPrint('$_tag recoverByMnemonic() - 解析响应数据'); + final responseData = response.data as Map; + final data = responseData['data'] as Map; + final result = RecoverAccountResponse.fromJson(data); + debugPrint('$_tag recoverByMnemonic() - 解析成功: $result'); + + // 保存账号数据 (使用恢复账户专用的保存方法) + debugPrint('$_tag recoverByMnemonic() - 保存账号数据'); + await _saveRecoverAccountData(result, deviceId); + + // 保存助记词 + debugPrint('$_tag recoverByMnemonic() - 保存助记词'); + await _secureStorage.write( + key: StorageKeys.mnemonic, + value: mnemonic, + ); + + // 标记助记词已备份 (恢复的用户肯定已经备份过了) + await _secureStorage.write( + key: StorageKeys.isMnemonicBackedUp, + value: 'true', + ); + + debugPrint('$_tag recoverByMnemonic() - 账户恢复完成'); + return result; + } on ApiException catch (e) { + debugPrint('$_tag recoverByMnemonic() - API 异常: $e'); + rethrow; + } catch (e, stackTrace) { + debugPrint('$_tag recoverByMnemonic() - 未知异常: $e'); + debugPrint('$_tag recoverByMnemonic() - 堆栈: $stackTrace'); + throw ApiException('恢复账户失败: $e'); + } + } + + /// 保存恢复账户数据 + Future _saveRecoverAccountData( + RecoverAccountResponse response, + String deviceId, + ) async { + debugPrint('$_tag _saveRecoverAccountData() - 开始保存恢复账号数据'); + + // 保存用户序列号 + debugPrint('$_tag _saveRecoverAccountData() - 保存 userSerialNum: ${response.userSerialNum}'); + await _secureStorage.write( + key: StorageKeys.userSerialNum, + value: response.userSerialNum.toString(), + ); + + debugPrint('$_tag _saveRecoverAccountData() - 保存 referralCode: ${response.referralCode}'); + await _secureStorage.write( + key: StorageKeys.referralCode, + value: response.referralCode, + ); + + // 保存用户信息 + debugPrint('$_tag _saveRecoverAccountData() - 保存 username: ${response.username}'); + await _secureStorage.write( + key: StorageKeys.username, + value: response.username, + ); + + if (response.avatarSvg != null) { + debugPrint('$_tag _saveRecoverAccountData() - 保存 avatarSvg (长度: ${response.avatarSvg!.length})'); + await _secureStorage.write( + key: StorageKeys.avatarSvg, + value: response.avatarSvg!, + ); + } + + // 保存 Token + debugPrint('$_tag _saveRecoverAccountData() - 保存 accessToken (长度: ${response.accessToken.length})'); + await _secureStorage.write( + key: StorageKeys.accessToken, + value: response.accessToken, + ); + + debugPrint('$_tag _saveRecoverAccountData() - 保存 refreshToken (长度: ${response.refreshToken.length})'); + await _secureStorage.write( + key: StorageKeys.refreshToken, + value: response.refreshToken, + ); + + // 保存设备 ID + debugPrint('$_tag _saveRecoverAccountData() - 保存 deviceId: ${_maskAddress(deviceId)}'); + await _secureStorage.write( + key: StorageKeys.deviceId, + value: deviceId, + ); + + // 标记账号已创建 + debugPrint('$_tag _saveRecoverAccountData() - 标记账号已创建'); + await _secureStorage.write( + key: StorageKeys.isAccountCreated, + value: 'true', + ); + + debugPrint('$_tag _saveRecoverAccountData() - 恢复账号数据保存完成'); + } + /// 登出 Future logout() async { debugPrint('$_tag logout() - 开始登出,清除所有数据'); diff --git a/frontend/mobile-app/lib/core/services/referral_service.dart b/frontend/mobile-app/lib/core/services/referral_service.dart index 05509616..01c152d1 100644 --- a/frontend/mobile-app/lib/core/services/referral_service.dart +++ b/frontend/mobile-app/lib/core/services/referral_service.dart @@ -111,11 +111,11 @@ class ReferralService { /// 获取当前用户信息 (包含默认推荐链接) /// - /// 调用 GET /api/me + /// 调用 GET /me Future getMe() async { try { debugPrint('获取用户信息...'); - final response = await _apiClient.get('/api/me'); + final response = await _apiClient.get('/me'); if (response.statusCode == 200) { final data = response.data as Map; @@ -132,7 +132,7 @@ class ReferralService { /// 生成推荐链接 (短链) /// - /// 调用 POST /api/referrals/links + /// 调用 POST /referrals/links /// 返回包含 shortUrl 和 fullUrl 的响应 /// /// [channel] - 渠道标识: wechat, telegram, twitter 等 @@ -145,7 +145,7 @@ class ReferralService { debugPrint('生成推荐链接: channel=$channel, campaignId=$campaignId'); final response = await _apiClient.post( - '/api/referrals/links', + '/referrals/links', data: { if (channel != null) 'channel': channel, if (campaignId != null) 'campaignId': campaignId, diff --git a/frontend/mobile-app/lib/features/auth/presentation/pages/import_mnemonic_page.dart b/frontend/mobile-app/lib/features/auth/presentation/pages/import_mnemonic_page.dart new file mode 100644 index 00000000..2359d7dd --- /dev/null +++ b/frontend/mobile-app/lib/features/auth/presentation/pages/import_mnemonic_page.dart @@ -0,0 +1,523 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import '../../../../core/di/injection_container.dart'; +import '../../../../routes/route_paths.dart'; + +/// 导入助记词页面 - 用于恢复已有账户 +/// 用户输入序列号和12个助记词来恢复钱包 +class ImportMnemonicPage extends ConsumerStatefulWidget { + const ImportMnemonicPage({super.key}); + + @override + ConsumerState createState() => _ImportMnemonicPageState(); +} + +class _ImportMnemonicPageState extends ConsumerState { + // 序列号输入控制器 + final TextEditingController _serialNumController = TextEditingController(); + + // 12个助记词输入控制器 + final List _controllers = List.generate( + 12, + (_) => TextEditingController(), + ); + + // 12个焦点节点 + final List _focusNodes = List.generate( + 12, + (_) => FocusNode(), + ); + + // 是否正在提交 + bool _isSubmitting = false; + + // 错误信息 + String? _errorMessage; + + @override + void dispose() { + _serialNumController.dispose(); + for (final controller in _controllers) { + controller.dispose(); + } + for (final node in _focusNodes) { + node.dispose(); + } + super.dispose(); + } + + /// 获取完整的助记词字符串 + String get _mnemonic { + return _controllers + .map((c) => c.text.trim().toLowerCase()) + .join(' '); + } + + /// 获取序列号 + int? get _serialNum { + final text = _serialNumController.text.trim(); + if (text.isEmpty) return null; + return int.tryParse(text); + } + + /// 检查是否所有单词都已填写 + bool get _isComplete { + final hasSerialNum = _serialNumController.text.trim().isNotEmpty; + final hasAllWords = _controllers.every((c) => c.text.trim().isNotEmpty); + return hasSerialNum && hasAllWords; + } + + /// 导入并恢复账号 + Future _importAndRecover() async { + // 验证序列号 + final serialNum = _serialNum; + if (serialNum == null) { + setState(() { + _errorMessage = '请输入有效的序列号'; + }); + return; + } + + // 验证助记词 + if (!_controllers.every((c) => c.text.trim().isNotEmpty)) { + setState(() { + _errorMessage = '请填写完整的12个助记词'; + }); + return; + } + + setState(() { + _isSubmitting = true; + _errorMessage = null; + }); + + try { + final accountService = ref.read(accountServiceProvider); + + // 调用恢复账户API + final response = await accountService.recoverByMnemonic( + serialNum, + _mnemonic, + ); + + if (!mounted) return; + + // 恢复成功,跳转到主页 + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('账户恢复成功!序列号: ${response.userSerialNum}'), + backgroundColor: const Color(0xFF52C41A), + ), + ); + + // 跳转到主页 + context.go(RoutePaths.ranking); + } catch (e) { + debugPrint('恢复账户失败: $e'); + if (!mounted) return; + + setState(() { + _errorMessage = e.toString().replaceAll('Exception: ', ''); + }); + } finally { + if (mounted) { + setState(() { + _isSubmitting = false; + }); + } + } + } + + /// 返回创建账号页面 + void _goBack() { + context.pop(); + } + + /// 处理单词输入完成,自动跳转到下一个 + void _onWordChanged(int index, String value) { + // 清除错误信息 + if (_errorMessage != null) { + setState(() { + _errorMessage = null; + }); + } + + // 如果输入了空格,可能是粘贴了多个单词 + if (value.contains(' ')) { + _handlePaste(index, value); + return; + } + + // 刷新状态以更新按钮 + setState(() {}); + } + + /// 处理粘贴多个单词的情况 + void _handlePaste(int startIndex, String value) { + final words = value.trim().split(RegExp(r'\s+')); + + for (int i = 0; i < words.length && startIndex + i < 12; i++) { + _controllers[startIndex + i].text = words[i].toLowerCase(); + } + + // 焦点移动到最后一个填写的位置或下一个空位 + final nextIndex = (startIndex + words.length).clamp(0, 11); + _focusNodes[nextIndex].requestFocus(); + + setState(() {}); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + width: double.infinity, + height: double.infinity, + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Color(0xFFFFF7E6), // 浅米色 + Color(0xFFEAE0C8), // 浅橙色 + ], + ), + ), + child: SafeArea( + child: Column( + children: [ + // 顶部导航栏 + _buildAppBar(), + // 内容区域 + Expanded( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 16), + // 序列号输入 + _buildSerialNumInput(), + const SizedBox(height: 16), + // 助记词标签 + const Text( + '助记词', + style: TextStyle( + fontSize: 14, + fontFamily: 'Inter', + fontWeight: FontWeight.w500, + color: Color(0xFF745D43), + ), + ), + const SizedBox(height: 8), + // 助记词输入区域 + _buildMnemonicInputArea(), + const SizedBox(height: 12), + // 提示文字 + const Text( + '支持 KAVA / DST / BSC 链的钱包导入。请在私密环境中操作。', + style: TextStyle( + fontSize: 14, + fontFamily: 'Inter', + height: 1.5, + color: Color(0xFF745D43), + ), + ), + // 错误信息 + if (_errorMessage != null) ...[ + const SizedBox(height: 12), + Container( + width: double.infinity, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: const Color(0xFFFFE4E4), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + _errorMessage!, + style: const TextStyle( + fontSize: 14, + color: Color(0xFFD32F2F), + ), + ), + ), + ], + ], + ), + ), + ), + ), + // 底部按钮区域 + _buildFooter(), + ], + ), + ), + ), + ); + } + + /// 构建顶部导航栏 + Widget _buildAppBar() { + return Container( + height: 56, + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Stack( + alignment: Alignment.center, + children: [ + // 返回按钮 + Positioned( + left: 0, + child: GestureDetector( + onTap: _goBack, + child: Container( + width: 48, + height: 48, + alignment: Alignment.center, + child: const Icon( + Icons.arrow_back_ios, + color: Color(0xFF5D4037), + size: 20, + ), + ), + ), + ), + // 标题 + const Text( + '导入助记词', + style: TextStyle( + fontSize: 18, + fontFamily: 'Inter', + fontWeight: FontWeight.w700, + letterSpacing: -0.27, + color: Color(0xFF5D4037), + ), + ), + ], + ), + ); + } + + /// 构建序列号输入框 + Widget _buildSerialNumInput() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + '序列号', + style: TextStyle( + fontSize: 14, + fontFamily: 'Inter', + fontWeight: FontWeight.w500, + color: Color(0xFF745D43), + ), + ), + const SizedBox(height: 8), + Container( + height: 48, + decoration: BoxDecoration( + color: const Color(0xFFFFFDF8), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: const Color(0x338B5A2B), + width: 1, + ), + ), + child: TextField( + controller: _serialNumController, + style: const TextStyle( + fontSize: 16, + fontFamily: 'Inter', + color: Color(0xFF5D4037), + ), + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 12), + hintText: '请输入账户序列号', + hintStyle: TextStyle( + fontSize: 16, + color: Color(0x668B5A2B), + ), + ), + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + ], + onChanged: (_) { + if (_errorMessage != null) { + setState(() { + _errorMessage = null; + }); + } + setState(() {}); + }, + ), + ), + ], + ); + } + + /// 构建助记词输入区域 + Widget _buildMnemonicInputArea() { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: const Color(0xFFFFFDF8), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: const Color(0x338B5A2B), + width: 1, + ), + ), + child: Column( + children: [ + // 4行,每行3个单词 + for (int row = 0; row < 4; row++) ...[ + if (row > 0) const SizedBox(height: 12), + Row( + children: [ + for (int col = 0; col < 3; col++) ...[ + if (col > 0) const SizedBox(width: 12), + Expanded( + child: _buildWordInput(row * 3 + col), + ), + ], + ], + ), + ], + ], + ), + ); + } + + /// 构建单个单词输入框 + Widget _buildWordInput(int index) { + return Container( + height: 44, + decoration: BoxDecoration( + color: const Color(0xFFFFF7E6), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: const Color(0x338B5A2B), + width: 1, + ), + ), + child: Row( + children: [ + // 序号 + Container( + width: 28, + alignment: Alignment.center, + child: Text( + '${index + 1}', + style: const TextStyle( + fontSize: 12, + fontFamily: 'Inter', + fontWeight: FontWeight.w500, + color: Color(0xFF8B5A2B), + ), + ), + ), + // 输入框 + Expanded( + child: TextField( + controller: _controllers[index], + focusNode: _focusNodes[index], + style: const TextStyle( + fontSize: 14, + fontFamily: 'Inter', + color: Color(0xFF5D4037), + ), + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(horizontal: 4, vertical: 10), + isDense: true, + ), + textInputAction: index < 11 ? TextInputAction.next : TextInputAction.done, + autocorrect: false, + enableSuggestions: false, + onChanged: (value) => _onWordChanged(index, value), + onSubmitted: (_) { + if (index < 11) { + _focusNodes[index + 1].requestFocus(); + } else { + // 最后一个,尝试提交 + if (_isComplete) { + _importAndRecover(); + } + } + }, + ), + ), + ], + ), + ); + } + + /// 构建底部按钮区域 + Widget _buildFooter() { + return Container( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 24), + child: Column( + children: [ + // 导入并恢复账号按钮 + GestureDetector( + onTap: _isSubmitting ? null : _importAndRecover, + child: Opacity( + opacity: _isComplete && !_isSubmitting ? 1.0 : 0.5, + child: Container( + width: double.infinity, + height: 48, + decoration: BoxDecoration( + color: const Color(0xFF8B5A2B), + borderRadius: BorderRadius.circular(8), + ), + child: Center( + child: _isSubmitting + ? const SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation(Colors.white), + ), + ) + : const Text( + '导入并恢复账号', + style: TextStyle( + fontSize: 16, + fontFamily: 'Inter', + fontWeight: FontWeight.w700, + letterSpacing: 0.24, + color: Color(0xB3FFFFFF), + ), + ), + ), + ), + ), + ), + const SizedBox(height: 12), + // 返回创建账号链接 + GestureDetector( + onTap: _goBack, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 8), + child: const Text( + '返回创建账号', + style: TextStyle( + fontSize: 14, + fontFamily: 'Inter', + decoration: TextDecoration.underline, + color: Color(0xFF745D43), + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/frontend/mobile-app/lib/features/auth/presentation/pages/onboarding_page.dart b/frontend/mobile-app/lib/features/auth/presentation/pages/onboarding_page.dart index 75a0e8af..05784493 100644 --- a/frontend/mobile-app/lib/features/auth/presentation/pages/onboarding_page.dart +++ b/frontend/mobile-app/lib/features/auth/presentation/pages/onboarding_page.dart @@ -181,8 +181,8 @@ class _OnboardingPageState extends ConsumerState { /// 导入助记词 void _importMnemonic() { - // TODO: 跳转到导入助记词页面 - debugPrint('导入助记词'); + debugPrint('[OnboardingPage] _importMnemonic - 跳转到导入助记词页面'); + context.push(RoutePaths.importMnemonic); } /// 跳转到备份助记词页面(账号已创建的情况) diff --git a/frontend/mobile-app/lib/features/home/presentation/widgets/bottom_nav_bar.dart b/frontend/mobile-app/lib/features/home/presentation/widgets/bottom_nav_bar.dart index 063e70ab..a98ddb6a 100644 --- a/frontend/mobile-app/lib/features/home/presentation/widgets/bottom_nav_bar.dart +++ b/frontend/mobile-app/lib/features/home/presentation/widgets/bottom_nav_bar.dart @@ -1,9 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:flutter_screenutil/flutter_screenutil.dart'; /// 底部导航栏组件 /// 包含四个Tab:龙虎榜、矿机、交易、我 -/// 使用响应式设计适配各种屏幕尺寸 +/// 自定义设计风格,使用 LayoutBuilder 确保自适应 class BottomNavBar extends StatelessWidget { final int currentIndex; final Function(int) onTap; @@ -16,8 +15,12 @@ class BottomNavBar extends StatelessWidget { @override Widget build(BuildContext context) { + // 获取底部安全区域高度 + final bottomPadding = MediaQuery.of(context).padding.bottom; + return Container( - height: 65.h, + // 内容高度 + 底部安全区域 + height: 56 + bottomPadding, decoration: const BoxDecoration( color: Color(0xFFFFF5E6), border: Border( @@ -27,10 +30,10 @@ class BottomNavBar extends StatelessWidget { ), ), ), - child: SafeArea( - top: false, + child: Padding( + // 底部留出安全区域 + padding: EdgeInsets.only(bottom: bottomPadding), child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _buildNavItem( index: 0, @@ -74,26 +77,29 @@ class BottomNavBar extends StatelessWidget { child: GestureDetector( onTap: () => onTap(index), behavior: HitTestBehavior.opaque, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - isSelected ? activeIcon : icon, - size: 24.sp, - color: isSelected ? const Color(0xFFD4AF37) : const Color(0xFF8B5A2B), - ), - SizedBox(height: 2.h), - Text( - label, - style: TextStyle( - fontSize: 12.sp, - fontFamily: 'Inter', - fontWeight: FontWeight.w500, - height: 1.33, + child: SizedBox( + height: 56, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + isSelected ? activeIcon : icon, + size: 24, color: isSelected ? const Color(0xFFD4AF37) : const Color(0xFF8B5A2B), ), - ), - ], + const SizedBox(height: 2), + Text( + label, + style: TextStyle( + fontSize: 12, + fontFamily: 'Inter', + fontWeight: FontWeight.w500, + height: 1.33, + color: isSelected ? const Color(0xFFD4AF37) : const Color(0xFF8B5A2B), + ), + ), + ], + ), ), ), ); diff --git a/frontend/mobile-app/lib/features/share/presentation/pages/share_page.dart b/frontend/mobile-app/lib/features/share/presentation/pages/share_page.dart index 75ae94ad..e219c3a3 100644 --- a/frontend/mobile-app/lib/features/share/presentation/pages/share_page.dart +++ b/frontend/mobile-app/lib/features/share/presentation/pages/share_page.dart @@ -119,16 +119,21 @@ class _SharePageState extends ConsumerState { _buildAppBar(), // 内容区域 Expanded( - child: SingleChildScrollView( - child: Column( - children: [ - const SizedBox(height: 100), - // QR 码区域 - _buildQrCodeSection(), - const SizedBox(height: 40), - // 链接输入框 - _buildLinkSection(), - ], + child: Center( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 24), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // QR 码区域 + _buildQrCodeSection(), + const SizedBox(height: 32), + // 链接输入框 + _buildLinkSection(), + ], + ), + ), ), ), ), diff --git a/frontend/mobile-app/lib/routes/app_router.dart b/frontend/mobile-app/lib/routes/app_router.dart index 4ead7c2d..28ce231b 100644 --- a/frontend/mobile-app/lib/routes/app_router.dart +++ b/frontend/mobile-app/lib/routes/app_router.dart @@ -8,6 +8,7 @@ import '../features/auth/presentation/pages/onboarding_page.dart'; import '../features/auth/presentation/pages/backup_mnemonic_page.dart'; import '../features/auth/presentation/pages/verify_mnemonic_page.dart'; import '../features/auth/presentation/pages/wallet_created_page.dart'; +import '../features/auth/presentation/pages/import_mnemonic_page.dart'; import '../features/home/presentation/pages/home_shell_page.dart'; import '../features/ranking/presentation/pages/ranking_page.dart'; import '../features/mining/presentation/pages/mining_page.dart'; @@ -109,6 +110,13 @@ final appRouterProvider = Provider((ref) { builder: (context, state) => const OnboardingPage(), ), + // Import Mnemonic (导入助记词) + GoRoute( + path: RoutePaths.importMnemonic, + name: RouteNames.importMnemonic, + builder: (context, state) => const ImportMnemonicPage(), + ), + // Backup Mnemonic (备份助记词 - 会调用 API 获取钱包信息) GoRoute( path: RoutePaths.backupMnemonic, diff --git a/frontend/mobile-app/lib/routes/route_names.dart b/frontend/mobile-app/lib/routes/route_names.dart index da0a38c8..f62614a8 100644 --- a/frontend/mobile-app/lib/routes/route_names.dart +++ b/frontend/mobile-app/lib/routes/route_names.dart @@ -10,6 +10,7 @@ class RouteNames { static const verifyMnemonic = 'verify-mnemonic'; static const walletCreated = 'wallet-created'; static const importWallet = 'import-wallet'; + static const importMnemonic = 'import-mnemonic'; // Main tabs static const ranking = 'ranking'; diff --git a/frontend/mobile-app/lib/routes/route_paths.dart b/frontend/mobile-app/lib/routes/route_paths.dart index c387c71b..d88c1228 100644 --- a/frontend/mobile-app/lib/routes/route_paths.dart +++ b/frontend/mobile-app/lib/routes/route_paths.dart @@ -10,6 +10,7 @@ class RoutePaths { static const verifyMnemonic = '/auth/verify-mnemonic'; static const walletCreated = '/auth/wallet-created'; static const importWallet = '/auth/import'; + static const importMnemonic = '/auth/import-mnemonic'; // Main tabs static const ranking = '/ranking';