feat(mobile-app): 重构向导页5注册流程

1. **推荐码改为必填项**
   - 移除"我没有推荐人"选项
   - 推荐码输入框始终可见
   - 支持手动输入和扫码两种方式
   - 添加推荐码验证逻辑

2. **隐藏导入助记词入口**
   - 保留代码但设置为 if (false) 隐藏
   - 需要时可修改为 true 启用

3. **添加恢复账号入口**
   - 新增"恢复账号(手机号+密码登录)"按钮
   - 点击跳转到 phoneLogin 页面

4. **路由更新**
   - 添加 RoutePaths.phoneLogin 路由常量

UI改进:
- 推荐码输入框样式更新:半透明白色背景
- 扫码按钮图标改为 qr_code_scanner
- 副标题文案改为"请输入推荐码"

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2025-12-20 19:08:13 -08:00
parent 4ec92d015b
commit 4906fa1815
2 changed files with 97 additions and 200 deletions

View File

@ -283,7 +283,7 @@ class _WelcomePageContent extends ConsumerStatefulWidget {
} }
class _WelcomePageContentState extends ConsumerState<_WelcomePageContent> { class _WelcomePageContentState extends ConsumerState<_WelcomePageContent> {
bool _hasReferrer = true; // _hasReferrer
final TextEditingController _referralCodeController = TextEditingController(); final TextEditingController _referralCodeController = TextEditingController();
@override @override
@ -326,12 +326,9 @@ class _WelcomePageContentState extends ConsumerState<_WelcomePageContent> {
return trimmed.length >= 3; return trimmed.length >= 3;
} }
/// ///
bool get _canProceed { bool get _canProceed {
// "没有推荐人" //
if (!_hasReferrer) return true;
// "有推荐人"
return _isValidReferralCode(_referralCodeController.text); return _isValidReferralCode(_referralCodeController.text);
} }
@ -341,44 +338,19 @@ class _WelcomePageContentState extends ConsumerState<_WelcomePageContent> {
context.push(RoutePaths.importMnemonic); context.push(RoutePaths.importMnemonic);
} }
///
Future<void> _goToPhoneRegister() async {
debugPrint('[GuidePage] _goToPhoneRegister - 跳转到手机号注册页面');
//
String? inviterCode;
if (_hasReferrer && _referralCodeController.text.trim().isNotEmpty) {
inviterCode = _referralCodeController.text.trim();
final secureStorage = ref.read(secureStorageProvider);
await secureStorage.write(
key: StorageKeys.inviterReferralCode,
value: inviterCode,
);
debugPrint('[GuidePage] 保存邀请人推荐码: $inviterCode');
}
if (!mounted) return;
context.push(
RoutePaths.phoneRegister,
extra: PhoneRegisterParams(inviterReferralCode: inviterCode),
);
}
/// () /// ()
Future<void> _saveReferralCodeAndProceed() async { Future<void> _saveReferralCodeAndProceed() async {
if (!_canProceed) return; if (!_canProceed) return;
// //
String? inviterCode; final inviterCode = _referralCodeController.text.trim();
if (_hasReferrer && _referralCodeController.text.trim().isNotEmpty) { final secureStorage = ref.read(secureStorageProvider);
inviterCode = _referralCodeController.text.trim(); await secureStorage.write(
final secureStorage = ref.read(secureStorageProvider); key: StorageKeys.inviterReferralCode,
await secureStorage.write( value: inviterCode,
key: StorageKeys.inviterReferralCode, );
value: inviterCode, debugPrint('[GuidePage] 保存邀请人推荐码: $inviterCode');
);
debugPrint('[GuidePage] 保存邀请人推荐码: $inviterCode');
}
if (!mounted) return; if (!mounted) return;
@ -401,7 +373,6 @@ class _WelcomePageContentState extends ConsumerState<_WelcomePageContent> {
final referralCode = _extractReferralCode(result); final referralCode = _extractReferralCode(result);
setState(() { setState(() {
_referralCodeController.text = referralCode; _referralCodeController.text = referralCode;
_hasReferrer = true;
}); });
} }
} }
@ -545,7 +516,7 @@ class _WelcomePageContentState extends ConsumerState<_WelcomePageContent> {
SizedBox(height: 12.h), SizedBox(height: 12.h),
// //
Text( Text(
'创建账号前的最后一步 · 请选择是否有推荐人', '创建账号前的最后一步 · 请输入推荐码',
style: TextStyle( style: TextStyle(
fontSize: 14.sp, fontSize: 14.sp,
height: 1.43, height: 1.43,
@ -606,122 +577,57 @@ class _WelcomePageContentState extends ConsumerState<_WelcomePageContent> {
); );
} }
/// () /// ()
Widget _buildReferrerOptions() { Widget _buildReferrerOptions() {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// // -
GestureDetector( Container(
onTap: () { padding: EdgeInsets.symmetric(vertical: 12.h, horizontal: 16.w),
setState(() { decoration: BoxDecoration(
_hasReferrer = true; color: Colors.white.withValues(alpha: 0.15),
}); borderRadius: BorderRadius.circular(12.r),
}, border: Border.all(
color: Colors.white.withValues(alpha: 0.3),
width: 1,
),
),
child: Row( child: Row(
children: [ children: [
_buildRadio(_hasReferrer), Expanded(
SizedBox(width: 12.w), child: TextField(
Text( controller: _referralCodeController,
'我有推荐人', decoration: InputDecoration(
style: TextStyle( hintText: '请输入推荐码或点击右侧扫码',
fontSize: 16.sp, hintStyle: TextStyle(
height: 1.5, fontSize: 15.sp,
color: Colors.white, color: Colors.white.withValues(alpha: 0.5),
shadows: [
Shadow(
color: Colors.black.withValues(alpha: 0.5),
blurRadius: 4,
), ),
], border: InputBorder.none,
isDense: true,
contentPadding: EdgeInsets.zero,
),
style: TextStyle(
fontSize: 16.sp,
color: Colors.white,
),
), ),
), ),
], //
), GestureDetector(
), onTap: _openQrScanner,
SizedBox(height: 14.h), child: Container(
// - 使 AnimatedSize padding: EdgeInsets.all(8.w),
AnimatedSize(
duration: const Duration(milliseconds: 200),
curve: Curves.easeInOut,
child: _hasReferrer
? Container(
padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 4.w),
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border( color: Colors.white.withValues(alpha: 0.2),
bottom: BorderSide( borderRadius: BorderRadius.circular(8.r),
width: 1,
color: Colors.white.withValues(alpha: 0.5),
),
),
), ),
child: Row( child: Icon(
children: [ Icons.qr_code_scanner,
Expanded( size: 20.sp,
child: GestureDetector( color: Colors.white,
onTap: _openQrScanner,
child: AbsorbPointer(
child: TextField(
controller: _referralCodeController,
readOnly: true,
decoration: InputDecoration(
hintText: '点击扫描推荐码',
hintStyle: TextStyle(
fontSize: 16.sp,
color: Colors.black.withValues(alpha: 0.4),
),
border: InputBorder.none,
isDense: true,
contentPadding: EdgeInsets.zero,
),
style: TextStyle(
fontSize: 16.sp,
color: Colors.black,
),
),
),
),
),
//
GestureDetector(
onTap: _openQrScanner,
child: Padding(
padding: EdgeInsets.only(left: 8.w),
child: Icon(
Icons.camera_alt_outlined,
size: 20.sp,
color: Colors.white.withValues(alpha: 0.8),
),
),
),
],
), ),
)
: const SizedBox.shrink(),
),
SizedBox(height: 24.h),
//
GestureDetector(
onTap: () {
setState(() {
_hasReferrer = false;
});
},
child: Row(
children: [
_buildRadio(!_hasReferrer),
SizedBox(width: 12.w),
Text(
'我没有推荐人',
style: TextStyle(
fontSize: 16.sp,
height: 1.5,
color: Colors.white,
shadows: [
Shadow(
color: Colors.black.withValues(alpha: 0.5),
blurRadius: 4,
),
],
), ),
), ),
], ],
@ -740,7 +646,7 @@ class _WelcomePageContentState extends ConsumerState<_WelcomePageContent> {
Padding( Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w), padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Text( child: Text(
'', '已有账号',
style: TextStyle( style: TextStyle(
fontSize: 14.sp, fontSize: 14.sp,
color: Colors.white.withValues(alpha: 0.7), color: Colors.white.withValues(alpha: 0.7),
@ -756,9 +662,12 @@ class _WelcomePageContentState extends ConsumerState<_WelcomePageContent> {
], ],
), ),
SizedBox(height: 24.h), SizedBox(height: 24.h),
// // +
GestureDetector( GestureDetector(
onTap: _goToPhoneRegister, onTap: () {
debugPrint('[GuidePage] 跳转到恢复账号页面');
context.push(RoutePaths.phoneLogin);
},
child: Container( child: Container(
width: double.infinity, width: double.infinity,
padding: EdgeInsets.symmetric(vertical: 14.h), padding: EdgeInsets.symmetric(vertical: 14.h),
@ -774,13 +683,13 @@ class _WelcomePageContentState extends ConsumerState<_WelcomePageContent> {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon( Icon(
Icons.phone_android, Icons.login,
size: 20.sp, size: 20.sp,
color: Colors.white, color: Colors.white,
), ),
SizedBox(width: 8.w), SizedBox(width: 8.w),
Text( Text(
'使用手机号注册', '恢复账号(手机号+密码登录)',
style: TextStyle( style: TextStyle(
fontSize: 15.sp, fontSize: 15.sp,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
@ -791,62 +700,49 @@ class _WelcomePageContentState extends ConsumerState<_WelcomePageContent> {
), ),
), ),
), ),
SizedBox(height: 12.h), // -
// if (false) // false true
GestureDetector( Padding(
onTap: _importMnemonic, padding: EdgeInsets.only(top: 12.h),
child: Container( child: GestureDetector(
width: double.infinity, onTap: _importMnemonic,
padding: EdgeInsets.symmetric(vertical: 14.h), child: Container(
decoration: BoxDecoration( width: double.infinity,
color: Colors.white.withValues(alpha: 0.15), padding: EdgeInsets.symmetric(vertical: 14.h),
borderRadius: BorderRadius.circular(12.r), decoration: BoxDecoration(
border: Border.all( color: Colors.white.withValues(alpha: 0.15),
color: Colors.white.withValues(alpha: 0.3), borderRadius: BorderRadius.circular(12.r),
width: 1, border: Border.all(
color: Colors.white.withValues(alpha: 0.3),
width: 1,
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.restore,
size: 20.sp,
color: Colors.white,
),
SizedBox(width: 8.w),
Text(
'已有账号?导入助记词恢复',
style: TextStyle(
fontSize: 15.sp,
fontWeight: FontWeight.w500,
color: Colors.white,
),
),
],
),
), ),
), ),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.restore,
size: 20.sp,
color: Colors.white,
),
SizedBox(width: 8.w),
Text(
'已有账号?导入助记词恢复',
style: TextStyle(
fontSize: 15.sp,
fontWeight: FontWeight.w500,
color: Colors.white,
),
),
],
),
), ),
),
], ],
); );
} }
/// ()
Widget _buildRadio(bool isSelected) {
return Container(
width: 20.w,
height: 20.w,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
width: isSelected ? 6.w : 2.w,
color: isSelected
? Colors.white
: Colors.white.withValues(alpha: 0.5),
),
),
);
}
} }
/// ///

View File

@ -12,6 +12,7 @@ class RoutePaths {
static const importWallet = '/auth/import'; static const importWallet = '/auth/import';
static const importMnemonic = '/auth/import-mnemonic'; static const importMnemonic = '/auth/import-mnemonic';
static const phoneRegister = '/auth/phone-register'; static const phoneRegister = '/auth/phone-register';
static const phoneLogin = '/auth/phone-login';
static const smsVerify = '/auth/sms-verify'; static const smsVerify = '/auth/sms-verify';
static const setPassword = '/auth/set-password'; static const setPassword = '/auth/set-password';