import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../core/utils/common_utils.dart'; import '../../../core/utils/toast_utils.dart'; import '../../../core/utils/logger.dart'; import '../../../l10n/app_localizations.dart'; import '../../../module/common_input_field.dart'; import '../../auth/domain/login_service.dart'; import '../data/change_password_provider.dart'; import '../domain/change_password_service.dart'; class ChangePasswordPage extends ConsumerStatefulWidget { const ChangePasswordPage({super.key}); @override ConsumerState createState() => _ChangePasswordPageState(); } class _ChangePasswordPageState extends ConsumerState { Timer? _smsTimer; // 验证密码一致性(回车键/Tab键触发) void _validatePasswordMatch() { final password = ref.read(changePwdPasswordProvider); final confirmPassword = ref.read(changePwdConfirmPasswordProvider); ref.read(changePwdShowPasswordErrorProvider.notifier).state = password.isNotEmpty && confirmPassword.isNotEmpty && password != confirmPassword; } void startSmsCountdown() { ref.read(changePwdSmsCountdownProvider.notifier).state = 60; _smsTimer?.cancel(); _smsTimer = Timer.periodic(const Duration(seconds: 1), (timer) { final value = ref.read(changePwdSmsCountdownProvider); if (value <= 1) { timer.cancel(); ref.read(changePwdSmsCountdownProvider.notifier).state = 0; } else { ref.read(changePwdSmsCountdownProvider.notifier).state = value - 1; } }); } void submit(BuildContext context) async { final l10n = AppLocalizations.of(context)!; final phone = ref.read(changePwdPhoneProvider); final smsCode = ref.read(changePwdServerSmsCodeProvider); final password = ref.read(changePwdPasswordProvider); final confirmPassword = ref.read(changePwdConfirmPasswordProvider); final error = validateForm( context: context, phone: phone, code: smsCode, password: password, confirmPassword: confirmPassword, ); if (error != null) { ToastUtils.showError(error); return; } // 调用重置密码接口 try { final response = await ChangePasswordService.changePassword( mobile: phone, oldPassword: password, newPassword: confirmPassword, smsCode: smsCode, ); if (response.success && response.code == 20000) { AppLogger.d('修改密码成功'); ToastUtils.showSuccess(l10n.changePasswordSuccess); if (mounted) { Navigator.of(context).pop(); } } else { final errorMsg = response.msg ?? l10n.changePasswordFailed; AppLogger.e(errorMsg); ToastUtils.showError(errorMsg); } } catch (e) { AppLogger.e('修改密码错误', e); ToastUtils.showError(l10n.changePasswordFailed); } } @override void dispose() { _smsTimer?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { final phone = ref.watch(changePwdPhoneProvider); final smsCode = ref.watch(changePwdServerSmsCodeProvider); final password = ref.watch(changePwdPasswordProvider); final confirmPassword = ref.watch(changePwdConfirmPasswordProvider); final pwdVisible = ref.watch(changePwdPasswordVisibleProvider); final confirmPwdVisible = ref.watch(changePwdConfirmPasswordVisibleProvider); final countdown = ref.watch(changePwdSmsCountdownProvider); final smsHasSent = ref.watch(changePwdSmsHasSentProvider); final showPasswordError = ref.watch(changePwdShowPasswordErrorProvider); final canSubmit = validateForm( context: context, phone: phone, code: smsCode, password: password, confirmPassword: confirmPassword) == null; final l10n = AppLocalizations.of(context)!; return Dialog( insetPadding: const EdgeInsets.all(24), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), child: Padding( padding: const EdgeInsets.all(20), child: Column( mainAxisSize: MainAxisSize.min, children: [ /// 标题 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( l10n.forgotPassword, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), IconButton( icon: const Icon(Icons.close), onPressed: () => Navigator.of(context).pop(), ) ], ), const SizedBox(height: 16), /// 手机号 CommonInputField( label: l10n.phoneNumber, required: true, value: phone, hintText: l10n.phoneNumberRequired, keyboardType: TextInputType.phone, onChanged: (v) => ref.read(changePwdPhoneProvider.notifier).state = v, ), const SizedBox(height: 16), /// 验证码 CommonInputField( label: l10n.smsCode, required: true, value: smsCode, hintText: l10n.smsCodeRequired, keyboardType: TextInputType.number, onChanged: (v) => ref.read(changePwdServerSmsCodeProvider.notifier).state = v, suffix: TextButton( onPressed: countdown > 0 ? null : () async { // 校验手机号 if (phone.isEmpty) { ToastUtils.showError(l10n.phoneNumberRequired); return; } if (!RegExp(r'^1\d{10}$').hasMatch(phone)) { ToastUtils.showError(l10n.phoneNumberInvalid); return; } // 发送验证码 try { AppLogger.d('修改密码 - 发送验证码,手机号: $phone'); final smsCode = await LoginService.sendSmsCode( mobile: phone, scope: 'yun-his-forget-password-sms-send', ); // 保存服务器返回的验证码 ref.read(changePwdServerSmsCodeProvider.notifier).state = smsCode; AppLogger.d('验证码已保存: $smsCode'); ToastUtils.showSuccess(l10n.smsCodeSent); ref.read(changePwdSmsHasSentProvider.notifier).state = true; startSmsCountdown(); } catch (e) { AppLogger.e('发送验证码失败', e); ToastUtils.showError(e.toString().replaceAll('Exception: ', '')); } }, child: Text( countdown > 0 ? '${countdown}s' : (smsHasSent ? l10n.resendSmsCode : l10n.getSmsCode), ), ), ), const SizedBox(height: 16), /// 新密码 CommonInputField( label: l10n.newPassword, required: true, value: password, hintText: l10n.newPasswordRequired, obscureText: !pwdVisible, onChanged: (v) => ref.read(changePwdPasswordProvider.notifier).state = v, suffix: IconButton( icon: Icon( pwdVisible ? Icons.visibility : Icons.visibility_off), onPressed: () => ref .read(changePwdPasswordVisibleProvider.notifier) .state = !pwdVisible, ), ), const SizedBox(height: 6), Align( alignment: Alignment.centerLeft, child: Text( l10n.passwordFormatError, style: TextStyle(fontSize: 12, color: Colors.grey), ), ), const SizedBox(height: 16), /// 确认密码 CommonInputField( label: l10n.confirmPassword, required: true, value: confirmPassword, hintText: l10n.confirmPasswordRequired, obscureText: !confirmPwdVisible, onChanged: (v) { ref.read(changePwdConfirmPasswordProvider.notifier).state = v; // 用户正在输入,清除错误提示 ref.read(changePwdShowPasswordErrorProvider.notifier).state = false; }, onFieldSubmitted: (v) { // 回车键或 Tab 键时验证密码一致性 _validatePasswordMatch(); }, suffix: IconButton( icon: Icon(confirmPwdVisible ? Icons.visibility : Icons.visibility_off), onPressed: () => ref .read(changePwdConfirmPasswordVisibleProvider.notifier) .state = !confirmPwdVisible, ), ), // 显示密码不一致错误提示(仅在回车/Tab时显示) if (showPasswordError) Padding( padding: const EdgeInsets.only(top: 8.0, left: 4.0), child: Text( l10n.confirmPasswordError, style: const TextStyle( color: Colors.red, fontSize: 12, ), ), ), const SizedBox(height: 24), /// 按钮 Row( mainAxisAlignment: MainAxisAlignment.end, children: [ OutlinedButton( onPressed: () => Navigator.of(context).pop(), child: Text(l10n.cancel), ), const SizedBox(width: 12), ElevatedButton( onPressed: canSubmit ? () => submit(context) : null, child: Text(l10n.confirm), ), ], ) ], ), ), ); } }