feat(auth): transition from email to phone-based OTP authentication
- Replace Email+Password login with Phone+OTP flow - Remove RegisterCubit and registration screens (email verification) - Remove ResetPasswordCubit and reset password screens - Add phone normalization and international dial code support - Update LoginCubit with sendCode/resend cooldown logic - Add new widgets: phone prefix selector, confirm sheet - Update all auth API endpoints: /otp/send, /phone-session - Update form inputs: Email -> Phone with E.164 validation - Update tests for new auth flow
This commit is contained in:
@@ -1,56 +1,161 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:formz/formz.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import '../../../../core/api/api_exception.dart';
|
||||
import '../../data/auth_repository.dart';
|
||||
import '../../data/models/login_request.dart';
|
||||
import '../../data/models/auth_response.dart';
|
||||
import '../../../../core/form_inputs/form_inputs.dart';
|
||||
|
||||
class LoginState extends Equatable {
|
||||
final Email email;
|
||||
final Password password;
|
||||
static const defaultDialCode = '+86';
|
||||
static final _e164Regex = RegExp(r'^\+[1-9]\d{7,14}$');
|
||||
|
||||
final String dialCode;
|
||||
final Phone phone;
|
||||
final VerificationCode code;
|
||||
final bool codeSent;
|
||||
final bool isSendingCode;
|
||||
final int resendCooldownSeconds;
|
||||
final FormzSubmissionStatus status;
|
||||
final String? errorMessage;
|
||||
|
||||
const LoginState({
|
||||
this.email = const Email.pure(),
|
||||
this.password = const Password.pure(),
|
||||
this.dialCode = defaultDialCode,
|
||||
this.phone = const Phone.pure(),
|
||||
this.code = const VerificationCode.pure(),
|
||||
this.codeSent = false,
|
||||
this.isSendingCode = false,
|
||||
this.resendCooldownSeconds = 0,
|
||||
this.status = FormzSubmissionStatus.initial,
|
||||
this.errorMessage,
|
||||
});
|
||||
|
||||
bool get isValid => email.isValid && password.isValid;
|
||||
bool get isPhoneValidForApi =>
|
||||
phone.isValid && _e164Regex.hasMatch(e164Phone);
|
||||
bool get isValid => isPhoneValidForApi && code.isValid;
|
||||
String get e164Phone => '$dialCode${phone.value}';
|
||||
bool get canSendCode =>
|
||||
isPhoneValidForApi && !isSendingCode && resendCooldownSeconds == 0;
|
||||
|
||||
LoginState copyWith({
|
||||
Email? email,
|
||||
Password? password,
|
||||
String? dialCode,
|
||||
Phone? phone,
|
||||
VerificationCode? code,
|
||||
bool? codeSent,
|
||||
bool? isSendingCode,
|
||||
int? resendCooldownSeconds,
|
||||
FormzSubmissionStatus? status,
|
||||
String? errorMessage,
|
||||
}) {
|
||||
return LoginState(
|
||||
email: email ?? this.email,
|
||||
password: password ?? this.password,
|
||||
dialCode: dialCode ?? this.dialCode,
|
||||
phone: phone ?? this.phone,
|
||||
code: code ?? this.code,
|
||||
codeSent: codeSent ?? this.codeSent,
|
||||
isSendingCode: isSendingCode ?? this.isSendingCode,
|
||||
resendCooldownSeconds:
|
||||
resendCooldownSeconds ?? this.resendCooldownSeconds,
|
||||
status: status ?? this.status,
|
||||
errorMessage: errorMessage,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [email, password, status, errorMessage];
|
||||
List<Object?> get props => [
|
||||
dialCode,
|
||||
phone,
|
||||
code,
|
||||
codeSent,
|
||||
isSendingCode,
|
||||
resendCooldownSeconds,
|
||||
status,
|
||||
errorMessage,
|
||||
];
|
||||
}
|
||||
|
||||
class LoginCubit extends Cubit<LoginState> {
|
||||
final AuthRepository _repository;
|
||||
Timer? _resendTimer;
|
||||
|
||||
LoginCubit(this._repository) : super(const LoginState());
|
||||
|
||||
void emailChanged(String value) {
|
||||
emit(state.copyWith(email: Email.dirty(value)));
|
||||
void phoneChanged(String value) {
|
||||
final nextPhone = Phone.dirty(value);
|
||||
if (nextPhone.value == state.phone.value) {
|
||||
emit(state.copyWith(phone: nextPhone, errorMessage: null));
|
||||
return;
|
||||
}
|
||||
_resendTimer?.cancel();
|
||||
emit(
|
||||
state.copyWith(
|
||||
phone: nextPhone,
|
||||
code: const VerificationCode.pure(),
|
||||
codeSent: false,
|
||||
resendCooldownSeconds: 0,
|
||||
errorMessage: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void passwordChanged(String value) {
|
||||
emit(state.copyWith(password: Password.dirty(value)));
|
||||
void dialCodeChanged(String value) {
|
||||
if (value == state.dialCode) {
|
||||
return;
|
||||
}
|
||||
_resendTimer?.cancel();
|
||||
emit(
|
||||
state.copyWith(
|
||||
dialCode: value,
|
||||
code: const VerificationCode.pure(),
|
||||
codeSent: false,
|
||||
resendCooldownSeconds: 0,
|
||||
errorMessage: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void codeChanged(String value) {
|
||||
emit(state.copyWith(code: VerificationCode.dirty(value)));
|
||||
}
|
||||
|
||||
Future<bool> sendCode() async {
|
||||
if (!state.phone.isValid) {
|
||||
emit(state.copyWith(errorMessage: '请输入有效手机号'));
|
||||
return false;
|
||||
}
|
||||
if (!state.canSendCode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final requestPhone = state.e164Phone;
|
||||
emit(state.copyWith(isSendingCode: true, errorMessage: null));
|
||||
try {
|
||||
await _repository.sendOtp(requestPhone);
|
||||
if (isClosed) {
|
||||
return false;
|
||||
}
|
||||
if (state.e164Phone != requestPhone) {
|
||||
emit(state.copyWith(isSendingCode: false));
|
||||
return false;
|
||||
}
|
||||
emit(
|
||||
state.copyWith(
|
||||
isSendingCode: false,
|
||||
codeSent: true,
|
||||
errorMessage: null,
|
||||
),
|
||||
);
|
||||
_startResendCooldown();
|
||||
return true;
|
||||
} catch (e) {
|
||||
if (isClosed) {
|
||||
return false;
|
||||
}
|
||||
final message = e is ApiException ? e.message : '验证码发送失败';
|
||||
emit(state.copyWith(isSendingCode: false, errorMessage: message));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<AuthResponse?> submit() async {
|
||||
@@ -59,12 +164,19 @@ class LoginCubit extends Cubit<LoginState> {
|
||||
emit(state.copyWith(status: FormzSubmissionStatus.inProgress));
|
||||
|
||||
try {
|
||||
final response = await _repository.createSession(
|
||||
LoginRequest(email: state.email.value, password: state.password.value),
|
||||
final response = await _repository.createPhoneSession(
|
||||
phone: state.e164Phone,
|
||||
token: state.code.value,
|
||||
);
|
||||
if (isClosed) {
|
||||
return null;
|
||||
}
|
||||
emit(state.copyWith(status: FormzSubmissionStatus.success));
|
||||
return response;
|
||||
} catch (e) {
|
||||
if (isClosed) {
|
||||
return null;
|
||||
}
|
||||
final message = e is ApiException ? e.message : e.toString();
|
||||
emit(
|
||||
state.copyWith(
|
||||
@@ -75,4 +187,29 @@ class LoginCubit extends Cubit<LoginState> {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
void _startResendCooldown() {
|
||||
_resendTimer?.cancel();
|
||||
emit(state.copyWith(resendCooldownSeconds: 60));
|
||||
_resendTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||
if (isClosed) {
|
||||
timer.cancel();
|
||||
return;
|
||||
}
|
||||
if (state.resendCooldownSeconds <= 1) {
|
||||
timer.cancel();
|
||||
emit(state.copyWith(resendCooldownSeconds: 0));
|
||||
return;
|
||||
}
|
||||
emit(
|
||||
state.copyWith(resendCooldownSeconds: state.resendCooldownSeconds - 1),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_resendTimer?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,238 +0,0 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:formz/formz.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import '../../../../core/api/api_exception.dart';
|
||||
import '../../../../core/form_inputs/form_inputs.dart';
|
||||
import '../../data/auth_repository.dart';
|
||||
import '../../data/models/signup_request.dart';
|
||||
import '../../data/models/auth_response.dart';
|
||||
|
||||
class RegisterState extends Equatable {
|
||||
final Username username;
|
||||
final Email email;
|
||||
final Password password;
|
||||
final VerificationCode verificationCode;
|
||||
final String inviteCode;
|
||||
final FormzSubmissionStatus status;
|
||||
final String? errorMessage;
|
||||
final String? pendingEmail;
|
||||
final bool codeSent;
|
||||
final bool isSending;
|
||||
|
||||
const RegisterState({
|
||||
this.username = const Username.pure(),
|
||||
this.email = const Email.pure(),
|
||||
this.password = const Password.pure(),
|
||||
this.verificationCode = const VerificationCode.pure(),
|
||||
this.inviteCode = '',
|
||||
this.status = FormzSubmissionStatus.initial,
|
||||
this.errorMessage,
|
||||
this.pendingEmail,
|
||||
this.codeSent = false,
|
||||
this.isSending = false,
|
||||
});
|
||||
|
||||
bool get isStep1Valid =>
|
||||
username.isValid && email.isValid && password.isValid;
|
||||
bool get isStep2Valid => verificationCode.isValid;
|
||||
|
||||
RegisterState copyWith({
|
||||
Username? username,
|
||||
Email? email,
|
||||
Password? password,
|
||||
VerificationCode? verificationCode,
|
||||
String? inviteCode,
|
||||
FormzSubmissionStatus? status,
|
||||
String? errorMessage,
|
||||
String? pendingEmail,
|
||||
bool? codeSent,
|
||||
bool? isSending,
|
||||
}) {
|
||||
return RegisterState(
|
||||
username: username ?? this.username,
|
||||
email: email ?? this.email,
|
||||
password: password ?? this.password,
|
||||
verificationCode: verificationCode ?? this.verificationCode,
|
||||
inviteCode: inviteCode ?? this.inviteCode,
|
||||
status: status ?? this.status,
|
||||
errorMessage: errorMessage,
|
||||
pendingEmail: pendingEmail ?? this.pendingEmail,
|
||||
codeSent: codeSent ?? this.codeSent,
|
||||
isSending: isSending ?? this.isSending,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
verificationCode,
|
||||
inviteCode,
|
||||
status,
|
||||
errorMessage,
|
||||
pendingEmail,
|
||||
codeSent,
|
||||
isSending,
|
||||
];
|
||||
}
|
||||
|
||||
class RegisterCubit extends Cubit<RegisterState> {
|
||||
final AuthRepository _repository;
|
||||
|
||||
RegisterCubit(this._repository) : super(const RegisterState());
|
||||
|
||||
void usernameChanged(String value) {
|
||||
emit(state.copyWith(username: Username.dirty(value)));
|
||||
}
|
||||
|
||||
void emailChanged(String value) {
|
||||
emit(state.copyWith(email: Email.dirty(value)));
|
||||
}
|
||||
|
||||
void passwordChanged(String value) {
|
||||
emit(state.copyWith(password: Password.dirty(value)));
|
||||
}
|
||||
|
||||
void verificationCodeChanged(String value) {
|
||||
emit(state.copyWith(verificationCode: VerificationCode.dirty(value)));
|
||||
}
|
||||
|
||||
void inviteCodeChanged(String value) {
|
||||
emit(state.copyWith(inviteCode: value));
|
||||
}
|
||||
|
||||
Future<bool> submitStep1() async {
|
||||
if (!state.isStep1Valid) return false;
|
||||
|
||||
emit(state.copyWith(status: FormzSubmissionStatus.inProgress));
|
||||
|
||||
try {
|
||||
final response = await _repository.createVerification(
|
||||
SignupStartRequest(
|
||||
username: state.username.value,
|
||||
email: state.email.value,
|
||||
password: state.password.value,
|
||||
inviteCode: state.inviteCode.isNotEmpty ? state.inviteCode : null,
|
||||
),
|
||||
);
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: FormzSubmissionStatus.success,
|
||||
pendingEmail: response.email,
|
||||
codeSent: true,
|
||||
),
|
||||
);
|
||||
return true;
|
||||
} catch (e) {
|
||||
final message = e is ApiException ? e.message : e.toString();
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: FormzSubmissionStatus.failure,
|
||||
errorMessage: message,
|
||||
),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<AuthResponse?> submitStep2() async {
|
||||
if (!state.isStep2Valid || state.pendingEmail == null) return null;
|
||||
|
||||
emit(state.copyWith(status: FormzSubmissionStatus.inProgress));
|
||||
|
||||
try {
|
||||
final response = await _repository.verifyVerification(
|
||||
SignupVerifyRequest(
|
||||
email: state.pendingEmail!,
|
||||
token: state.verificationCode.value,
|
||||
),
|
||||
);
|
||||
emit(state.copyWith(status: FormzSubmissionStatus.success));
|
||||
return response;
|
||||
} catch (e) {
|
||||
final message = e is ApiException ? e.message : e.toString();
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: FormzSubmissionStatus.failure,
|
||||
errorMessage: message,
|
||||
),
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> resendCode() async {
|
||||
if (state.pendingEmail == null) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: FormzSubmissionStatus.failure,
|
||||
errorMessage: '验证码发送失败,请返回上一步重试',
|
||||
),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
emit(state.copyWith(status: FormzSubmissionStatus.inProgress));
|
||||
|
||||
try {
|
||||
await _repository.resendVerification(
|
||||
SignupResendRequest(email: state.pendingEmail!),
|
||||
);
|
||||
emit(
|
||||
state.copyWith(status: FormzSubmissionStatus.success, codeSent: true),
|
||||
);
|
||||
return true;
|
||||
} catch (e) {
|
||||
final message = e is ApiException ? e.message : '验证码发送失败,请重试';
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: FormzSubmissionStatus.failure,
|
||||
errorMessage: message,
|
||||
),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> sendCodeSilently() async {
|
||||
if (!state.isStep1Valid || state.isSending) return;
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
isSending: true,
|
||||
status: FormzSubmissionStatus.inProgress,
|
||||
errorMessage: null,
|
||||
),
|
||||
);
|
||||
|
||||
try {
|
||||
final response = await _repository.createVerification(
|
||||
SignupStartRequest(
|
||||
username: state.username.value,
|
||||
email: state.email.value,
|
||||
password: state.password.value,
|
||||
inviteCode: state.inviteCode.isNotEmpty ? state.inviteCode : null,
|
||||
),
|
||||
);
|
||||
emit(
|
||||
state.copyWith(
|
||||
isSending: false,
|
||||
status: FormzSubmissionStatus.success,
|
||||
pendingEmail: response.email,
|
||||
codeSent: true,
|
||||
errorMessage: null,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
final message = e is ApiException ? e.message : '验证码发送失败,请重试';
|
||||
emit(
|
||||
state.copyWith(
|
||||
isSending: false,
|
||||
status: FormzSubmissionStatus.failure,
|
||||
errorMessage: message,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,314 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:formz/formz.dart';
|
||||
import '../../../../core/form_inputs/form_inputs.dart';
|
||||
import '../../data/auth_repository.dart';
|
||||
|
||||
class ResetPasswordState extends Equatable {
|
||||
final Email email;
|
||||
final VerificationCode code;
|
||||
final Password newPassword;
|
||||
final Password confirmPassword;
|
||||
final FormzSubmissionStatus status;
|
||||
final String? errorMessage;
|
||||
final bool isSuccess;
|
||||
final int resendCountdown;
|
||||
final bool codeSent;
|
||||
|
||||
const ResetPasswordState({
|
||||
this.email = const Email.pure(),
|
||||
this.code = const VerificationCode.pure(),
|
||||
this.newPassword = const Password.pure(),
|
||||
this.confirmPassword = const Password.pure(),
|
||||
this.status = FormzSubmissionStatus.initial,
|
||||
this.errorMessage,
|
||||
this.isSuccess = false,
|
||||
this.resendCountdown = 0,
|
||||
this.codeSent = false,
|
||||
});
|
||||
|
||||
bool get canSubmit {
|
||||
if (!codeSent) {
|
||||
return email.isValid && status != FormzSubmissionStatus.inProgress;
|
||||
}
|
||||
return email.isValid &&
|
||||
code.isValid &&
|
||||
newPassword.isValid &&
|
||||
confirmPassword.isValid &&
|
||||
newPassword.value == confirmPassword.value &&
|
||||
status != FormzSubmissionStatus.inProgress;
|
||||
}
|
||||
|
||||
ResetPasswordState copyWith({
|
||||
Email? email,
|
||||
VerificationCode? code,
|
||||
Password? newPassword,
|
||||
Password? confirmPassword,
|
||||
FormzSubmissionStatus? status,
|
||||
String? errorMessage,
|
||||
bool? isSuccess,
|
||||
int? resendCountdown,
|
||||
bool? codeSent,
|
||||
}) {
|
||||
return ResetPasswordState(
|
||||
email: email ?? this.email,
|
||||
code: code ?? this.code,
|
||||
newPassword: newPassword ?? this.newPassword,
|
||||
confirmPassword: confirmPassword ?? this.confirmPassword,
|
||||
status: status ?? this.status,
|
||||
errorMessage: errorMessage,
|
||||
isSuccess: isSuccess ?? this.isSuccess,
|
||||
resendCountdown: resendCountdown ?? this.resendCountdown,
|
||||
codeSent: codeSent ?? this.codeSent,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
email,
|
||||
code,
|
||||
newPassword,
|
||||
confirmPassword,
|
||||
status,
|
||||
errorMessage,
|
||||
isSuccess,
|
||||
resendCountdown,
|
||||
codeSent,
|
||||
];
|
||||
}
|
||||
|
||||
class ResetPasswordCubit extends Cubit<ResetPasswordState> {
|
||||
final AuthRepository _repository;
|
||||
Timer? _resendTimer;
|
||||
|
||||
ResetPasswordCubit(this._repository) : super(const ResetPasswordState());
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_resendTimer?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void emailChanged(String value) {
|
||||
emit(state.copyWith(email: Email.dirty(value), errorMessage: null));
|
||||
}
|
||||
|
||||
void codeChanged(String value) {
|
||||
emit(
|
||||
state.copyWith(code: VerificationCode.dirty(value), errorMessage: null),
|
||||
);
|
||||
}
|
||||
|
||||
void newPasswordChanged(String value) {
|
||||
emit(
|
||||
state.copyWith(newPassword: Password.dirty(value), errorMessage: null),
|
||||
);
|
||||
}
|
||||
|
||||
void confirmPasswordChanged(String value) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
confirmPassword: Password.dirty(value),
|
||||
errorMessage: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> sendCode() async {
|
||||
if (state.status == FormzSubmissionStatus.inProgress ||
|
||||
state.resendCountdown > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!state.email.isValid) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: FormzSubmissionStatus.failure,
|
||||
errorMessage: state.email.value.isEmpty ? '请输入邮箱' : '邮箱格式不正确',
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: FormzSubmissionStatus.inProgress,
|
||||
codeSent: true,
|
||||
resendCountdown: 60,
|
||||
errorMessage: null,
|
||||
),
|
||||
);
|
||||
_startResendCountdown();
|
||||
|
||||
try {
|
||||
await _repository.requestPasswordReset(state.email.value);
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: FormzSubmissionStatus.success,
|
||||
errorMessage: 'CODE_SENT_SUCCESS',
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
_cancelResendCountdown();
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: FormzSubmissionStatus.failure,
|
||||
codeSent: false,
|
||||
resendCountdown: 0,
|
||||
errorMessage: '网络错误,请稍后重试',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _cancelResendCountdown() {
|
||||
_resendTimer?.cancel();
|
||||
}
|
||||
|
||||
void _startResendCountdown() {
|
||||
_cancelResendCountdown();
|
||||
_resendTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||
final newCountdown = state.resendCountdown - 1;
|
||||
if (newCountdown <= 0) {
|
||||
timer.cancel();
|
||||
emit(state.copyWith(resendCountdown: 0));
|
||||
} else {
|
||||
emit(state.copyWith(resendCountdown: newCountdown));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> resendCode() async {
|
||||
if (state.resendCountdown > 0 ||
|
||||
state.status == FormzSubmissionStatus.inProgress) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!state.email.isValid) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: FormzSubmissionStatus.failure,
|
||||
errorMessage: state.email.value.isEmpty ? '请输入邮箱' : '邮箱格式不正确',
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: FormzSubmissionStatus.inProgress,
|
||||
codeSent: true,
|
||||
resendCountdown: 60,
|
||||
errorMessage: null,
|
||||
),
|
||||
);
|
||||
_startResendCountdown();
|
||||
|
||||
try {
|
||||
await _repository.requestPasswordReset(state.email.value);
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: FormzSubmissionStatus.success,
|
||||
errorMessage: 'CODE_SENT_SUCCESS',
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
_cancelResendCountdown();
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: FormzSubmissionStatus.failure,
|
||||
resendCountdown: 0,
|
||||
errorMessage: '网络错误,请稍后重试',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> submit() async {
|
||||
if (!state.codeSent) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: FormzSubmissionStatus.failure,
|
||||
errorMessage: '请先获取验证码',
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!state.email.isValid) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: FormzSubmissionStatus.failure,
|
||||
errorMessage: '请输入有效的邮箱地址',
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!state.code.isValid) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: FormzSubmissionStatus.failure,
|
||||
errorMessage: '请输入6位验证码',
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!state.newPassword.isValid) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: FormzSubmissionStatus.failure,
|
||||
errorMessage: '新密码至少6位',
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!state.confirmPassword.isValid) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: FormzSubmissionStatus.failure,
|
||||
errorMessage: '请输入确认密码',
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.newPassword.value != state.confirmPassword.value) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: FormzSubmissionStatus.failure,
|
||||
errorMessage: '两次密码输入不一致',
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: FormzSubmissionStatus.inProgress,
|
||||
errorMessage: null,
|
||||
),
|
||||
);
|
||||
|
||||
try {
|
||||
await _repository.confirmPasswordReset(
|
||||
email: state.email.value,
|
||||
token: state.code.value,
|
||||
newPassword: state.newPassword.value,
|
||||
);
|
||||
emit(
|
||||
state.copyWith(status: FormzSubmissionStatus.success, isSuccess: true),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: FormzSubmissionStatus.failure,
|
||||
errorMessage: '密码重置失败,请检查验证码',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user