Files
xianyan/lib/features/auth/presentation/register_section.dart
Developer ae1df22732 feat: v6.10.3 多语言翻译补全 + 17项功能修复
- 引导页协议多语言支持(languageId传递)
- 登录页双书名号修复 + 注册页协议勾选
- 个人中心页面多语言(18个翻译键)
- 网络断开提示增加关闭/刷新按钮
- 了解我们:新增秋叶qy开发者 + ayk签名修改 + 贡献者精简 + 微风暴微信搜索
- iOS快捷按钮重复修复(删除Info.plist静态定义)
- 测试账号123456警告提示
- 扫码登录自动跳转(HTTP轮询+WebSocket双通道)
- 登录页老用户按钮改次要色
- Syncfusion图表崩溃修复(DeferredBuilder+animationDuration:0)
- macOS标题栏跟随软件夜间模式
- 平台兼容分发渠道弹窗
- 软件著作权图片+交叉水印
- 桌面小部件平台兼容说明默认收起
- iOS/macOS图标更新+名称确认为闲言
- 12个语言文件补全roleNative+7个分发渠道翻译字段
2026-06-02 04:50:32 +08:00

1015 lines
35 KiB
Dart

/// ============================================================
/// 闲言APP — 注册区域组件
/// 创建时间: 2026-05-10
/// 更新时间: 2026-06-01
/// 作用: 登录页面的注册区域,分步式注册流程
/// 上次更新: 接入多语言翻译系统,替换硬编码中文文本
/// ============================================================
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../core/router/app_nav_extension.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 '../../../../core/utils/logger.dart';
import '../../../../l10n/translation_resolver.dart';
import '../../../../l10n/types/t.dart';
import '../../../../shared/widgets/feedback/app_toast.dart';
import '../../../../shared/widgets/containers/glass_container.dart';
import '../providers/auth_provider.dart';
import '../services/email_service.dart';
import '../services/user_security_service.dart';
import 'login_form_sections.dart';
class RegisterSection extends ConsumerStatefulWidget {
const RegisterSection({
super.key,
required this.ext,
required this.onSwitchToLogin,
required this.onRegisterSuccess,
});
final AppThemeExtension ext;
final VoidCallback onSwitchToLogin;
final VoidCallback onRegisterSuccess;
@override
ConsumerState<RegisterSection> createState() => _RegisterSectionState();
}
class _RegisterSectionState extends ConsumerState<RegisterSection> {
int _regStep = 1;
bool _obscureRegPassword = true;
bool _subscribeEmail = false;
bool _agreedToTerms = false;
bool _emsSending = false;
int _emsCountdown = 0;
bool _showSecQuestion = false;
int? _selectedSecQuestion;
String _selectedSecQuestionText = '';
List<SecQuestionItem> _secQuestions = [];
final _usernameController = TextEditingController();
final _emailController = TextEditingController();
final _regPasswordController = TextEditingController();
final _regConfirmPasswordController = TextEditingController();
final _regCodeController = TextEditingController();
final _secAnswerController = TextEditingController();
@override
void initState() {
super.initState();
_usernameController.addListener(() => setState(() {}));
_emailController.addListener(() => setState(() {}));
_regPasswordController.addListener(() => setState(() {}));
_regConfirmPasswordController.addListener(() => setState(() {}));
_regCodeController.addListener(() => setState(() {}));
_secAnswerController.addListener(() => setState(() {}));
_loadSecQuestions();
}
Future<void> _loadSecQuestions() async {
try {
final questions = await UserSecurityService.secQuestions();
if (mounted) {
setState(() => _secQuestions = questions);
}
} catch (e) {
Log.w('加载密保问题列表失败: $e');
}
}
@override
void dispose() {
_usernameController.dispose();
_emailController.dispose();
_regPasswordController.dispose();
_regConfirmPasswordController.dispose();
_regCodeController.dispose();
_secAnswerController.dispose();
super.dispose();
}
AppThemeExtension get ext => widget.ext;
@override
Widget build(BuildContext context) {
final authState = ref.watch(authProvider);
final t = ref.watch(translationsProvider);
final auth = t.auth;
final common = t.common;
return Column(
children: [
_buildHeader(auth),
_buildStepIndicator(),
const SizedBox(height: AppSpacing.md),
GlassContainer(
depth: GlassDepth.elevated,
padding: const EdgeInsets.all(AppSpacing.lg),
child: _buildRegStepContent(authState, auth, common),
),
const SizedBox(height: AppSpacing.md),
CupertinoButton(
onPressed: () {
widget.onSwitchToLogin();
setState(() => _regStep = 1);
},
child: Text(
auth.hasAccountLogin,
style: AppTypography.subhead.copyWith(color: ext.accent),
),
),
],
);
}
Widget _buildHeader(TAuth auth) {
return Padding(
padding: const EdgeInsets.only(bottom: AppSpacing.xl),
child: Row(
children: [
Container(
width: 56,
height: 56,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [ext.accent, ext.accentLight],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: AppRadius.lgBorder,
),
child: Icon(
CupertinoIcons.person_add,
size: 28,
color: ext.textInverse,
),
),
const SizedBox(width: AppSpacing.md),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
auth.createAccount,
style: AppTypography.title2.copyWith(
color: ext.textPrimary,
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 2),
Text(
auth.registerNewAccount,
style: AppTypography.subhead.copyWith(
color: ext.textSecondary,
),
),
],
),
),
],
),
);
}
Widget _buildStepIndicator() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(3, (i) {
final step = i + 1;
final isActive = step == _regStep;
final isDone = step < _regStep;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
width: isActive ? 24 : 8,
height: 8,
decoration: BoxDecoration(
color: isDone
? ext.accent.withValues(alpha: 0.5)
: isActive
? ext.accent
: ext.textHint.withValues(alpha: 0.3),
borderRadius: AppRadius.smBorder,
),
),
);
}),
);
}
Widget _buildRegStepContent(AuthState authState, TAuth auth, TCommon common) {
switch (_regStep) {
case 1:
return Column(
children: [
LabeledInputField(
label: auth.username,
labelIcon: CupertinoIcons.person_add,
controller: _usernameController,
placeholder: auth.usernameHint,
icon: CupertinoIcons.person_add,
ext: ext,
),
const SizedBox(height: AppSpacing.md),
LabeledInputField(
label: auth.emailRequired,
labelIcon: CupertinoIcons.mail,
controller: _emailController,
placeholder: auth.emailHint,
icon: CupertinoIcons.mail,
ext: ext,
keyboardType: TextInputType.emailAddress,
),
const SizedBox(height: AppSpacing.lg),
SizedBox(
width: double.infinity,
height: 50,
child: CupertinoButton(
padding: EdgeInsets.zero,
minimumSize: Size.zero,
color: ext.accent,
borderRadius: AppRadius.mdBorder,
onPressed:
_usernameController.text.trim().isNotEmpty &&
_emailController.text.trim().isNotEmpty
? () => setState(() => _regStep = 2)
: null,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
CupertinoIcons.arrow_right,
size: 16,
color: CupertinoColors.white,
),
const SizedBox(width: 6),
Text(
auth.nextStep,
style: AppTypography.callout.copyWith(
color: CupertinoColors.white,
fontWeight: FontWeight.w600,
),
),
],
),
),
),
],
);
case 2:
return Column(
children: [
Padding(
padding: const EdgeInsets.only(bottom: AppSpacing.md),
child: Column(
children: [
Icon(CupertinoIcons.mail, size: 36, color: ext.accent),
const SizedBox(height: AppSpacing.xs),
Text(
'${auth.codeSentTo} ${_emailController.text.trim()}',
style: AppTypography.footnote.copyWith(
color: ext.textSecondary,
),
textAlign: TextAlign.center,
),
],
),
),
LabeledInputField(
label: auth.verifyCode,
labelIcon: CupertinoIcons.number,
controller: _regCodeController,
placeholder: auth.enterCodeHint,
icon: CupertinoIcons.number,
ext: ext,
keyboardType: TextInputType.number,
suffix: SizedBox(
width: 90,
child: CupertinoButton(
color: ext.accent.withValues(alpha: 0.15),
borderRadius: AppRadius.mdBorder,
padding: EdgeInsets.zero,
onPressed: (_emsSending || _emsCountdown > 0)
? null
: () => _sendCode(_emailController.text.trim()),
child: _emsSending
? CupertinoActivityIndicator(radius: 6, color: ext.accent)
: Text(
_emsCountdown > 0 ? '${_emsCountdown}s' : auth.resend,
style: AppTypography.caption1.copyWith(
color: ext.accent,
),
),
),
),
),
const SizedBox(height: AppSpacing.sm),
Container(
padding: const EdgeInsets.all(AppSpacing.sm),
decoration: BoxDecoration(
color: ext.accent.withValues(alpha: 0.08),
borderRadius: AppRadius.mdBorder,
border: Border.all(
color: ext.accent.withValues(alpha: 0.2),
width: 0.5,
),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(CupertinoIcons.info_circle, size: 16, color: ext.accent),
const SizedBox(width: AppSpacing.sm),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Tips',
style: AppTypography.caption1.copyWith(
color: ext.accent,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 2),
Text(
auth.codeNotReceivedTip,
style: AppTypography.caption2.copyWith(
color: ext.textSecondary,
),
),
],
),
),
],
),
),
const SizedBox(height: AppSpacing.lg),
Row(
children: [
Expanded(
child: SizedBox(
height: 50,
child: CupertinoButton(
padding: EdgeInsets.zero,
minimumSize: Size.zero,
color: ext.bgSecondary,
borderRadius: AppRadius.mdBorder,
onPressed: () => setState(() => _regStep = 1),
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: _regCodeController.text.trim().isNotEmpty
? () => setState(() => _regStep = 3)
: null,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
CupertinoIcons.checkmark_shield,
size: 16,
color: CupertinoColors.white,
),
const SizedBox(width: 6),
Text(
auth.nextStep,
style: AppTypography.callout.copyWith(
color: CupertinoColors.white,
fontWeight: FontWeight.w600,
),
),
],
),
),
),
),
],
),
],
);
case 3:
return Column(
children: [
LabeledInputField(
label: auth.setPassword,
labelIcon: CupertinoIcons.lock,
controller: _regPasswordController,
placeholder: auth.passwordHint,
icon: CupertinoIcons.lock,
ext: ext,
obscureText: _obscureRegPassword,
suffix: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () =>
setState(() => _obscureRegPassword = !_obscureRegPassword),
child: ConstrainedBox(
constraints: const BoxConstraints(
minWidth: 44,
minHeight: 44,
),
child: Center(
child: Icon(
_obscureRegPassword
? CupertinoIcons.eye_slash
: CupertinoIcons.eye,
size: 18,
color: ext.textHint,
),
),
),
),
),
const SizedBox(height: AppSpacing.md),
LabeledInputField(
label: auth.confirmPassword,
labelIcon: CupertinoIcons.lock_shield,
controller: _regConfirmPasswordController,
placeholder: auth.confirmPasswordHint,
icon: CupertinoIcons.lock_shield,
ext: ext,
obscureText: true,
),
const SizedBox(height: AppSpacing.md),
GestureDetector(
onTap: () => setState(() => _showSecQuestion = !_showSecQuestion),
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(
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(),
Text(
_selectedSecQuestionText.isNotEmpty
? auth.selected
: auth.enhanceSecurity,
style: AppTypography.caption1.copyWith(
color: ext.textHint,
),
),
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,
),
),
],
),
),
),
if (_showSecQuestion) ...[
const SizedBox(height: AppSpacing.sm),
CupertinoButton(
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.md,
vertical: AppSpacing.sm,
),
color: ext.bgSecondary,
borderRadius: AppRadius.mdBorder,
onPressed: _secQuestions.isEmpty
? null
: () => _showSecQuestionPicker(context, auth, common),
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,
),
],
),
),
if (_selectedSecQuestion != null) ...[
const SizedBox(height: AppSpacing.sm),
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,
),
),
],
],
const SizedBox(height: AppSpacing.md),
GestureDetector(
onTap: () => setState(() => _subscribeEmail = !_subscribeEmail),
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,
),
),
],
),
),
const SizedBox(height: AppSpacing.lg),
_buildAgreementRow(ext, auth),
const SizedBox(height: AppSpacing.md),
Row(
children: [
Expanded(
child: SizedBox(
height: 50,
child: CupertinoButton(
padding: EdgeInsets.zero,
minimumSize: Size.zero,
color: ext.bgSecondary,
borderRadius: AppRadius.mdBorder,
onPressed: () => setState(() => _regStep = 2),
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:
(_agreedToTerms &&
_regPasswordController.text.isNotEmpty &&
_regConfirmPasswordController.text.isNotEmpty &&
!authState.isLoading)
? _handleRegister
: null,
child: authState.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,
),
),
],
),
),
),
),
],
),
],
);
default:
return const SizedBox.shrink();
}
}
Widget _buildAgreementRow(AppThemeExtension ext, TAuth auth) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => setState(() => _agreedToTerms = !_agreedToTerms),
child: ConstrainedBox(
constraints: const BoxConstraints(minWidth: 44, minHeight: 44),
child: Center(
child: Container(
width: 20,
height: 20,
margin: const EdgeInsets.only(top: 1),
decoration: BoxDecoration(
color: _agreedToTerms ? ext.accent : Colors.transparent,
borderRadius: BorderRadius.circular(5),
border: Border.all(
color: _agreedToTerms ? ext.accent : ext.textHint,
width: 1.5,
),
),
child: _agreedToTerms
? Icon(
CupertinoIcons.checkmark,
size: 13,
color: ext.textOnAccent,
)
: null,
),
),
),
),
const SizedBox(width: AppSpacing.sm),
Expanded(
child: Text.rich(
TextSpan(
text: auth.registerAgreePrefix,
style: AppTypography.footnote.copyWith(
color: ext.textSecondary,
),
children: [
WidgetSpan(
child: GestureDetector(
onTap: () => _showAgreement(true),
child: Text(
auth.userAgreement.startsWith('')
? auth.userAgreement
: '${auth.userAgreement}',
style: AppTypography.footnote.copyWith(
color: ext.accent,
fontWeight: FontWeight.w600,
),
),
),
),
TextSpan(
text: auth.and,
style: AppTypography.footnote.copyWith(
color: ext.textSecondary,
),
),
WidgetSpan(
child: GestureDetector(
onTap: () => _showAgreement(false),
child: Text(
auth.privacyPolicy.startsWith('')
? auth.privacyPolicy
: '${auth.privacyPolicy}',
style: AppTypography.footnote.copyWith(
color: ext.accent,
fontWeight: FontWeight.w600,
),
),
),
),
],
),
),
),
],
);
}
void _showAgreement(bool isUserAgreement) {
if (isUserAgreement) {
context.appPush('/agreement/user-service-agreement');
} else {
context.appPush('/agreement/privacy-policy');
}
}
Future<void> _handleRegister() async {
final t = ref.read(translationsProvider);
final username = _usernameController.text.trim();
final password = _regPasswordController.text;
final confirmPassword = _regConfirmPasswordController.text;
final email = _emailController.text.trim();
final code = _regCodeController.text.trim();
if (username.isEmpty || password.isEmpty || email.isEmpty) {
AppToast.showWarning(t.auth.pleaseFillRequired);
return;
}
if (password != confirmPassword) {
AppToast.showError(t.auth.passwordMismatch);
return;
}
if (password.length < 6) {
AppToast.showWarning(t.auth.passwordTooShort);
return;
}
if (code.isNotEmpty && !EmailService.verifyCode(email: email, code: code)) {
AppToast.showError(t.auth.codeError);
return;
}
final success = await ref
.read(authProvider.notifier)
.register(
username: username,
password: password,
email: email,
secQuestion: _selectedSecQuestion,
secAnswer: _secAnswerController.text.trim().isNotEmpty
? _secAnswerController.text.trim()
: null,
);
if (success && mounted) {
AppToast.showSuccess(t.auth.registerSuccess);
widget.onRegisterSuccess();
}
}
Future<void> _sendCode(String email) async {
final t = ref.read(translationsProvider);
if (_emsSending || _emsCountdown > 0 || email.isEmpty) return;
if (!email.contains('@')) {
AppToast.showWarning(t.auth.pleaseEnterValidEmail);
return;
}
setState(() {
_emsSending = true;
_emsCountdown = 60;
});
_startCountdown();
try {
final code = EmailService.generateCode();
final ok = await EmailService.sendVerificationCode(
toEmail: email,
code: code,
);
if (!ok && mounted) {
AppToast.showError(t.auth.codeSendFailed);
setState(() {
_emsSending = false;
_emsCountdown = 0;
});
} else if (mounted) {
AppToast.showSuccess(t.auth.codeSent);
setState(() => _emsSending = false);
}
} catch (e) {
Log.e('发送验证码失败', e);
if (mounted) {
final errorMsg = e.toString();
if (errorMsg.contains('已注册') ||
errorMsg.contains('already') ||
errorMsg.contains('exists')) {
_showEmailRegisteredDialog(email);
} else {
AppToast.showError(t.auth.codeSendFailedShort);
}
setState(() {
_emsSending = false;
_emsCountdown = 0;
});
}
}
}
void _startCountdown() {
Future.doWhile(() async {
await Future<void>.delayed(const Duration(seconds: 1));
if (!mounted) return false;
setState(() => _emsCountdown--);
return _emsCountdown > 0;
});
}
void _showEmailRegisteredDialog(String email) {
final t = ref.read(translationsProvider);
showCupertinoDialog<void>(
context: context,
builder: (ctx) => CupertinoAlertDialog(
title: Text(t.auth.emailRegistered),
content: Text(t.auth.emailRegisteredHint.replaceAll('{email}', email)),
actions: [
CupertinoDialogAction(
child: Text(t.common.cancel),
onPressed: () => Navigator.pop(ctx),
),
CupertinoDialogAction(
isDefaultAction: true,
child: Text(t.auth.goLogin),
onPressed: () {
Navigator.pop(ctx);
widget.onSwitchToLogin();
},
),
],
),
);
}
void _showSecQuestionPicker(
BuildContext context,
TAuth auth,
TCommon common,
) {
showCupertinoModalPopup<void>(
context: context,
builder: (ctx) => Container(
height: 260,
color: ext.bgElevated.withValues(alpha: 0.95),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
CupertinoButton(
child: Text(
common.cancel,
style: AppTypography.subhead.copyWith(
color: ext.textSecondary,
),
),
onPressed: () => Navigator.pop(ctx),
),
Text(
auth.selectSecQuestion,
style: AppTypography.headline.copyWith(
color: ext.textPrimary,
fontWeight: FontWeight.w600,
),
),
CupertinoButton(
child: Text(
common.confirm,
style: AppTypography.subhead.copyWith(
color: ext.accent,
fontWeight: FontWeight.w600,
),
),
onPressed: () => Navigator.pop(ctx),
),
],
),
Expanded(
child: CupertinoPicker(
itemExtent: 36,
scrollController: FixedExtentScrollController(
initialItem: _selectedSecQuestion != null
? _secQuestions.indexWhere(
(q) => q.id == _selectedSecQuestion,
)
: 0,
),
onSelectedItemChanged: (index) {
if (index < _secQuestions.length) {
setState(() {
_selectedSecQuestion = _secQuestions[index].id;
_selectedSecQuestionText = _secQuestions[index].question;
});
}
},
children: _secQuestions.map((q) {
return Center(
child: Text(
q.question,
style: AppTypography.subhead.copyWith(
color: ext.textPrimary,
),
),
);
}).toList(),
),
),
],
),
),
);
}
}