407 lines
13 KiB
Dart
407 lines
13 KiB
Dart
/// ============================================================
|
||
/// 闲言APP — 注册步骤3:设置密码与安全选项
|
||
/// 创建时间: 2026-06-08
|
||
/// 更新时间: 2026-06-12
|
||
/// 作用: 注册流程第三步,设置密码、密保问题、订阅邮件,完成注册
|
||
/// 上次更新: 移除协议勾选(已移至步骤1)
|
||
/// ============================================================
|
||
|
||
import 'package:flutter/cupertino.dart';
|
||
|
||
import '../../../../core/theme/app_theme.dart';
|
||
import '../../../../core/theme/app_spacing.dart';
|
||
import '../../../../core/theme/app_typography.dart';
|
||
import '../../../../core/theme/app_radius.dart';
|
||
import '../../../../l10n/types/t.dart';
|
||
import '../services/user_security_service.dart';
|
||
import 'login_form_sections.dart';
|
||
|
||
/// 注册步骤3 — 设置密码与安全选项
|
||
class RegisterStepPassword extends StatelessWidget {
|
||
const RegisterStepPassword({
|
||
super.key,
|
||
required this.username,
|
||
required this.email,
|
||
required this.passwordController,
|
||
required this.confirmPasswordController,
|
||
required this.secAnswerController,
|
||
required this.obscurePassword,
|
||
required this.onToggleObscure,
|
||
required this.showSecQuestion,
|
||
required this.onToggleSecQuestion,
|
||
required this.secQuestions,
|
||
required this.selectedSecQuestion,
|
||
required this.selectedSecQuestionText,
|
||
required this.onSelectSecQuestion,
|
||
required this.subscribeEmail,
|
||
required this.onToggleSubscribe,
|
||
required this.onPrev,
|
||
required this.onBack,
|
||
required this.onRegister,
|
||
required this.isLoading,
|
||
required this.canRegister,
|
||
required this.ext,
|
||
required this.auth,
|
||
});
|
||
|
||
final String username;
|
||
final String email;
|
||
final TextEditingController passwordController;
|
||
final TextEditingController confirmPasswordController;
|
||
final TextEditingController secAnswerController;
|
||
final bool obscurePassword;
|
||
final VoidCallback onToggleObscure;
|
||
final bool showSecQuestion;
|
||
final VoidCallback onToggleSecQuestion;
|
||
final List<SecQuestionItem> secQuestions;
|
||
final int? selectedSecQuestion;
|
||
final String selectedSecQuestionText;
|
||
final VoidCallback onSelectSecQuestion;
|
||
final bool subscribeEmail;
|
||
final VoidCallback onToggleSubscribe;
|
||
final VoidCallback onPrev;
|
||
final VoidCallback onBack;
|
||
final VoidCallback onRegister;
|
||
final bool isLoading;
|
||
final bool canRegister;
|
||
final AppThemeExtension ext;
|
||
final TAuth auth;
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Column(
|
||
children: [
|
||
// 已填写账号信息(点击可修改)
|
||
GestureDetector(
|
||
onTap: onBack,
|
||
child: Container(
|
||
padding: const EdgeInsets.all(AppSpacing.md),
|
||
decoration: BoxDecoration(
|
||
color: ext.bgSecondary,
|
||
borderRadius: AppRadius.mdBorder,
|
||
border: Border.all(color: ext.bgElevated),
|
||
),
|
||
child: Row(
|
||
children: [
|
||
Icon(
|
||
CupertinoIcons.person_crop_circle_fill,
|
||
size: 32,
|
||
color: ext.accent,
|
||
),
|
||
const SizedBox(width: AppSpacing.sm),
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
username,
|
||
style: AppTypography.subhead.copyWith(
|
||
color: ext.textPrimary,
|
||
fontWeight: FontWeight.w600,
|
||
),
|
||
),
|
||
const SizedBox(height: 2),
|
||
Text(
|
||
email,
|
||
style: AppTypography.caption1.copyWith(
|
||
color: ext.textSecondary,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
Icon(CupertinoIcons.pencil, size: 16, color: ext.textHint),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(height: AppSpacing.md),
|
||
// 密码输入
|
||
LabeledInputField(
|
||
label: auth.setPassword,
|
||
labelIcon: CupertinoIcons.lock,
|
||
controller: passwordController,
|
||
placeholder: auth.passwordHint,
|
||
icon: CupertinoIcons.lock,
|
||
ext: ext,
|
||
obscureText: obscurePassword,
|
||
suffix: GestureDetector(
|
||
behavior: HitTestBehavior.opaque,
|
||
onTap: onToggleObscure,
|
||
child: ConstrainedBox(
|
||
constraints: const BoxConstraints(minWidth: 44, minHeight: 44),
|
||
child: Center(
|
||
child: Icon(
|
||
obscurePassword
|
||
? CupertinoIcons.eye_slash
|
||
: CupertinoIcons.eye,
|
||
size: 18,
|
||
color: ext.textHint,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(height: AppSpacing.md),
|
||
// 确认密码
|
||
LabeledInputField(
|
||
label: auth.confirmPassword,
|
||
labelIcon: CupertinoIcons.lock_shield,
|
||
controller: confirmPasswordController,
|
||
placeholder: auth.confirmPasswordHint,
|
||
icon: CupertinoIcons.lock_shield,
|
||
ext: ext,
|
||
obscureText: true,
|
||
),
|
||
const SizedBox(height: AppSpacing.md),
|
||
// 密保问题折叠区
|
||
_buildSecQuestionToggle(),
|
||
if (showSecQuestion) ...[
|
||
const SizedBox(height: AppSpacing.sm),
|
||
_buildSecQuestionSelector(context),
|
||
if (selectedSecQuestion != null) ...[
|
||
const SizedBox(height: AppSpacing.sm),
|
||
_buildSecAnswerInput(),
|
||
],
|
||
],
|
||
const SizedBox(height: AppSpacing.md),
|
||
// 邮件订阅
|
||
_buildSubscribeRow(),
|
||
const SizedBox(height: AppSpacing.lg),
|
||
// 导航按钮
|
||
_buildNavButtons(),
|
||
],
|
||
);
|
||
}
|
||
|
||
/// 密保问题折叠切换
|
||
Widget _buildSecQuestionToggle() {
|
||
return GestureDetector(
|
||
onTap: onToggleSecQuestion,
|
||
behavior: HitTestBehavior.opaque,
|
||
child: Container(
|
||
padding: const EdgeInsets.symmetric(
|
||
horizontal: AppSpacing.md,
|
||
vertical: AppSpacing.sm,
|
||
),
|
||
decoration: BoxDecoration(
|
||
color: ext.bgSecondary,
|
||
borderRadius: AppRadius.mdBorder,
|
||
border: Border.all(
|
||
color: showSecQuestion
|
||
? ext.accent.withValues(alpha: 0.4)
|
||
: ext.textHint.withValues(alpha: 0.15),
|
||
),
|
||
),
|
||
child: Row(
|
||
children: [
|
||
Icon(
|
||
selectedSecQuestion != null
|
||
? CupertinoIcons.checkmark_shield_fill
|
||
: CupertinoIcons.shield,
|
||
size: 16,
|
||
color: showSecQuestion ? ext.accent : ext.textHint,
|
||
),
|
||
const SizedBox(width: AppSpacing.sm),
|
||
Text(
|
||
auth.secQuestionOptional,
|
||
style: AppTypography.subhead.copyWith(
|
||
color: showSecQuestion ? ext.accent : ext.textSecondary,
|
||
),
|
||
),
|
||
const Spacer(),
|
||
if (selectedSecQuestion != null) ...[
|
||
Icon(
|
||
CupertinoIcons.checkmark_circle_fill,
|
||
size: 14,
|
||
color: ext.accent,
|
||
),
|
||
const SizedBox(width: 4),
|
||
],
|
||
AnimatedRotation(
|
||
duration: const Duration(milliseconds: 200),
|
||
turns: showSecQuestion ? 0.5 : 0,
|
||
child: Icon(
|
||
CupertinoIcons.chevron_down,
|
||
size: 14,
|
||
color: ext.textHint,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 密保问题选择按钮
|
||
Widget _buildSecQuestionSelector(BuildContext context) {
|
||
return CupertinoButton(
|
||
padding: const EdgeInsets.symmetric(
|
||
horizontal: AppSpacing.md,
|
||
vertical: AppSpacing.sm,
|
||
),
|
||
color: ext.bgSecondary,
|
||
borderRadius: AppRadius.mdBorder,
|
||
onPressed: secQuestions.isEmpty ? null : onSelectSecQuestion,
|
||
child: Row(
|
||
children: [
|
||
Icon(
|
||
CupertinoIcons.question_circle,
|
||
size: 16,
|
||
color: ext.textSecondary,
|
||
),
|
||
const SizedBox(width: AppSpacing.sm),
|
||
Expanded(
|
||
child: Text(
|
||
selectedSecQuestionText.isNotEmpty
|
||
? selectedSecQuestionText
|
||
: auth.selectSecQuestion,
|
||
style: AppTypography.subhead.copyWith(
|
||
color: selectedSecQuestionText.isNotEmpty
|
||
? ext.textPrimary
|
||
: ext.textHint,
|
||
),
|
||
),
|
||
),
|
||
Icon(CupertinoIcons.chevron_right, size: 14, color: ext.textHint),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 密保答案输入
|
||
Widget _buildSecAnswerInput() {
|
||
return Container(
|
||
decoration: BoxDecoration(
|
||
color: ext.bgSecondary,
|
||
borderRadius: AppRadius.mdBorder,
|
||
),
|
||
child: CupertinoTextField(
|
||
controller: secAnswerController,
|
||
placeholder: auth.enterSecAnswerHint,
|
||
placeholderStyle: AppTypography.body.copyWith(color: ext.textHint),
|
||
style: AppTypography.body.copyWith(color: ext.textPrimary),
|
||
decoration: null,
|
||
padding: const EdgeInsets.symmetric(
|
||
horizontal: AppSpacing.md,
|
||
vertical: AppSpacing.sm + 2,
|
||
),
|
||
maxLength: 50,
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 邮件订阅行
|
||
Widget _buildSubscribeRow() {
|
||
return GestureDetector(
|
||
onTap: onToggleSubscribe,
|
||
behavior: HitTestBehavior.opaque,
|
||
child: Row(
|
||
children: [
|
||
Container(
|
||
width: 20,
|
||
height: 20,
|
||
decoration: BoxDecoration(
|
||
color: subscribeEmail ? ext.accent : CupertinoColors.transparent,
|
||
borderRadius: BorderRadius.circular(5),
|
||
border: Border.all(
|
||
color: subscribeEmail ? ext.accent : ext.textHint,
|
||
width: 1.5,
|
||
),
|
||
),
|
||
child: subscribeEmail
|
||
? Icon(
|
||
CupertinoIcons.checkmark,
|
||
size: 14,
|
||
color: ext.textInverse,
|
||
)
|
||
: null,
|
||
),
|
||
const SizedBox(width: AppSpacing.sm),
|
||
Text(
|
||
auth.subscribeEmail,
|
||
style: AppTypography.footnote.copyWith(color: ext.textSecondary),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 导航按钮行(上一步 + 完成注册)
|
||
Widget _buildNavButtons() {
|
||
return Row(
|
||
children: [
|
||
// 上一步
|
||
Expanded(
|
||
child: SizedBox(
|
||
height: 50,
|
||
child: CupertinoButton(
|
||
padding: EdgeInsets.zero,
|
||
minimumSize: Size.zero,
|
||
color: ext.bgSecondary,
|
||
borderRadius: AppRadius.mdBorder,
|
||
onPressed: onPrev,
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
const Icon(
|
||
CupertinoIcons.arrow_left,
|
||
size: 16,
|
||
color: CupertinoColors.white,
|
||
),
|
||
const SizedBox(width: 6),
|
||
Text(
|
||
auth.prevStep,
|
||
style: AppTypography.callout.copyWith(
|
||
color: ext.textPrimary,
|
||
fontWeight: FontWeight.w600,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(width: AppSpacing.sm),
|
||
// 完成注册
|
||
Expanded(
|
||
child: SizedBox(
|
||
height: 50,
|
||
child: CupertinoButton(
|
||
padding: EdgeInsets.zero,
|
||
minimumSize: Size.zero,
|
||
color: ext.accent,
|
||
borderRadius: AppRadius.mdBorder,
|
||
onPressed: canRegister ? onRegister : null,
|
||
child: isLoading
|
||
? const CupertinoActivityIndicator(
|
||
color: CupertinoColors.white,
|
||
)
|
||
: Row(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
const Icon(
|
||
CupertinoIcons.checkmark_circle,
|
||
size: 16,
|
||
color: CupertinoColors.white,
|
||
),
|
||
const SizedBox(width: 6),
|
||
Text(
|
||
auth.completeRegister,
|
||
style: AppTypography.callout.copyWith(
|
||
color: CupertinoColors.white,
|
||
fontWeight: FontWeight.w600,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
}
|